vue2 和 vue3响应式原理区别
vue2 和 vue3响应式原理区别
一、vue2 的实现原理:
对象类型:通过 Object.defineProperty() 对属性的读取,修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get() {}, set() {} })
举例:
let person= {
name: '张三',
age: 18
}
let p = {}
Object.defineProperty(p, 'name', {
configurable: true,
get() {
return person.name
},
set(value) {
console.log('有人修改了name属性,更新界面!')
person.name = value
}
})
Object.defineProperty(p, 'age', {
configrable: true,
get() {
return person.age
},
set(value) {
console.log('有人修改了age属性,更新界面!')
person.age = value
}
})
此时在控制台输入 person 和 p 都可以得到 name 和 age 的信息,且都是双向数据绑定。
接下来做修改属性的操作:p.name = '李四'
结果:
我们可以看到结果中,有触发到 '有人修改了 name 属性,更新界面!',
因此,证明目前修改数据仍然是双向绑定!
修改 p.age 同理...
但是!!!当我们添加一个属性时:p.sex = '男' ;
会发现添加的 sex 属性是没有 set 和 get 方法的,是个死数据。
当我们删除一个属性时:delete p.name,回车后返回结果是 true。再查看 p 时,里面也没有了 name 属性;
同样的,操作这两个方法时,都没有提示‘有人修改了 xxxx 属性,更新界面!’的日志。
因此证明了:这两个操作不是双向绑定的。
结论:
对属性读取能捕获到,对属性修改能捕获到,
但是添加一个属性捕获不到,删除一个已有的数据也捕获不到。
所以,存在两个问题:
a. 新增属性、删除属性,界面不会更新。($set 或 $delete)
b. 直接通过下标修改属性,界面不会自动更新($set、或数组变更方法)
二、vue3 的实现原理:
Proxy(window 中内置的构造函数): window.Proxy
new Proxy() 需要传递两个参数
const p = new Proxy(person, {}) ---> p: 代理数据 / 代理对象 person:源数据 / 源对象
注意:第二个参数必传,即使不传内容,也要用 {} 空对象进行占位。
proxy 的作用:可以让 p 去映射对 person 的操作
例:
let person1 = {
name: '张三',
age: 18
}
const p1 = new Proxy(person1, {})
console.log(person1);
console.log(p1)
控制台输入 person 后可以看到返回值:{name: '张三', age: 18}, 展开可以看到有原型对象
控制台再输入 p 后可以看到返回值:Proxy {name: '张三', age: 18}, 展开可以看到 [[handler]],[[targer]], 原型对象
接下来对属性进行操作:
修改:控制台输入p.name = '李四'
===> '李四'
再输入 person.name
===> 也变成了'李四'
添加:p.sex = '男' ===> '男'
再输入 person.sex ===> '男'; 再输入 p ===> Proxy {name: '李四', age: 18, sex: '男'}
删除:delete p.age ===> true
再输入 person ===> {name: '李四', sex: '男'};
目前完成的只是代理,并不是响应式,想要实现响应式,需要借助 Proxy 中 get 和 set,来捕获到数据的修改
举例:
let person = {
name: '张三',
age: 18
}
const p = new Proxy(person, {
get(target, prop) {
console.log('有人读取了p身上的某个属性')
// 如果get()中没有传参,那么:
// 在控制台测试一下 :p.name ===> '有人读取了p身上的某个属性' undefined
// 有undefined,是因为getter没有return信息
// return 100
// 再在控制台测试一下 :p.name ===> '有人读取了p身上的某个属性' 100
// 这样虽然有返回值了,但是修改任何一个属性,返回的都是100,所以需要有参数!
// get(target, prop)的两个参数:
// 第一个参数:new Proxy()时传入的参数-person,将这个参数称为源对象或源数据!
// 用target来表示。
// 第二个参数:用传入的想要修改的值,去源对象中匹配,匹配到的属性的值!
// 用prop来表示。
// 例:控制台输入p.name修改数据后,prop就是用想要修改的name去person中找name;
// 找到后,返回name对应的值 即'张三'
// return target[prop]
// 验证:控制台输入p.name ===> '张三'
// 虽然成功了,但是return target[prop]是在操作源数据
// 所以需要用到 Reflect.get(target, prop) !!!!!Reflect详解在下面!!!!!!
Reflect.get(target, prop)
},
set(target, prop, value) {
// 注意!!!!在修改和增加时,都能调用!!!!
// 前两个参数同get(), 但是比get()多一个参数:value,即,修改的那个值
console.log(`修改了${prop}属性,改为${value}`)
target[prop] = value
// 控制台输入 p.name = '李四' ===> 修改了name属性,改为'李四'
// 同理,虽然成功了,但是target[prop] = value是在操作源数据
// 所以需要用到 Reflect.set(target, prop, value)
Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
// 参数及内容同get和set
return Reflect.deleteProperty(target, prop)
}
})
Reflect:反射
let obj = {
a: 1,
b: 2
}
// 想要读取obj中的a或b属性,除了可以使用obj.a或obj.b方法外,还可以通过window.Reflect方法
Reflect.get(obj, 'a'); // 1
// 如果想要改数据
Reflect.set(obj, 'a', 666); // true
console.log(obj); // {a: 666, b: 2}
// 如果想要删除数据:
Reflect.deleteProperty(obj, 'a'); // true
console.log(obj); // {b: 2}
Reflect 的作用:
为什么使用 Reflect?
ECMA 正在尝试将 Object 中很多有用的 API, 移植到 Reflect 身上。
例如:Object 身上有的 Object.defineProperty(),Reflect 身上也有
Object.defineProperty() 与 Reflect() 的区别:
1. 想要通过 Object.defineProperty() 去追加属性的时候,如果重名,整个代码全部挂掉;
例如:
// 先写
Object.defineProperty(obj, c, {
get() {
return 3
}
})
// 再写
Object.defineProperty(obj, c, {
get() {
return 4
}
})
// 则会报错
用 Reflect.defineProperty() 去追加属性的时候,即使重名,也不会报错;但是只有第一次追加的值才有效;且 Reflect.defineProperty 是有返回值的
// 先写
Reflect.defineProperty(obj, c, {
get() {
return 4
}
})
// 有返回值
// true
// 再写
Reflect.defineProperty(obj, c, {
get() {
return 4
}
})
// 不会报错,但是只有第一次追加的值才有效
// 有返回值
// false
2. 相比 Object.defineProperty() 来说好分析错误;
Object.defineProperty() 如果有问题要借助 try {} catch() {};
Reflect.defineProperty() 直接通过判断即可。
if(b) {
console.log('某某某操作成功')
} else {
console.log('某某某操作失败')
}
通过 Proxy(代理),拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
通过 Reflect(反射):对被代理对象的属性进行操作。
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
MDN 文档中描述的 Proxy 与 Reflect:
Proxy:https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
Reflect:https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Reflect