解析Vue2.0和3.0的响应式原理和异同(带源码)
前言
2019.10.5日发布了Vue3.0,现在2020年了,估计Vue3.0正式版也快出来了。
2.0跟3.0的变化也挺大的,
- 结构: 2.0用Flex ,3.0用 TypeScript。
- 性能: 3.0优化了Virtual Dom的算法。
- 响应式原理:2.0用 Object.defineProperty,3.0用Proxy
- ...
Vue2.0和Vue3.0实现原理
- Vue 2.0
Vue2.0实现MVVM(双向数据绑定)的原理是通过 Object.defineProperty 来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Vue官网也给出了解释:
- Vue 3.0 实现响应式基于ES6: Proxy
Vue2.0和Vue3.0的差异如下:
Vue2.0
- 基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式。
- Object.defineProperty 无法检测到对象属性的添加和删除 。
- 由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让Vue将它转换为响应式。
- 深度监听需要一次性递归,对性能影响比较大。
Vue3.0
- 基于Proxy和Reflect,<可以原生监听数组,可以监听对象属性的添加和删除。
- 不需要一次性遍历data的属性,可以显著提高性能。
- 因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11 。
Vue2.x实现响应式
下面是基于Object.defineProperty ,一步步实现简单版Vue2.0。
- 由于Object.defineProperty 无法监听数组,所以数组类型实现响应式,需要处理。
判断如果是数组类型,就重写数组的原型方法('push','pop','shift',unshift)
// 重新定义数组原型,Object.defineProperty不具备监听数组的方法
const oldArrayProperty = Array.prototype;
const arrProto = Object.create(oldArrayProperty);
["push","pop","shift","unshift","splice"].forEach(
methodName =>
(arrProto[methodName] = function() {
updateView();
oldArrayProperty[methodName].call(this, ...arguments);
})
)
- 将传入的data属性进行深度监听,判断是对象还是数组。
function observer(target){
if(typeof target !== 'object' || target === null){
return target
}
// 如果是数组类型,重写数组原型的方法("push","pop","shift","unshift","splice")
if(Array.isArray(target)){
target.__proto__ == arrProto;
}
// 如果是对象,遍历对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter
for(let key in target){
defineReactive(target,key,target[key])
}
}
-
核心API Object.defineProperty,将传入属性转为 getter/setter
function defineReactive(target, key, value){ // 如果对象有更多的层级,再次调用observer监听方法,实现深层次的监听。 observer(value); Object.defineProperty(target, key, { get(){ return value; }, set(newValue){ // 设置值的时候也需要深度监听 observer(value); if(newValue !== value){ value = newValue; // 数据驱动视图,如果数据改变,就调用视图更新的方法。对应到Vue中是执行VDOM updateView(); } } }) }
-
数据更新会触发视图更新,这是MVVM的绑定原理,这就会涉及到Vue的 template 编译为 render 函数,在执行 Virtual Dom, Diff算法, Vnode等 这些东西了。
function updateView(){ console.log('视图更新') }
5.使用
const data = {
name: "zhangsan",
age: 20,
info: {
address: "北京" // 需要深度监听
},
nums: [10, 20, 30]
};
observer(data);
Vue3.0实现响应式
Vue3.0基于Proxy来做数据大劫持代理,可以原生支持到数组的响应式,不需要重写数组的原型,还可以直接支持新增和删除属性, 比Vue2.x的Object.defineProperty更加的清晰明了。
-
核心代码(非常少)
const proxyData = new Proxy(data, { get(target,key,receive){ // 只处理本身(非原型)的属性 const ownKeys = Reflect.ownKeys(target) if(ownKeys.includes(key)){ console.log('get',key) // 监听 } const result = Reflect.get(target,key,receive) return result }, set(target, key, val, reveive){ // 重复的数据,不处理 const oldVal = target[key] if(val == oldVal){ return true } const result = Reflect.set(target, key, val,reveive) return result }, // 删除属性 deleteProperty(target, key){ const result = Reflect.deleteProperty(target,key) return result } })
-
使用
const data = { name: "zhangsan", age: 20, info: { address: "北京" // 需要深度监听 }, nums: [10, 20, 30] };
直接这样就可以了,也不需要声明,Proxy直接会代理监听data的内容,非常的简单方便,唯一的不足就是部分浏览器无法兼容Proxy,也不能hack,所以目前只能兼容到IE11。
全部源码
可直接将代码复制到chrome浏览器的控制台,直接调试打印。
-
Vue2.0
function defineReactive(target, key, value) { //深度监听 observer(value); Object.defineProperty(target, key, { get() { return value; }, set(newValue) { //深度监听 observer(value); if (newValue !== value) { value = newValue; updateView(); } } }); } function observer(target) { if (typeof target !== "object" || target === null) { return target; } if (Array.isArray(target)) { target.__proto__ = arrProto; } for (let key in target) { defineReactive(target, key, target[key]); } } // 重新定义数组原型 const oldAddrayProperty = Array.prototype; const arrProto = Object.create(oldAddrayProperty); ["push", "pop", "shift", "unshift", "spluce"].forEach( methodName => (arrProto[methodName] = function() { updateView(); oldAddrayProperty[methodName].call(this, ...arguments); }) ); // 视图更新 function updateView() { console.log("视图更新"); } // 声明要响应式的对象 const data = { name: "zhangsan", age: 20, info: { address: "北京" // 需要深度监听 }, nums: [10, 20, 30] }; // 执行响应式 observer(data);
-
Vue3.0
const proxyData = new Proxy(data, { get(target,key,receive){ // 只处理本身(非原型)的属性 const ownKeys = Reflect.ownKeys(target) if(ownKeys.includes(key)){ console.log('get',key) // 监听 } const result = Reflect.get(target,key,receive) return result }, set(target, key, val, reveive){ // 重复的数据,不处理 const oldVal = target[key] if(val == oldVal){ return true } const result = Reflect.set(target, key, val,reveive) console.log('set', key, val) return result }, deleteProperty(target, key){ const result = Reflect.deleteProperty(target,key) console.log('delete property', key) console.log('result',result) return result } }) // 声明要响应式的对象,Proxy会自动代理 const data = { name: "zhangsan", age: 20, info: { address: "北京" // 需要深度监听 }, nums: [10, 20, 30] };