什么是Proxy

Proxy翻译过来即为代理,当对一个对象进行任何操作/访问的时候,都必须经过这层代理。例如当你想要给一个对象增加一个属性的时候,经过代理的处理可在对象上增加你想要的数据结构。

ES6原生提供Proxy构造函数,Proxy对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)——MDN。

语法

const p = new Proxy(target, handler)

target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数、甚至另一个代理)。

handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时p的行为。MDN文档

示例

const p = new Proxy({}, {
  set(target, propKey, value) {
    if (propKey === 'name') {
      throw new TypeError('name属性不允许修改');
    }
    target[propKey] = value;
  }
});

p.name = 'proxy'; // TypeError: name属性不允许修改
p.sex = 'male'; 
console.log(p.sex); // male

为什么是Proxy

Vue2.x Object.defineProperty()

MDN上对Object.defineProperty()的说明,该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

在Vue2.x中,在递归遍历data中的数据时,使用Object.defineProperty()劫持getter和setter,在getter中做数据依赖收集处理,在setter中监听数据的变化,并通知订阅当前数据的地方。部分源码

// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set

let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
      dep.depend()
      if (childOb) {
        childOb.dep.depend()
        if (Array.isArray(value)) {
          dependArray(value)
        }
      }
    }
    return value
  },
  set: function reactiveSetter (newVal) {
    const value = getter ? getter.call(obj) : val
    /* eslint-disable no-self-compare */
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return
    }
    /* eslint-enable no-self-compare */
    if (process.env.NODE_ENV !== 'production' && customSetter) {
      customSetter()
    }
    if (setter) {
      setter.call(obj, newVal)
    } else {
      val = newVal
    }
    childOb = !shallow && observe(newVal)
    dep.notify()
  }
})

存在的问题

1、检测不到对象属性的添加和删除。当在对象上新加一个属性时,新加的属性并没有加入vue监测数据更新的机制(因为实在初始化之后添加的)。

2、无法监控数组下标的变化,导致直接通过数组下标给数组赋值,不能实时响应。

3、当data中数据比较多且层级很深的时候,会有性能问题,因为要遍历data中所有的数据并给其设置成响应式的。

Vue3.0 使用Proxy

在Vue3.0中通过使用Proxy来代替Object.defineProperty()来解决上述问题,因为当对一个对象进行任何操作/访问的时候,都必须经过这层代理Proxy。

举个例子

使用Object.defineProperty()

class Observer {
  constructor(data) {
    // 遍历参数data的属性,给添加到this上
    for (let key of Object.keys(data)) {
      if (typeof data[key] === 'object') {
        data[key] = new Observer(data[key]);
      }
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          console.log('getter:' + key);
          return data[key];
        },
        set(newVal) {
          console.log('setter:' + key + '=' + newVal);
          if (newVal === data[key]) {
            return;
          }
          data[key] = newVal;
        }
      })
    }
  }
}

const obj = {
  name: 'app'
}
const app = new Observer(obj);
app.name = 'appNewName';
console.log(app.name);
app.age = '18';
console.log(app.age);

执行结果

// 修改obj的name属性
setter:name=appNewName
getter:name
appNewName

// 新增属性
18

使用proxy

const obj = {
  name: 'proxy',
  np: {
    a: 1
  }
}
const p = new Proxy(obj, {
  get(target, propKey, receiver) {
    console.log('getter:' + propKey)
    return Reflect.get(target, propKey, receiver)
  },
  set(target, propKey, value, receiver) {
    console.log('setter:' + propKey + '=' + value)
    Reflect.set(target, propKey, value, receiver)
  }
})

p.name = 'proxyNew';
console.log(p.name);
p.age = '18';
console.log(p.age);

执行结果

// 修改原name属性
setter:name=proxyNew
getter:name
proxyNew

// 新增age属性
setter:age=18
getter:age
18

通过对比,可以看出给对象新增一个属性,使用Object.defineProperty并没有监听到,而使用proxy则可以监听到。

总结

1、Proxy用来操作对象,Object.defineProperty()用来操作对象的属性。

2、Vue 2.x 使用Object.defineProperty() 实现数据响应。

3、Vue 3.0 使用Proxy实现数据响应。

注:

[1] Proxy-MDN

[2] 简单通俗的理解Vue3.0中的Proxy

[3] 深入响应式原理