blog icon indicating copy to clipboard operation
blog copied to clipboard

Object.defineProperty 与 Proxy

Open vibing opened this issue 4 years ago • 0 comments

Object.definedProperty

在 vue2.x 版本中使用 Object.definedProperty 来劫持数据,实现数据双向绑定。我们来实现一个简单的数据劫持:

function observer(obj) {
  if (typeof obj === 'object') {
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        listener(obj, key);
      }
    }
  }
}

function listener(obj, key) {
  let curValue = obj[key];
  observer(curValue); // 如果curValue是对象,进入递归

  Object.defineProperty(obj, key, {
    get() {
      console.log('get-->', curValue);
      return curValue;
    },
    set(newVal) {
      console.log('set-->', newVal);
      curValue = newVal;
    }
  });
}

上面写了个简单版的数据劫持,现在我们来测试一下:

const obj = {
  name: 'Jack',
  age: 20
};

observer(obj)

现在我们将 obj.name 改为 Tom: 灰常好,set 方法执行了,我们在更改数据前,劫持了数据。

我们再看看获取 name: 灰常好,get 方法也执行了,我们在获取数据前,劫持了数据。

看上去很美好,但 Object.definedProperty 并不是完美的,它在有些情况下无法劫持数据:

  • 对删除和新增的属性无法监听到
  • 数组的变化无法监听,虽然可以触发 get,但无法触发 set 的劫持
  • 如果对象过大、层级过深,那么遍历的时间会更久,引发性能问题

Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

语法

const p  = new Proxy(target, handler)
  • target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

还记得 Object.defineProperty 的语法吗:

Object.defineProperty(obj, key, options);

从语法上就能发现一个最大的不同点:Object.defineProperty 监听的是对象的属性,而 Proxy 监听的是整个对象

所以我们不需要遍历对象,而是直接监听对象:

const obj = {
  arr: []
}

const handler = {
  get(target, key, receiver){
    console.log('get->', target[key])
    if(typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], handler)
    }
    return target[key]
  },
  set(target, key, value){
    console.log('set->', key, value)
    return Reflect.set(target, key, value)
  }
}
const p = new Proxy(obj, handler)

上面我们监听的是一个空的对象,我们直接添加属性看看:

  p.name = 'Jack';

这里注意,我们代理的是 obj,返回的是 p,所以要对 p 进行操作:

真好,set 触发了。

我们再看看获取 name:

真好,get 也触发了。

我们再看看数组:

都可以非常好的监听到 get 、set,太强大了

关于 Proxy 的更多用法和说明,请看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

vibing avatar Jun 22 '20 02:06 vibing