观察者模式

应用场景:

  1. 场景一: 当观察的数据对象发生变化时, 自动调用相应函数。比如 vue 的双向绑定;
  2. 场景二: 每当调用对象里的某个方法时, 就会调用相应'访问'逻辑。比如给测试框架赋能的 spy 函数;

场景一: 双向绑定

Object.defineProperty

使用 Object.defineProperty(obj, props, descriptor) 实现观察者模式, 其也是 vue 双向绑定 的核心, 示例如下(当改变 obj 中的 value 的时候, 自动调用相应相关函数):

var obj = {
  data: { list: [] },
}

Object.defineProperty(obj, 'list', {
  get() {
    return this.data['list']
  },
  set(val) {
    console.log('值被更改了')
    this.data['list'] = val
  }
})

Proxy

Proxy/Reflect 是 ES6 引入的新特性, 也可以使用其完成观察者模式, 示例如下(效果同上):

var obj = {
  value: 0
}

var proxy = new Proxy(obj, {
  set: function(target, key, value, receiver) { // {value: 0}  "value"  1  Proxy {value: 0}
    console.log('调用相应函数')
    Reflect.set(target, key, value, receiver)
  }
})

proxy.value = 1 // 调用相应函数

场景二

下面来实现 sinon 框架的 spy 函数:

const sinon = {
  analyze: {},
  spy: function(obj, fnName) {
    const that = this
    const oldFn = Object.getOwnPropertyDescriptor(obj, fnName).value
    Object.defineProperty(obj, fnName, {
      value: function() {
        oldFn()
        if (that.analyze[fnName]) {
          that.analyze[fnName].count = ++that.analyze[fnName].count
        } else {
          that.analyze[fnName] = {}
          that.analyze[fnName].count = 1
        }
        console.log(`${fnName} 被调用了 ${that.analyze[fnName].count} 次`)
      }
    })
  }
}

const obj = {
  someFn: function() {
    console.log('my name is someFn')
  }
}

sinon.spy(obj, 'someFn')

obj.someFn()
// my name is someFn
// someFn 被调用了 1 次
obj.someFn()
// my name is someFn
// someFn 被调用了 2 次

vue 在 3.0 版本上使用 Proxy 重构的原因

首先罗列 Object.defineProperty() 的缺点:

  1. Object.defineProperty() 不会监测到数组引用不变的操作(比如 push/pop 等);
  2. Object.defineProperty() 只能监测到对象的属性的改变, 即如果有深度嵌套的对象则需要再次给之绑定 Object.defineProperty();

关于 Proxy 的优点

  1. 可以劫持数组的改变;
  2. defineProperty 是对属性的劫持, Proxy 是对对象的劫持;