vue2.0到3.0的双向绑定的原理变更到实现

vue2.X实现原理

通过 Object.defineProperty 实现的

<body>
    <div id="app">
        <input type="text" id="txt">
        <p id="show-txt"></p>
    </div>
    <script>
        var obj = {}
        Object.defineProperty(obj, 'txt', {
            get: function () {
                return obj
            },
            set: function (newValue) {
                document.getElementById('txt').value = newValue
                document.getElementById('show-txt').innerHTML = newValue
            }
        })
        document.addEventListener('keyup', function (e) {
            obj.txt = e.target.value
        })
    </script>
</body>

关于defineProperty的缺点很明显:

  • Object.defineProperty监听的是对象的属性,如果对象比较复杂,需要逐个深层遍历他的属性来实现监听,耗费性能

  • Object.defineProperty无法监听数组的变化,使Vue不得不对数组做了额外的hack。

vue3.0实现原理

vue3.0是基于proxy对象实现的

  // 语法
 let p = new Proxy(target, handler);

这里重点说一下handlerhandler本身就是ES6所新设计的一个对象.它的作用就是用来自定义代理对象的各种可代理操作。它本身一共有13中方法,每种方法都可以代理一种操作,常用的几种方法如下:

// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
handler.defineProperty()

// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()

// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()

// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()

// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()

// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()

// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.apply()

// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
handler.construct()

与其说vue3.0的新特性,倒不如说是Proxy的优点:

  • 直接监听对象
  • 可以监听数组变化
  • 多种拦截方式更加强大

同样的功能,那么proxy如何实现呢?

    let input = document.getElementById('txt')
    let p = document.getElementById('show-txt')
    const obj = {}
    const newObj = new Proxy(obj, {
      get: function (target, key, receiver) {
        console.log(target, key, receiver, 'get');
        return Reflect.get(target, key, receiver);
      },
      set: function (target, key, value, receiver) {
        console.log(target, key, value, receiver, 'set');
        if (key === "txt") {
          input.value =  value
          p.innerHTML = value;
        }
        return Reflect.set(target, key, value, receiver);
      }
    })
    input.addEventListener('keyup', function(e) {
      newObj.txt = e.target.value;
    })