2.1 Object.defineProperty和Proxy

在介绍这一章的源码分析之前,我们需要掌握一下贯穿整个vue数据代理,监控的技术核心:Object.defineProperty 和 Proxy

2.1.1 Object.defineProperty

官方定义:Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。基本用法: Object.defineProperty(obj, prop, descriptor)

我们可以用来精确添加或修改对象的属性,只需要在descriptor中将属性特性描述清楚,descriptor的属性描述符有两种形式,一种是数据描述符,另一种是存取描述符。

数据描述符

  • configurable:数据是否可删除
  • enumerable:属性是否可枚举
  • value:属性值,默认为undefined
  • writable:属性是否可读写存取描述符

  • configurable:数据可改变

  • enumerable:可枚举
  • get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。
  • set:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。注意: 数据描述符的value,writable 和 存取描述符的get, set属性不能同时存在,否则会抛出异常。

有了Object.defineProperty方法,我们可以方便的利用存取描述符中的getter/setter来进行数据监听,在get,set钩子中分别做不同的操作,这是vue双向数据绑定原理的雏形,我们会在响应式系统的源码分析时具体阐述。

  1. var o = {}
  2. var value;
  3. Object.defineProperty(o, 'a', {
  4. get() {
  5. console.log('获取值')
  6. return value
  7. },
  8. set(v) {
  9. console.log('设置值')
  10. value = v
  11. }
  12. })
  13. o.a = 'sss'
  14. // 设置值
  15. console.log(o.a)
  16. // 获取值
  17. // 'sss'

然而Object.defineProperty的get和set方法只能观测到对象属性的变化,对于数组类型的变化并不能检测到,这是用Object.defineProperty进行数据监控的缺陷,而vue中对于数组类型的方法做了特殊的处理。es6的proxy可以完美的解决这一类问题。

2.1.2 Proxy

Proxy 是es6的语法,和Object.defineProperty一样,也是用于修改某些操作的默认行为,但是和Object.defineProperty不同的是,Proxy针对目标对象,会创建一个新的实例对象,并将目标对象代理到新的实例对象上, 本质的区别就是多了一层代理,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。外界通过操作新的实例对象从而操作真正的目标对象。针对getter和setter的基本用法如下:

  1. var obj = {}
  2. var nobj = new Proxy(obj, {
  3. get(target, property) {
  4. console.log('获取值')
  5. return target[property]
  6. },
  7. set(target, key, value) {
  8. console.log('设置值')
  9. return target[key]
  10. }
  11. })
  12. nobj.a = 1111 // 通过操作代理对象从而映射到目标对象上
  13. // 设置值
  14. // 获取值
  15. // 1111
  16. console.log(nobj.a)

Proxy 支持的拦截操作有13种之多,具体可以参照Proxy,上面提到,Object.defineProperty的get和set方法并不能监测到数组的变化,而Proxy是否能做到呢?

  1. var arr = [1, 2, 3]
  2. let obj = new Proxy(arr, {
  3. get: function (target, key, receiver) {
  4. console.log("获取数组");
  5. return Reflect.get(target, key, receiver);
  6. },
  7. set: function (target, key, receiver) {
  8. console.log('设置数组');
  9. return Reflect.set(target, key, receiver);
  10. }
  11. })
  12. obj.push(222)
  13. // '获取数组'
  14. // '设置数组'

显然proxy可以很容易的监听到数组的变化。