使用Proxy实现Vue数据劫持

使用Proxy实现Vue数据劫持

Proxy是什么?

Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

可以这么说Proxy是提供给开发人员一个及其接近浏览器底层的API。在没有Proxy之前,是无法改变原生JS特性的。Proxy一共提供了13种修改原生特性的能力。

let target = {
  a : 1
}

let proxy = new Proxy(target, {
  set(target, key, value, receiver){
    console.log(key);
    return Reflect.set(target, key, value, receiver);
  }
})

Proxy第一个参数是目标对象,第二个参数是一个对象,其属性是当执行一个操作时定义代理的行为的函数。这时可以在第二个参数中加入一个set方法,这时可以监听到是哪个key做了改变。并且通过Reflect的set方法去模拟真实的set方法。

详细看MDN :

为什么说Proxy的性能比Object.defineProperty更好呢?

Vue3.0将放弃Object.defineProperty,改用性能更好的Proxy。

Object.definePropery是什么我就不多说了,现在网上有太多的文章去介绍它了。我们把重点放大Proxy上。

为什么说Proxy的性能比Object.defineProperty更好?

Object.defineProperty只能监听属性,而Proxy能监听整个对象,省去对非对象或数组类型的劫持,也能做到监听。

vue是对对象每一个属性进行Object.defineProperty。

vue中对对象属性进行Object.defineProperty

Proxy:

let target = {
    a : 1,
    b : 2,
    c : 3
  }

  let proxy = new Proxy(target, {
    set(target, key, value, receiver){
      console.log('检测到了set的key为 -> ' + key);
      return Reflect.set(target, key, value, receiver);
    }
  })

  proxy.a = '1'; // 检测到了set的key为 -> a
  proxy.b = '2'; // 检测到了set的key为 -> b
  proxy.c = '3'; // 检测到了set的key为 -> c

这样的话就可以监听整个对象,不用再去遍历所有属性进行劫持了,这样就省去了遍历元素。

第二点,Object.defineProperty不能监测到数组变化

先来看看尤大是怎么做的:

这里就是Array的原生方法进行重写,包括push、pop、shift、unshift、splice、sort、reverse。虽然改写了方法,但是通过下标的方式,还是无法被监测到,用Object.defineProperty是无法实现的。但是Proxy可以,具体实现如下:

let target = [1,2,3]

  let proxy = new Proxy(target, {
    set(target, key, value, receiver){
      console.log('检测到了set的key为 -> ' + key);
      return Reflect.set(target, key, value, receiver);
    }
  })

  proxy[0] = '1'; // 检测到了set的key为 -> 0
  proxy.push('2') // 检测到了set的key为 -> 3  检测到了set的key为 -> length
  proxy.pop();    // 检测到了set的key为 -> length

这时就可以监听数组类型的数据了。


用Proxy实现Vue的数据劫持

完成数据劫持的主要思路就是将传入的对象,以及对象内部全部改为Proxy代理。

function isArray(o){
  return Object.prototype.toString.call(o) === `[object Array]`
}

function isObject(o){
  return Object.prototype.toString.call(o) === `[object Object]`
}

class Observables{
    constructor(
      target,
      handler = {
        set(target, key, value, receiver){
          console.log('检测到了set的key为 -> ' + key);
          return Reflect.set(target, key, value, receiver);
        }
      }
    ){
      if( !isObject(target) && !isArray(target) ){
        throw new TypeError('target 不是数组或对象')
      }

      this._target = JSON.parse( JSON.stringify(target) );  // 避免引用修改  数组不考虑
      this._handler = handler;

      return new Proxy(this._observables(this._target), this._handler);
    }
    // 为每一项为Array或者Object类型数据变为代理
    _observables(target){
      // 遍历对象中的每一项
      for( const key in target ){
        // 如果对象为Object或者Array
        if( isObject(target[key]) || isArray(target[key]) ){
          // 递归遍历
          this._observables(target[key]);
          // 转为Proxy
          target[key] = new Proxy(target[key], this._handler);
        }
      }
      // 将转换好的target返回出去
      return target;
    }
  }

这时我们的Observables函数已经写好了,来测试一下

const o = {
  a : [1, 2],
  c : {
    a : 1,
    b : 2,
    c : [
      [1,2,{
        d : 3
      }]
    ]
  },
  b : 2
}

const ob = new Observables(o);
ob.a.push(3); // 检测到了set的key为 -> 2 检测到了set的key为 -> length
ob.c.a = 2; // 检测到了set的key为 -> a
ob.c.c[0][2].d = 6; // 检测到了set的key为 -> d
ob.b = 44; // 检测到了set的key为 -> b

可以看到push和下标都是可以监测到的。

完成,收工!


谢谢各位小伙伴的阅读

编辑于 2018-11-23 10:53