一、Object.defineProperty
定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
为什么能实现响应式:
通过 defineProperty 两个属性,get 和 set
- get
属性的getter函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象 (由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值
- set
属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined
定义一个响应式函数defineReactive
function update(){
app.innerText = obj.foo
}
function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
get(){
console.log(`get ${key}:${val}`);
return val
},
set(newVal){
if(newVal !== val){
val = newVal
update()
}
}
})
}
调用 definReactive,数据发生变化触发 update方法,实现数据响应式
const obj = {}
definReactive(obj,'foo','')
setTimeout(()=>{
obj.foo = new Date().toLocaleTimeString()
},1000)
在对象存在多个 key 情况下,需要进行遍历
function observe(obj){
if(typeof obj !== 'object' || obj == null){
return
}
Object.keys(obj).forEach(key =>{
defineReactive(obj,key,obj[key])
})
}
如果存在嵌套对象的情况,还需要在defineReactive中进行递归
function defineReactive(obj,key,val){
observe(val)
Object.defineProperty(obj,key,{
get(){
console.log(`get ${key}:${val}`);
return val
},
set(newVal){
if(newVal !== val){
val = newVal
update()
}
}
})
}
当给 key 赋值为对象的时候,还需要在 set 属性中进行递归
set(newVal){
if(newVal !== val){
observe(newVal) //新值是对象的情况
notifyUpdate()
}
}
上述例子能够实现对一个对象的基本响应式,但仍然存在诸多问题
现在对一个对象进行删除与添加属性操作,无法劫持到
const obj = {
foo:"foo",
bar:"bar"
}
observe(obj)
delete obj.foo //不可以
obj.jar = 'xxx' // 不可以
当我们对一个数组进行监听的时候,并不那么好使了
const arrData = [1,2,3,4];
arrData.forEach((val,index)=>{
defineProperty(arrData,index,val)
})
arrData.push();// 不可以
arrData.pop();// 不可以
arrDate[0] = 99;// 可以
数组的 api无法劫持到,从而无法实现数据响应式,故增加了 set / delete API ,并且对数组 api 方法进行一个重写。
还有一个问题是,如果存在深层的嵌套对象关系,需要深层的监听,造成性能的浪费
小结
- 检测不到对象属性的添加和删除
- 数组API方法无法监听到
- 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题
二、proxy
Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了
定义一个响应式方法reactive
function reactive(obj){
if(typeof obj !== 'object' && obj != null){
return obj
}
//Proxy相当于在对象外层加拦截
const observed = new Proxy(obj,{
get(target,key,receiver){
const res = Reflect.get(target,key,receiver);
console.log(`获取 ${key}:${res}`)
return res
},
set(target,key,value,receiver){
const res = Reflect.set(target,key,value,receiver)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target,key){
const res = Reflect.deleteProperty(target,key)
console.log(删除 `${key}:${res}`)
return res
}
})
return observed
}
测试:
const state = reactive({
foo:'foo'
})
// 获取
state.foo // 可以
state.foo = 'test' // 可以
state.done = 'true' //可以
delete state.done // 可以
嵌套对象情况,有问题
const state = reactive({
bar:{a:1}
})
state.bar.a = '我是嵌套的值' // 不可以
可以在 get 之上再进行一层代理解决
function reactive(obj){
if(typeof obj !== 'object' && obj != null){
return obj
}
const observed = new Proxy(obj,{
get(target,key,receiver){
const res = Reflect.get(target,key,receiver)
console.log(`获取${key}:${res}`)
return isObject(res) ? reactive(res) : res
},
return observed
})
}
三、 总结
Object.defineProperty 只能遍历对象属性进行劫持
function observe(obj){
if(typeof obj !== 'object' && obj != null){
return obj
}
Object.keys(obj).forEach(key =>{
defineReactive(obj,key,obj[key])
})
}
Proxy 直接可以劫持整个对象,并返回一个新对象,可以只操作新的对象达到响应式目的
function reactive(obj){
if(typeof obj !== 'object' && obj != null){
return obj
}
const observed = new Proxy(obj,{
get(t,k,r){
const res = Reflect.get(t,k,c)
console.log(`获取${k}:${res}`)
return res
},
set(t,k,v,r){
const res = Reflect.set(t,k,v,c)
console.log(`设置${k}:${value}`)
return res
},
deleteProperty(t,k){
const res = Reflect.deleteProperty(t,k)
console.log(`删除${k}:${res}`)
return res
}
})
return observed
}
Proxy 可以直接监听数组的变化(push/shift/splice)
const obj = [3,2,1]
const proxyObj = reactive(obj)
obj.push(5)
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的
正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set、delete方法)
// 数组重写
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
arrayProto[method] = function () {
originalProto[method].apply(this.arguments)
dep.notice()
}
});
// set、delete
Vue.set(obj,'bar','newbar')
Vue.delete(obj),'bar')
Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/163296.html