前言

vue2 在开发的时候,es6 并未完全普及,因此 vue2 对对象的属性的变化监听采用了 es5 的 Object.defineProperty方法来实现。但是由于 Object.defineProperty的局限性,只能对对象已有的属性进行改造成 get / set实现监听,无法监听到对象的属性新增和属性删除操作。

在对象属性的 get 操作时进行依赖收集,在对象属性的 set 操作时进行依赖的触发。收集的依赖其实就是 Watcher 的实例。而且对象收集和建立 Watcher 二者之间本来毫无关系。是巧妙地让 Watcher 实例化的时候获取了值,触发 get 操作。get 就会知道要收集依赖了。但是这个依赖也就是 Watcher 实例是无法通过 get 的时候传递的,因此,vue 就在 Watcher 实例化的时候将自己放到了一个约定的地方,即window.target处,在触发 get 操作时会去该地方取依赖,即可完成依赖的收集。

实现

  1. class Dep {
  2. constructor() {
  3. // subs 是依赖,依赖就是 Watcher 的实例
  4. this.subs = []
  5. }
  6. addSub(sub) {
  7. this.subs.push(sub);
  8. }
  9. removeSub(sub) {
  10. const index = this.subs.indexOf(sub);
  11. if(index > -1) {
  12. this.subs.splice(index, 1);
  13. }
  14. }
  15. depend() {
  16. if(window.target) {
  17. this.addSub(window.target);
  18. }
  19. }
  20. notify() {
  21. // 遍历通知全部依赖
  22. const subs = this.subs;
  23. for(let i = 0; i < subs.length; i++) {
  24. subs[i].update();
  25. }
  26. }
  27. }
  28. function defineReactive(data, key, val) {
  29. // 对象嵌套时,递归子属性
  30. if(typeof val === "object") {
  31. new Observer(val);
  32. }
  33. let dep = new Dep();
  34. Object.defineProperty(data, key, {
  35. enumerable: true,
  36. configurable: true,
  37. get: function() {
  38. // 收集依赖
  39. dep.depend();
  40. return val;
  41. },
  42. set: function (newVal) {
  43. if(newVal === val) return;
  44. val = newVal;
  45. // 通知依赖
  46. dep.notify();
  47. }
  48. })
  49. }
  50. class Watcher {
  51. constructor(data, exp, cb) {
  52. this.data = data;
  53. // 执行 this.getter 就可以从 data 读取值
  54. this.getter = this.parsePath(exp);
  55. this.cb = cb;
  56. // 获取依赖的实际值
  57. this.value = this.get();
  58. }
  59. get() {
  60. window.target = this;
  61. let value = this.getter(this.data);
  62. window.target = undefined;
  63. return value;
  64. }
  65. update() {
  66. const oldVal = this.value;
  67. // 获取新的值,不会再收集一遍依赖,因为没有改变 window.target
  68. this.value = this.getter(this.data);
  69. this.cb(this.value, oldVal);
  70. }
  71. parsePath(path) {
  72. const regex = /[^\w.&]/
  73. if(regex.test(path)) return;
  74. const keys = path.split(".");
  75. return function(obj) {
  76. for(let i = 0; i < keys.length; i++) {
  77. if(!obj) return;
  78. obj = obj[keys[i]];
  79. }
  80. return obj;
  81. }
  82. }
  83. }
  84. class Observer {
  85. constructor(data) {
  86. this.data = data;
  87. if(!Array.isArray(this.data)) {
  88. this.walk(this.data);
  89. }
  90. }
  91. walk(obj) {
  92. const keys = Object.keys(obj);
  93. for(let i = 0; i < keys.length; i++) {
  94. defineReactive(obj, keys[i], obj[keys[i]]);
  95. }
  96. }
  97. }

使用上述的变化侦测。

  1. const data = {
  2. a: {
  3. b: 123,
  4. c: 456
  5. }
  6. }
  7. new Observer(data);
  8. new Watcher(data, 'a.b', (val, oldVal) => {
  9. console.log(`监听到 a.b 变化了,变化后的值为 ${val},旧的值为 ${oldVal}`);
  10. })
  11. setTimeout(() => {
  12. console.log("修改数据")
  13. data.a.b = 789;
  14. }, 3000)