Object.defineProperty的缺点及vue3为何用proxy

这是我以前被问到的一个问题,被问以前没有想过,被问到的时候很懵😯 vue2的数据双向绑定是用的这个Object.defineProperty,vue3会用proxy实现数据劫持。那vue3为何会这么作呢?vue

vue数据双向绑定的实现原理

这个问题我以前试着描述过不少次,可是由于都自认为不够清楚。 vue是利用数据劫持结合发布订阅模式实现的数据双向绑定。在vue的实现里面,简单来讲有四部分:数组

  • mvvm用来初始化数据
  • observer用来对初始数据经过Object.defineProperty添加setter和getter,当取数据(即调用get)的时候添加订阅对象(watcher)到数组里, 当给数据赋值(即调用set)的时候就能知道数据的变化,此时调用发布订阅中心的notify,从而遍历当前这个数据的订阅数组,执行里面全部的watcher,通知变化update。
  • compiler是用来把data编译到dom中。分三步:1.先把真实的dom移入到内存中 fragment,2.编译:提取想要的元素节点v-model和文本节点{{}};3.把编译好的fragment塞回到页面去。第二步骤中会对编译到dom中的data添加watcher,当data变化时,这里的watcher回调也能收到通知获得执行。
  • watcher是oberver和compiler之间通讯的桥梁。

Object.defineProperty的缺点

1.Object.defineProperty的第一个缺陷,没法监听数组变化。 可是vue中是能够监听数组的变化的,那他是怎么实现的呢?使用了如下八种方法:浏览器

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
复制代码

实现示例参考:性能优化

const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];

aryMethods.forEach((method)=> {

    // 这里是原生Array的原型方法
    let original = Array.prototype[method];

   // 将push, pop等封装好的方法定义在对象arrayAugmentations的属性上
   // 注意:是属性而非原型属性
    arrayAugmentations[method] = function () {
        console.log('我被改变啦!');

        // 调用对应的原生方法并返回结果
        return original.apply(this, arguments);
    };

});

let list = ['a', 'b', 'c'];
// 将咱们要监听的数组的原型指针指向上面定义的空数组对象
// 别忘了这个空数组的属性上定义了咱们封装好的push等方法
list.__proto__ = arrayAugmentations;
list.push('d');  // 我被改变啦! 4

// 这里的list2没有被从新定义原型指针,因此就正常输出
let list2 = ['a', 'b', 'c'];
list2.push('d');  // 4
复制代码

以上代码参考:这里bash

2.Object.defineProperty的第二个缺陷,只能劫持对象的属性,所以咱们须要对每一个对象的每一个属性进行遍历,若是属性值也是对象那么须要深度遍历,显然能劫持一个完整的对象是更好的选择。app

vue3为何用proxy?

1.proxy能够直接监听数组的变化; 2.proxy能够监听对象而非属性.它在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。 Proxy直接能够劫持整个对象,并返回一个新对象。dom

总结

  • Proxy返回的是一个新对象,咱们能够只操做新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改;
  • Proxy做为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
  • 固然,Proxy的劣势就是兼容性问题,并且没法用polyfill实现

本文参考:参考资料1mvvm

相关文章
相关标签/搜索