v3 会使用带有 gettersetter 的处理程序遍历其所有 property 并将其转换为 Proxy 。这是 ES6 仅有的特性,在 v3 版本也使用了 Object.defineProperty 来支持 IE 浏览器。

v2 将 data 整个对象变为可观察对象,通过递归的方式给每个 Key 使用 Object.defineProperty 加上 gettersettter ,如果是数组就重写代理数组对象的七个方法。

Object.defineProperty

观察者例子

  1. let data = { price: 5, quantity: 2 }
  2. let total = 0
  3. let target = null
  4. // 发布订阅器
  5. class Dep {
  6. constructor () {
  7. this.subscribers = []
  8. }
  9. depend () {
  10. if (target && !this.subscribers.includes(target)) {
  11. this.subscribers.push(target)
  12. }
  13. }
  14. notify () {
  15. this.subscribers.forEach(sub => sub())
  16. }
  17. }
  18. // 给每个data的属性挂载发布订阅器,消息代理
  19. Object.keys(data).forEach(key => {
  20. let internalValue = data[key]
  21. const dep = new Dep()
  22. Object.defineProperty(data, key, {
  23. get() {
  24. dep.depend()
  25. return internalValue
  26. },
  27. set(newVal) {
  28. internalValue = newVal
  29. dep.notify()
  30. }
  31. })
  32. })
  33. // 处理添加订阅消息
  34. function watcher(myFun) {
  35. target = myFun
  36. target()
  37. target = null
  38. }
  39. watcher(() => {
  40. total = data.price * data.quantity
  41. })
  42. console.log("total = " + data.total)
  43. data.price = 20
  44. console.log("total = " + data.total)
  45. data.quantity = 10
  46. console.log("total = " + data.total)

缺点

  • 不能监听数组的变化

    • 数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse.
    • Vue 把会修改原来数组的方法定义为变异方法 (mutation method)
    • 非变异方法 (non-mutating method):例如 filter, concat, slice 等,它们都不会修改原始数组,而会返回一个新的数组。
    • Vue 的做法是把这些方法重写来实现数组的劫持。
  • 必须遍历对象的每个属性

    • Object.defineProperty() 多数要配合 Object.keys() 和遍历
  • 必须深层遍历嵌套的对象
    1. let ccobj = {
    2. cmm: {
    3. name: 'kskjkjkjdfg'
    4. }
    5. }

    Proxy

    例子

    ```javascript let data = { price: 5, quantity: 2 } let data_without_proxy = { price: 5, quantity: 2 }; // 保存源对象 let total = 0 let target = null let deps = new Map(); // 创建一个Map对象

Object.keys(data).forEach(key => { // 为每个属性都设置一个依赖实例 并放入 deps 中 deps.set(key, new Dep()); });

class Dep { constructor () { this.subscribers = [] } depend () { if (target && !this.subscribers.includes(target)) { this.subscribers.push(target) } } notify () { this.subscribers.forEach(sub => sub()) } }

data = new Proxy(data_without_proxy, { // 重写数据以在中间创建一个代理 get(obj, key) { deps.get(key).depend(); // <— 依旧为存储target return obj[key]; // 返回原始数据 }, set(obj, key, newVal) { obj[key] = newVal; // 将原始数据设置为新值 deps.get(key).notify(); // <— 依旧为重新运行已存储的targets return true; } });

function watcher(myFun) { target = myFun target() target = null }

watcher(() => { total = data.price * data.quantity; }); ```

优点

  • 针对对象:针对整个对象,而不是对象的某个属性
  • 支持数组:不需要对数组的方法进行重载,省去了众多 hack
  • 嵌套支持: get 里面递归调用 Proxy 并返回
  • 添加新的响应性属性要使用Vue.$set(),删除现有的响应性属性要使用Vue.$delete()。v3不需要了

其他

Proxy中经常用到Reflect.get 和 Reflect.set ,可以理解为类继承里的 super,即调用代理的原来对象的方法