vue 2.x与3.0响应式原理及区别
自我纪录.
vue2.x 响应式
实现原理
对象类型:通过Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'propName', {
get () {},
set () {}
})
存在问题
新增属性、删除属性, 界面不会更新。
直接通过下标修改数组, 界面不会自动更新。
代码
// 源数据
let obj = {
name:'张三',
age:18
}
// 模拟Vue2中实现响应式
let o = {},
Object.defineProperty(obj,'name',{
configurable:true,// true 才可以进行'删'操作
get(){ // 读取name属性时 调用
return obj.name
},
set(value){ // 更改name属性时 调用 并更新页面
obj.name = value
},
})
存在'问题'代码
data(){
obj:{
name:'张三',
age:18
},
list:['吃饭','睡觉','学习']
},
methods:{
addSet(){
// this.obj.sex = '男' //无法响应
// Object.assign给目标对象合并一个新对象 该方法本质上是改变了foo对象的引用,指向了一个新的对象
// this.obj= Object.assign({}, this.obj, {sex: '男'})
// Vue.set === this.set // 需要引入 Vue
this.$set(this.obj,'sex','男')
},
deleteName(){
// delete this.obj.name //无法响应
this.$delete(this.obj,'name')
},
updateList(){
// this.list[0] = '不吃饭' //无法响应
// 数组的方法
// this.list.splice(0,1,'不吃饭')
this.$set(this.list,0,'不吃饭')
}
}
vue3.0 响应式
实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
MDN文档中描述的Proxy与Reflect:
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
代码
new Proxy(data, {
// 拦截读取属性值
get (target, propName) {
return Reflect.get(target, propName)
},
// 拦截设置属性值或添加新属性
set (target, propName, value) {
return Reflect.set(target, propName, value)
},
// 拦截删除属性
deleteProperty (target, propName) {
return Reflect.deleteProperty(target, propName)
}
})
proxy.name = 'tom'
对比
// 源数据
let obj = {
name:'张三',
age:18
}
// 模拟Vue2中实现响应式
let o = {},
Object.defineProperty(obj,'name',{
configurable:true,// true 才可以进行'删'操作
get(){ // 读取name属性时 调用
return obj.name
},
set(value){ // 更改name属性时 调用 并更新页面
return obj.name = value
},
})
// 模拟Vue3中实现响应式
// window.Proxy // 内置的构造函数
// o 映射(代理) obj的操作 第二个参数 必填可用{}占位
// 最终版本
let o = new Proxy('obj',{
get(target,propName){ // 读取 obj 上的 xxx(propName)属性
return Reflect.get(target,propName)
},
set(target,propName,value){ // 修改 obj 上的 xxx(propName)属性 || 追加 obj xxx 属性
return Reflect.set(target,propName,value)
},
deleteProperty(target,propName){// 删除 obj 上的 xxx(propName)属性
return Reflect.deleteProperty(target,propName)
}
})
// 如下演示原有
// 初级版本
let o = new Proxy('obj',{
get(target,propName){ // 读取 obj 上的 xxx(propName)属性
return target[propName]
},
set(target,propName,value){ // 修改 obj 上的 xxx(propName)属性 || 追加 obj xxx 属性
return target[propName] = value
},
deleteProperty(target,propName){// 删除 obj 上的 xxx(propName)属性
return delete target[propName]
}
})
// window.Reflect //window 内置对象
// Reflect.get(obj,'name') //读
// Reflect.set(obj,'name','小明') //改
// Reflect.deleteProperty(obj,'name') //删
// 因为ECMA 语言规范 正在给Object API 尝试移植 到 Reflect 上
// Object.defineProperty(obj,'job',{ // 追加属性
// get(){
// return '前端'
// }
// })
// Object.defineProperty(obj,'job',{ // 追加属性
// get(){
// return '中级前端'
// }
// })
// 会报错 只能用 try{] catch{} 去捕获
try {
Object.defineProperty(obj, 'job', { // 追加属性
get() {
return '前端'
}
})
Object.defineProperty(obj, 'job', { // 追加属性
get() {
return '中级前端'
}
})
} catch (error) {
console.log(error)
}
// Reflect.defineProperty 返回结果是 boolean 追加成功||失败
// 所以框架底层一般都用
const one = Reflect.defineProperty(obj, 'job', { // 追加属性
get() {
return '前端'
}
})
const two = Reflect.defineProperty(obj, 'job', { // 追加属性
get() {
return '中级前端'
}
})
if(one){
console.log('追加成功')
}else{
console.log('追加失败')
}