重庆大型网站建设,公司搜索seo,镇江网站建设工程,抚州市建设局官网站day-098-ninety-eight-20230624-vue-响应式处理思路-仿源码
vue
vue大体概念 Vue是渐进式框架 所谓渐进式框架#xff0c;就是把一套全面的框架设计体系#xff0c;拆分成为多个框架#xff0c;项目中需要用到那些需求#xff0c;再导入对应的框架#xff0c;以此来保证…day-098-ninety-eight-20230624-vue-响应式处理思路-仿源码
vue
vue大体概念 Vue是渐进式框架 所谓渐进式框架就是把一套全面的框架设计体系拆分成为多个框架项目中需要用到那些需求再导入对应的框架以此来保证外部资源的最小化 Vue2全家桶 Vue2vue框架的核心含单个组件状态管理、组件的管理。 vue-cli用于创建项目的脚手架工具。管控webpack等打包功能。 vuex3实现vue组件间的公共状态管理。 vuex-persist 公共状态持久化存储插件。… vue-router3SPA单页面应用中的路由管理UI组件库 PC端饿了么团队element-ui、阿里antd of vue1、京东iview。移动端有赞vant2、蚂蚁金服cube… … Vue3全家桶 vue3 vite用于创建项目的脚手架工具。 vuex4、piniavue-router4UI组件库 PC端element-plus、antd of vue3…移动端vant3… … Vue生态中完善的项目解决方案 antd pro vue淘系方案-核心是vue3。 pro.antdv官网免费版pro.antdv文档收费版vue3 TS 若依 若依-官网 …
vue常见面试题
Vue2框架常见的面试题 谈谈你对 MVVM / MVC 模式的理解Vue2框架怎么实现对象和数组的监听「Vue2响应式原理」v-model指令实现的原理v-show 与 v-if 有什么区别Class 与 Style 如何动态绑定computed 和 watch 的区别和运用的场景谈谈你对 Vue2 生命周期的理解Vue怎么用 vm.$set() 解决对象新增属性不能响应的问题 开发中常用的Vue指令有哪些
MVVM与MVC
面试题谈谈你对 MVVM / MVC 模式的理解 MVVM模式双向数据驱动如Vue2与Vue3。 model数据层。 在数据层我们需要构建出项目中需要的各种数据与方法。例如响应式状态、属性、计算属性、监听器、过滤器、方法、钩子函数… 说明 在vue2中基于OptionsAPI配置项方式来管理这些内容。 export default {data(){ return { ... } },props:[...],computed:{},watch:{},filters:{},methods:{},...
}script
export default {data(){ return { ...响应式状态 } },props:[...属性],computed:{...计算属性},watch:{...监听器},filters:{...过滤器},methods:{...方法},...
}
/scriptvue3中基于CompositionAPI聚合式函数式编程方式来管理这些内容。 view 视图层。 视图层的原理在Vue框架中我们基于template或jsx语法构建需要的视图最后把视图编译为VirtualDOM(虚拟DOM)再经过DOM-diff进行差异化对比最后把VirtualDOM/补丁包渲染为真实的DOM。 步骤说明 基于template或jsx语法构建需要的视图。 这个主要是用户自己手写的绑定响应式数据与绑定事件。还基于指令控制数据与视图的联系。 把视图编译为VirtualDOM。 在vue2中基于vue-template-compiler插件把视图编译为VirtualDOM。在vue3中基于vue/compiler-sfc插件把视图编译为VirtualDOM。 经过DOM-diff进行差异化对比。 这个是vue内部做的diff算法。 把VirtualDOM/补丁包渲染为真实的DOM。 渲染周期步骤 第一次渲染是VirtualDOM直接渲染为真实的DOM。非初次渲染是补丁包渲染为真实的DOM。 补丁包是通过DOM-diff这一步来对比新旧数据来生成的。性能好能更快渲染。 这一步基本上都是vue内部做的。 viewModel监听层Vue框架的核心。 这个是vue框架内部自动做的。正常不用关心。 监听响应式数据的变化当数据发生改变后通知视图更新。 Vue2中基于Object.defineProperty对数据进行劫持。Vue3中基于ES6中的Proxy对数据进行劫持。基于观察者模式通知视图更新。 监听视图的变化一般指的是Form表单内容的改变当视图内容改变后自动修改对应的数据数据一改视图紧接着跟着更新。 监听视图变化主要是基于v-model指令。 MVC模式单向数据驱动框架如React。 model 数据层。 构建项目中需要的数据和方法。例如状态、属性、钩子函数、普通函数等。 类组件中基于 state/props/实例 构建状态和属性。函数组件中基于useState/useEffect 等Hooks函数完成上述内容的管理。 view 视图层。 在React中基于jsx语法构建需要的视图。React会基于babel-preset-react-app把jsx语法编译为React.createElement格式createElement方法执行会创建出对应的VirtualDOM经过DOM-diff对比最后把VirtualDOM/补丁包基于ReactDOM中的render方法渲染为真实的DOM controller 控制层。 实现事件绑定和相关的业务逻辑。React框架实现了数据更改可以让视图自动更新的机制。但是React不同于Vue并没有对状态做数据劫持。如果打算修改状态后让视图更新需要基于特定的方法去修改状态才可以 类组件中可以用setState/forceUpdate方法。函数组件中可以用useState等Hook函数。 但是React中默认并没有实现对视图的监听这样导致视图内容改变对应的状态也不会自动更改 不过我们可以自己给表单元素做事件绑定当内容改变后手动去修改对应的状态。 总结无论是MVVM还是MVC都是目前前端主流的框架思想都是以数据驱动视图渲染为核心告别传统直接操作DOM的方式转而操作VirtualDOM再配合对应的生态体系让项目开发既高效又提高了性能…
Vue的学习路线
如何学习Vue 第一条线视图线 template或JSX语法 指令「内置的14个指令和自定义指令」JSX语法 VirtualDOM编译的机制掌握DOM-DIFF算法… 第二条线数据线 学习 OptionsAPI/CompositionAPI 中的语法、原理、区别等内容 OptionsAPI选项 学习 MVVM 的原理 数据是如何被监听的「Vue2和Vue3是不一样的」监听数据变化后如何通知视图更新「观察者模式」如何监听视图的变化以及如何让状态跟着更改「v-model」 第三条线组件化开发 单文件组件「含样式私有化方案的原理」类组件和函数组件复合组件通信组件封装的技巧「各种封装技巧」 通用业务组件UI组件库的二次封装通用功能组件Vue.mixinVue.directiveVue.extend… 第四条线实战线 vuex / vue-router … keep-alivetransitioncomponent上拉刷新、下拉加载超长列表性能优化登录/权限管理模型前后端数据通信管理方案…
OptionsAPI选项式数据
OptionsAPI选项-数据OptionsAPI选项-DOMOptionsAPI选项-生命周期钩子OptionsAPI选项-资源OptionsAPI选项-组合OptionsAPI选项-其它
对象和数组的监听
面试题Vue2框架怎么实现对象和数组的监听「Vue2响应式原理」
数据初始化
vue2源码在/node_modules/vue/dist/vue.js中。在new Vue()的时候OptionsAPI中的data是用来构建响应式数据-即状态的。 特点 在data中构建的状态会直接挂载到实例上。 在js中可以基于实例去访问对应的状态 - vm.msg/this.xxx;而挂载到实例上的信息可以直接在视图中访问 - {{msg}}; 在data中构建的状态会被进行数据劫持即get/set。数据劫持的目的是让其变为响应式的这样以后修改此状态信息会触发set劫持函数在此劫持函数中不仅修改了状态值而且还会通知视图更新 - 只有在new的时候写在data中的状态才会默认被数据劫持变为响应式状态。 Vue2响应式源码 在new Vue()后首先执行Vue.prototype._init方法在此方法中做了很多事情例如 向实例上挂载很多内置的私有属性。 带$xxx是我们开发者后续要用到的。带_xxx是给Vue内部用的。 基于callHook$1方法触发beforeCreate()钩子函数执行。初始化上下文中的信息。执行initState方法初始化属性、状态、计算属性、监听器等信息。触发created钩子函数执行。… 执行initState方法的时候 基于initProps$1初始化属性。注册接收属性与属性规则校验。基于initMethods初始化普通函数。基于initComputed$1初始化计算属性。基于initWatch初始化监听器。基于initData初始化状态。… 执行initData方法的时候主要目的就是初始化状态-也就是把信息做响应式数据劫持。 先判断data是否是一个函数组件中的data都是函数如果是函数先把函数执行函数中的this是实例并且传递实例把执行的返回值重新赋值给data。 var data vm.$options.data;
data vm._data isFunction(data) ? getData(data, vm) : data || {};接下来要确保data是一个纯粹的对象。 if (!isPlainObject(data)) {data {};warn$2(data functions should return an object:\n https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function, vm);
}然后基于Object.keys方法获取data对象中的可枚举、非Symbol类型的私有属性然后判断这些属性是否出现在methods和props中如果出现了则报错原因methods/pprops中编写的信息也会直接挂在实例上如果名字则相互冲突了 var keys Object.keys(data);
var props vm.$options.props;
var methods vm.$options.methods;
var i keys.length;
while (i--) {...
}var keys Object.keys(data);
var props vm.$options.props;
var methods vm.$options.methods;
var i keys.length;
while (i--) {var key keys[i];{if (methods hasOwn(methods, key)) {warn$2(Method \.concat(key, \ has already been defined as a data property.), vm);}}if (props hasOwn(props, key)) {warn$2(The data property \.concat(key, \ is already declared as a prop. ) Use prop default value instead., vm);}else if (!isReserved(key)) {proxy(vm, _data, key);}
}最后基于observe函数对data对象中的信息进行数据劫持 var ob observe(data);
ob ob.vmCount;学习总结真实项目中建议把状态数据全部事先写在data中即便不清楚其值也先写上可以赋值初始值。因为只有写在data中的数据在最开始渲染阶段才会被做响应式的数据劫持。 执行observe方法的时候把data对象传递进去。 如果data对象已经被处理过则不会重新处理。 if (value hasOwn(value, __ob__) value.__ob__ instanceof Observer) {return value.__ob__;
}而且data对象必须符合好多条件才可以去处理是数组或者对象、并且没有被冻结/密封/阻止扩展、并且不是ref对象也不是VirtualDOM(vnode)… if (shouldObserve (ssrMockReactivity || !isServerRendering()) (isArray(value) || isPlainObject(value)) Object.isExtensible(value) !value.__v_skip /* ReactiveFlags.SKIP */ !isRef(value) !(value instanceof VNode)) {return new Observer(value, shallow, ssrMockReactivity);
}简洁处理 if (... (isArray(value) || isPlainObject(value)) Object.isExtensible(value) !value.__v_skip !isRef(value) !(value instanceof VNode)) {return new Observer(value, shallow, ssrMockReactivity);
}如果符合了全部条件则创建Obsever类的实例把data对象传递进去进行处理。 学习总结如果某个写在data中的对象我们不期望对其内部做劫持处理此时我们只需要把这个对象基于Object.freeze()冻结即可 因为劫持处理的过程是需要消耗性能和时间的。 例如从服务器获取的数据我们并没有修改其内部某一项值让视图更新的需求那么这些数据压根就不需要做劫持。 执行new Observer(data)对data对象中的每一项进行数据劫持 但凡被处理过的对象都会设置一个__ob__属性属性值是Observer类的实例。 def(value, __ob__, this);然后判断data是数组还是对象两者处理的方式是不一样的。 如果是对象 基于Object.keys()获取对象所有可枚举、非Symbol类型的私有属性。 然后迭代这些成员对每一个成员基于defineReactive()做数据劫持。 var keys Object.keys(value);
for (var i 0; i keys.length; i) {var key keys[i];defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock);
}如果是数组 在Vue2中有一个对象arrayMethods这个对象的特点 对象中有7个方法push/pop/shift/unshift/splice/sort/reverse;对象.__proto__指向Array.prototype 接下来让data这个数组拥有arrayMethods上的这七个方法。 在非IE浏览器中就是让 data数组.__proto__arrayMethods。在IE浏览器中迭代arrayMethods中的每一个方法把这些方法作为data数组的私有方法。 当我们以后调用数组这7个方法的时候用的都是arrayMethods中的这七个方法。 调用重写的这7个方法其内部 获取传递的实参。基于Array.prototype内置的方法实现对应的功能。如果调用的是push/unshift/splice需要把新增的内容基于observeArray进行递归处理实现深度的监听劫持。最后通知视图更新。 执行observeArray对传递的data数组再次进行递归处理。 在defineReactive函数中。 首先又对此对象中的某个成员进行校验验证是否是冻结/密封的如果是则不进行数据劫持。 var property Object.getOwnPropertyDescriptor(obj, key);
if (property property.configurable false) {return;
}然后对对象中此成员的值进行递归处理目的是进行尝试的监听劫持 var childOb !shallow observe(val, false, mock);最后基于Object.defineProperty()对此对象中的这个成员做get/set劫持。 在observeArray方法中 迭代数组中的每一项对每一项再基于observe进行递归处理实现深度的监听劫持。 for (var i 0, l value.length; i l; i) {observe(value[i], false, this.mock);
}深度的监听劫持: let vm new Vue({data: {msg: 哈哈,obj:{x:10,}},
});不仅对msg与obj做了数据劫持还对obj这个对象的x属性也做了数据劫持。 总结Vue2响应式原理针对数组和对象有不同的处理情况 如果是对象基于Object.defineProperty对对象中的每个成员成员特点是可枚举、非Symbol类型进行深度的监听劫持。 当修改成员值的时候触发set劫持函数。在set函数中不仅修改了成员值而且还对新修改的值做监听劫持。最主要的是通知视图更新 如果是数组并不像对象一样没有对数组中的每个索引项做监听劫持。所以基于索引修改数组某一项的值视图是不会更新的。而是重写了数组的7个方法-push/shift/unshift/pop/splice/sort/reverse基于这7个方法修改数组的内容不仅仅修改了内容而且对新修改的内容也会做劫持也会通知视图的更新最后对数组中的每一项内容也基于递归的方式看看是否需要劫持 代码示例 fang/f20230624/day0624/test.html !DOCTYPE html
htmlheadmeta charsetUTF-8 /titleDocument/title/headbodydiv idapp{{msg}}-{{text}}/div/body
/html
!-- script src./node_modules/vue/dist/vue.min.js/script --
script srchttps://cdn.jsdelivr.net/npm/vue2/dist/vue.js/script
scriptlet vm new Vue({data: {msg: 哈哈,},});vm.text 嘿嘿;console.log(实例vm--, vm);setTimeout(() {vm.text hhh;console.log(2000, text改值了但视图并没有自动更新);}, 2000);setTimeout(() {vm.msg 方;console.log(10000, msg改值了但视图会自动更新);}, 10000);vm.$mount(#app);
/script响应式处理思路-仿源码 fang/f20230624/day0624/test.html !DOCTYPE html
htmlheadmeta charsetUTF-8 /titleDocument/title/headbodydiv idapp{{msg}}-{{text}}/div/body
/html
!-- script src./node_modules/vue/dist/vue.min.js/script --
!-- script srchttps://cdn.jsdelivr.net/npm/vue2/dist/vue.js/script --
script// let vm new Vue({// data: {// msg: 哈哈,// obj:{// x:10,// }// },// });// vm.text 嘿嘿;// console.log(实例vm--, vm);// setTimeout(() {// vm.text hhh;// console.log(2000, text改值了但视图并没有自动更新);// }, 2000);// setTimeout(() {// vm.msg 方;// console.log(10000, msg改值了但视图会自动更新);// }, 10000);// vm.$mount(#app);
/scriptscript src./test.js/scriptfang/f20230624/day0624/test.js // 检测是否为纯粹对象。
const toString Object.prototype.toString;
const isPlainObject function isPlainObject(obj) {if (toString.call(obj) ! [object Object]) return false;let proto Object.getPrototypeOf(obj);if (!proto) return true;let Ctor constructor in obj obj.constructor;return Ctor Object;
};// 给对象设置不可枚举的属性。
const define function define(obj, key, value) {Object.defineProperty(obj, key, {value,enumerable: false,writable: true,configurable: true,});return obj;
};// 通知视图更新的方法。
const notify function notify() {console.log(视图更新);
};// 重写数组7个方法的对象。
const arrayProto Array.prototype;
const arrayMethods Object.create(arrayProto);
let methods [push, pop, shift, unshift, splice, sort, reverse];
methods.forEach((method) {let original arrayProto[method]; //对应Array.prototype上的内置方法。define(arrayMethods, method, function mutator(...args) {// 基于内置的方法把功能先实现。this-我们要操作的数组let result original.call(this, ...args);// 对于新增或修改的信息需要基于递归进行深层次的监听劫持。let inserted;switch (method) {case push:case unshift:inserted args;break;case splice:inserted args.slice(2);break;default:break;}if (inserted) {observeArray(inserted);}// 通知视图更新。notify();return result;});
});
// ary.push(100, 200, 300);// 数据劫持的处理。
const defineReactive function defineReactive(obj, key, proxy) {// 对成员的规则再次校验。let property Object.getOwnPropertyDescriptor(obj, key);if (property property.configurable false) {return;}// 对此成员的值进行深度处理。observe(obj[key]);// 对此成员进行数据劫持。Object.defineProperty(obj, key, {get: function reactiveGetter() {return proxy[key];},set: function reactiveSetter(newVal) {// 新老值相同则不进行任何的处理。if (Object.is(newVal, obj[key])) {return;}// 修改值proxy[key] newVal;// 对新设置的值也要进行深度处理。observe(newVal);// 通知视图更新notify();},});
};// 对数组中的每一项进行响应式处理。
const observeArray function observeArray(arr) {// 对传递数组中的每一项都基于observe进行响应式处理。// debugger;arr.forEach((item) {observe(item);});
};
// 对数组/对象进行响应式处理。
const observe function observe(data) {let isArray Array.isArray(data);let isObject isPlainObject(data);// 如果是数组/对象并且不是被冻结/密封/阻止扩展的我们才处理。if ((isArray || isObject) Object.isExtensible(data)) {// 防止套娃操作。if (data.hasOwnProperty(__ob__)) {return data;}define(data, __ob__, true);// 数组重定向其原型指向 对数组每一项进行深度处理。if (isArray) {data.__proto__ arrayMethods; // Object.setPrototypeOf(data, arrayMethods);observeArray(data);}// 对象迭代对象中的每一项对每一项都基于defineProperty进行数据劫持。if (isObject) {let keys Object.keys(data);let proxy { ...data };keys.forEach((key) {defineReactive(data, key, proxy);});}}// console.log(data--, data);return data;
};// -----做测试。
let data {msg: 哈哈,obj: {x: 10,y: {z: [100, 200],},},arr: [1, 2, { n: 1000 }],
};
data.data data;
observe(data);console.log(响应式数据data--, data);setTimeout(() {console.log(data.arr[1] 改);data.arr[1] 改; //视图不更新
}, 2000);
setTimeout(() {console.log(data.msg 改);data.msg 改; //视图更新
}, 1000);
setTimeout(() {console.log(data.arr.push(4, 5, 6));data.arr.push(4, 5, 6); //视图更新
}, 5000);
setTimeout(() {console.log(data.obj.x 改了data.obj.x);data.obj.x 改了data.obj.x; //视图更新
}, 10000);// console.log(data.arr--, data.arr);进阶参考
OptionsAPI选项-数据