Vue,从Object.defineProperty到Proxy的演变......

众所周知,尤大在Vue3.0的响应式中做了改变,从2.0的Object.defineProperty到现在的Proxy。 为什么会从Object.defineProperty更换到Proxy? 答案:肯定是Proxy更加好使!那有哪些地方比Object.defineProperty更加优秀那?接下来我们往下看~

以下便是我的学习和理解
如有不足之处还请指正!谢谢您的阅读~


Vue2.0中defineProperty是如何去做到进行响应式的:

//源数据
let person = {
    name: '韩梅梅',
    age: 22
}

// 模拟Vue2中实现响应式
let p = {}
Object.defineProperty(p,'name',{
    //配置是否可以删除 对象属性
    configurable:true,
    //读取调用
    get(){
        return person.name
    },
    //修改调用
    set(value){
        console.log('修改了name!')
        person.name = value
    }
})
复制代码

我们再来看一下控制台上面的输出。

QQ图片20211118141501.png 可以看到:

当我们在修改name的时候,我们触发了set()。

当我们读取属性的时候,其实走的是get()这里没有打印出来。

在defineProperty中当我们想去使用delete 去删除某一个属性时,我们需要进行配置一下 configurable属性,为true时可删除,否则不可以,删除。这个状态只可定义一次,而且之后不可改变。

这个就是Vue2.0的响应式核心:

对象类型:Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)

数组类型:通过重写更新数组的一系列方法来实现拦截,(对数组的变更方法进行了包裹)

Object.defineProperty(data, 'count',{
	get() {},
	set() {}
})
复制代码

存在的问题:

  • 新增属性、删除属性、界面不会更新。
  • 直接通过下标修改数组,界面不会自动更新

vue中,实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。这里需要注意,递归的时候由于属性的值可能也是一个对象,在递归处理对象属性值的时候 ,递归循环引用对象很容易出现递归爆栈问题,在vue中已经通过定义obj对象记录已经被设置过setter和getter方法的对象,避免了此问题,但如果需要扩展对象,必须手动给新属性设置setter和getter方法,这就是为什么不在data中预先声明好的属性无法进行双向绑定,需要通过this.$set()来设置。

对于Object.defineProperty可以监测数组下标变化吗?

这个答案是肯定的。

尤大通过原型继承得到一个新的原型对象,在此基础上,劫持了7种常用的数组操作进行了重写,分别是push() 、pop() 、shift()、 unshift() 、splice() 、sort()、 reverse(),Vue.set()对于数组的处理其实就是调用了splice方法...


Proxy

Proxy的出现,给vue响应式带来了极大的便利,比如可以直接劫持数组、对象的改变,可以直接添加对象属性,但是兼容性可能会有些问题...

废话不多说,我们直接来看Vue3.0是如何通过 Proxy进行实现响应式的!

//源数据
let person = {
    name: '韩梅梅',
    age: 22
}
//模拟Vue3中实现原理
//person 源数据
//Proxy代理对象
//Reflectduix1
 const p = new Proxy(person,{
     get(target,prpoName){
         console.log('读取P身上得一个属性--->',prpoName)
         //真正的修改person
         return Reflect.get(target,prpoName)
     },
     //有人读取P的某个属性、或给P追加某一个属性是调用
     set(target,prpoName,value){
         console.log(`有人修改了P身上${prpoName}属性,我要去更新界面`)
         //真正的修改person
        Reflect.set(target,prpoName,value)
     },
     deleteProperty(target, prpoName) {
         console.log(`有人修改了P身上${prpoName}属性,我要去更新界面`)
         console.log(target,prpoName)
         //真正的修改person
        return   Reflect.deleteProperty(target, prpoName)
     }
 })
复制代码

QQ图片20211118142913.png

我们可以很清晰的看到, 这里面使用了三个函数:set()、get()、 deleteProperty() 而在我们的添加元素和修改元素我们都是调用了set()。

在Vue3.0的响应式中,不光是用到了Proxy,还用到了Reflect也就是反射对象。

我个人感觉是使用Reflect有一个很大的好处,最大的作用就是让你的try catch 的少一些(这里指的是你直接去封装框架),使用了Reflect就不在需要进行try catch了,但是感觉也有缺点,就是没有了那种即使的指出报错。

let obj = {a: 1, b: 2}

const d1 = Reflect.defineProperty(obj, 'c', {
    get() {
        return 3
    }
})
const d2 = Reflect.defineProperty(obj, 'c', {
    get() {
        return 4
    }
})

if (d2) {
    console.log('操作成功')
} else {
    console.log('操作失败')
    console.log(obj)
}

// 测试是否继续往下执行
console.log('well')
复制代码

QQ图片20211118145246.png

我们在来聊一下definePropertyProxy的一些不同点:

1、proxy 能观察的类型比defineProperty更丰富

2、proxy 不兼容ie,也没有polyfill,defineProperty 能支持到ie9

3、Object.defineProperty 是劫持对象的属性,新增元素需要再次defineProperty.而proxy劫持的是整个对象,不需要做特殊处理

4、使用defineProperty时,我们修改原来的obj对象就可以触发拦截,而使用proxy,就必须修改代理对象,即proxy的实例

5、对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到

6、数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。


当然Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。想要进一步了解的可以移步去看阮老师的讲解的Proxy、Reflect

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。

  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。

  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。

  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。

  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。

  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。

  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。

  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。

  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。


放一张 浏览器支持Proxy的图

微信图片_20211118151022.jpg

可以看到,Proxy对于IE浏览器简直一点爱都没有...


参考文章:blog.csdn.net/yuqing1008/…

猜你喜欢

转载自juejin.im/post/7031808193344831518