响应式根基:Object.defineProperty 与 Proxy 拦截区别

337 阅读2分钟

概要内容

  • Object.defineProperty 拦截测试
  • Proxy 拦截测试
  • 总结

假如你熟悉Vue,同时好奇心比较强,你肯定会想知道Vue是如何实现响应式的,要了解响应式原理就需要我们了解Object.defineProperty 和 Proxy 这两个API。针对这两个API编写对应的测试例子看看情况如何。

Object.defineProperty 拦截测试


公共代码

let hero = {
        name: '赵云',
        hp: 100,
        sp: 100,
        equipment: ['马', '长枪']
}

Object.keys(hero).forEach(key => {
        let internalValue = hero[key]
        Object.defineProperty(hero, key, {
            get() {
                console.log(`getting key "${key}": ${internalValue}`)
                return internalValue
            },
            set(newValue) {
                console.log(`setting key "${key}": ${internalValue} -> ${newValue}`)
                internalValue = newValue
            }
        })
})

测试1:修改对象-string类型字段,拦截测试

  • code:

    console.log("------修改:对象-string类型字段,拦截测试------");
    hero.name = "吕布"
    console.log(`更改后结果:${hero.name}`);
    
  • 输出结果:

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9480a7cab7a646b797ee416d50fb4a81~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:对象-普通字段修改,可以被get set拦截

测试2:修改对象-数组类型字段,新增元素,拦截测试

  • code:

    console.log("------修改:对象-数组类型字段,新增元素,拦截测试------");
    hero.equipment.push("盔甲");
    console.log(`更改后结果: ${hero.equipment}`);
    
  • 输出结果:

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/682a57acfae04e33a3b9a6cdf0adfb59~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:对象-数组字段新增元素,可以被get拦截,无法被set拦截

测试3:修改对象-添加字段,拦截测试

  • code

    console.log("------修改:对象-添加字段,拦截测试------");
    hero.age = 23;
    console.log(`更改后结果: ${hero.age}`);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e854b05b940459d890ed1e0b0f0807a~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:对象-添加字段,无法被get set拦截

测试4:修改对象-删除字段,拦截测试

  • code

    console.log("------修改:对象-删除字段,拦截测试------");
    delete hero.name;
    console.log(`更改后结果: ${hero.name}`);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7726c0620f9e417c804966834c9e8b7e~tplv-k3u1fbpfcp-zoom-1.image

  • 结论

测试5:修改数组类型字段,新增元素,拦截测试

  • code

    Object.defineProperty(hero.equipment, 'push', {
            value() {
                console.log(`value ${this} -  ${arguments[0]}`)
                this[this.length] = arguments[0]
            }
    });
    
    console.log("---------------第二部分:对象-数组value拦截测试-----------------------");
    console.log("------修改:数组类型字段,新增元素,拦截测试------");
    console.log(`当前 equipment: ${hero.equipment}`);
    hero.equipment.push("盔甲");
    console.log(`新增[盔甲]后 equipment: ${hero.equipment}`);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/80eae0e90c4c4f0f84dda99d91919d65~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:可以利用value 拦截到对象-数组字段元素删减

Proxy拦截测试


公共代码:

let hero = {
        name: '赵云',
        hp: 100,
        sp: 100,
        equipment: ['马', '长枪']
    }

let handler = {
    get: function (target, key, receiver) {
        let value = Reflect.get(target, key, receiver)
        console.log(`getting key "${key}": ${value}`)
        return value;
    },
    set: function (target, key, value, receiver) {
        let oldValue = Reflect.get(target, key, receiver);
        const result = Reflect.set(target, key, value, receiver);
        console.log(`setting key "${key}": ${oldValue} -> ${value}  result:${result}`)
        return result
    }
}

let heroProxy = new Proxy(hero, handler);

测试1:修改对象string类型字段,拦截测试

  • code

    console.log("------修改:对象string类型字段,拦截测试------");
    heroProxy.name = "吕布"
    console.log(`更改后结果:${heroProxy.name}`);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/28d8133a2b524fa2b205e20ccbff44fe~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:对象-普通字段修改,可以被get set拦截

测试2:修改对象数组类型字段,新增元素,拦截测试

  • code

    console.log("------修改:对象数组类型字段,新增元素,拦截测试------");
    heroProxy.equipment.push("盔甲");
    console.log(`更改后结果: ${heroProxy.equipment}`);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f54cc35b18d948898a784626d4af9937~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:对象-数组字段新增元素,可以被get拦截,无法被set拦截

测试3:修改对象-添加字段,拦截测试

  • code

    console.log("------修改:对象-添加字段,拦截测试------");
    heroProxy.age = 23;
    console.log(`更改后结果: ${heroProxy.age}`);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8b772ec60b5d401ab9145fa4fe118b07~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:对象-添加字段,可以被set拦截

测试4:修改对象-删除字段,拦截测试

  • code

    console.log("------修改:对象-删除字段,拦截测试------");
    delete heroProxy.name;
    console.log(`更改后结果: ${heroProxy.name}`);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/077ea4ba7c2f49f99a5facb77f75971f~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:对象-删除字段,可以被get拦截

测试5:修改:对象数组类型字段

  • code

    let heroProxyArray = new Proxy(hero.equipment, handler);
    console.log("------修改:对象数组类型字段,新增元素,拦截测试------");
    console.log(`当前 equipment:`);
    console.log(heroProxyArray);
    
    heroProxyArray.push("匕首");
    
    console.log(`新增[匕首]后 equipment:`);
    console.log(heroProxyArray);
    
  • 输出结果

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b393414d7873477d95cca3e35a6dc515~tplv-k3u1fbpfcp-zoom-1.image

  • 结论:利用proxy 即可轻松拦截数组变化


demo 源码:vue-principle-learn
后续我会把vue原理相关的学习资料和demo都会更新到此仓库,欢迎star收藏~

总结:

  • Object.defineProperty
    • 缺点1:只能遍历对象已存在的属性,进行get set拦截,无法针对新增、删除元素进行拦截
    • 缺点2:针对array 拦截,需要拦截push、shift、pop、unshift等,拦截操作复杂
  • Proxy
    • 优势1:新增、删减字段都能轻松拦截
    • 优势2:针对array 拦截,跟object 属性get set一样拦截,无需特殊处理

参考文献

以上:如发现有问题,欢迎留言指出,我及时更正