变动侦测分为两种类型,一种是“”推,另一种是“拉”。
Angular和react的变动侦测都属于“拉”,也就是说,当状态发生变化时,它不晓得哪个状态发生变化了,只晓得状态有可能扭转了,而后就会发送一个信号通知框架,框架收到信号后,就会进行一个暴力比照找到哪些DOM须要从新渲染的。这也是Angular的脏查看(基于zoom.js,利用$digest函数触发)的过程,react用的是虚构DOM。
Vue的变动侦测属于“推”。当状态发生变化,Vue在肯定水平上能马上晓得哪些状态产生扭转,具备更细粒度的更新;也因为粒度越细,每个状态绑定的依赖就越多,依赖追踪在内存中的开销就越大,因而,Vue也引入了虚构DOM的概念,将一个状态的细粒度绑定到组件(Vue的另一外围:单文件组件化),这样子当状态产生扭转,就会告诉到组件,组件外部再应用虚构DOM进行比照更新。
家喻户晓,Vue2.x的版本应用Object.defineProperty,Vue3.x应用ES6的Proxy来进行变动侦测的。上面次要讲Object和Array的变动侦测:
(注:以下代码块来自Vue2.6.10源码)

一、Object的变动侦测
1.Object通过Object.defineProperty将属性转换成getter/setter的模式来追踪变动,在getter中收集依赖,在setter中触发依赖。

第一版Object.defineProperty:

function defineReactive$$1 (obj, key, val) {     Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get: function reactiveGetter () {        return val      },      set: function reactiveSetter (newVal) {         if(val === newVal){            return         }         val = newVal;      }    });  }    

2.在getter中收集依赖,依赖被存储在Dep中,在Dep中对依赖进行增加,删除以及更新等操作。

Dep的封装:var uid = 0;var Dep = function Dep () {    this.id = uid++;    this.subs = [];  };  Dep.prototype.addSub = function addSub (sub) {    this.subs.push(sub);  };  Dep.prototype.removeSub = function removeSub (sub) {    remove(this.subs, sub);  };  Dep.prototype.depend = function depend () {    if (Dep.target) {      Dep.target.addDep(this);    }  };  Dep.prototype.notify = function notify () {    // stabilize the subscriber list first    var subs = this.subs.slice();    for (var i = 0, l = subs.length; i < l; i++) {      subs[i].update();    }  };  // The current target watcher being evaluated.  // This is globally unique because only one watcher  // can be evaluated at a time.  Dep.target = null;  var targetStack = [];  function pushTarget (target) {    targetStack.push(target);    Dep.target = target;  }  function popTarget () {    targetStack.pop();    Dep.target = targetStack[targetStack.length - 1];  }  function remove (arr, item) {    if (arr.length) {      var index = arr.indexOf(item);      if (index > -1) {        return arr.splice(index, 1)      }    }  }

第二版Object.defineProperty:

function defineReactive$$1 (obj,key,val) {    var dep = new Dep();//新增    Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get: function reactiveGetter () {        if (Dep.target) {//新增          dep.depend();//新增        }        return val      },      set: function reactiveSetter (newVal) {        if (newVal === val) {          return        }        val = newVal;        dep.notify();//新增      }    });  }

3.所谓的依赖就是wather,只有watcher触发的getter就会去收集依赖到Dep中去,当数据发生变化时,会循环依赖列表,把所有的watcher告诉一遍。
watcher原理:先把本人设置到全局惟一的指定地位(pushTarget(this)),而后读取数据( value = this.getter.call(vm, vm);)触发该数据的getter。接着,在getter中就会从全局惟一的那个地位读取正在读取数据的watcher,并把这个watcher收集到Dep中。

  /**   * A watcher parses an expression, collects dependencies,   * and fires callback when the expression value changes.   * This is used for both the $watch() api and directives.   */  var Watcher = function Watcher (vm,expOrFn,cb) {    this.vm = vm;    this.cb = cb;    if (typeof expOrFn === 'function') {      this.getter = expOrFn;    } else {      this.getter = parsePath(expOrFn);//parsePath读取一个字符串的keypath    }  };  /**   * Evaluate the getter, and re-collect dependencies.   */  Watcher.prototype.get = function get () {    pushTarget(this);//this就是以后watcher实例。看下面的pushTarget,把watcher实例赋值给Dep。target    var value;    var vm = this.vm;    try {      value = this.getter.call(vm, vm);//读取值的时候,触发getter,就能够将this被动增加到Dep(依赖收集)    } catch (e) {        throw e    } finally {          }    return value  };  /**   * Subscriber interface.   * Will be called when a dependency changes.   */  Watcher.prototype.update = function update () {    /* istanbul ignore else */    if (this.lazy) {      this.dirty = true;    } else if (this.sync) {      this.run();    } else {      queueWatcher(this);    }  };

4.创立Observer办法递归object中的所有数据(包含子数据)都转换成响应式模式。只有Object类型才会调用walk将每个属性转换成getter/setter模式侦测,在defineReactive$$1中新增observe(new Observer(value);)来递归子属性。

var Observer = function Observer (value) {    this.value = value;    if (Array.isArray(value)) {    } else {      this.walk(value);    }  };    Observer.prototype.walk = function walk (obj) {    var keys = Object.keys(obj);    for (var i = 0; i < keys.length; i++) {      defineReactive$$1(obj, keys[i]);    }  };

5.无奈跟踪新增属性和删除属性,能够用Vue提供的vm.$set和vm.$delete解决
6.总结图解:

二、Array的变动侦测
1.在Observer中应用拦截器笼罩那些行将转换成响应式的Array类型数据的原型。(value.__proto__=Object.create(Array.prototype);)

function copyAugment (target, src, keys) {  for (var i = 0, l = keys.length; i < l; i++) {    var key = keys[i];    def(target, key, src[key]);  }} function def (obj, key, val, enumerable) {    Object.defineProperty(obj, key, {      value: val,      enumerable: !!enumerable,      writable: true,      configurable: true    });  }  //鉴于有些浏览器不反对__proto__属性,在这个判断对象中是否有__proto__  var hasProto = '__proto__' in {};  var arrayKeys = Object.getOwnPropertyNames(arrayMethods);  var Observer = function Observer (value) {  this.dep = new Dep();//创立一个Dep实例,寄存依赖列表  def(value, '__ob__', this);//在value上减少一个不可枚举的属性__ob__,这个属性的值是Observer实例。这样就能够通过__ob__拜访Observer实例,  if (Array.isArray(value)) {      if (hasProto) {      //__proto__是指向原型的,通过它来笼罩侦测数据的原型;Object.create返回一个带有Array原型对象和属性的新对象。        value.__proto__=Object.create(Array.prototype);      } else {        copyAugment(value, arrayMethods, arrayKeys);      }    }  };
2.Array也是在getter中收集依赖,在拦截器中触发依赖,并将依赖保留在Observer实例的Dep中。function observe (value, asRootData) {    if (!isObject(value) || value instanceof VNode) {      return    }    var ob;    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {      ob = value.__ob__;    } else {      ob = new Observer(value);    }        return ob  }function defineReactive$$1 (obj, key, val) {     var childOb = observe(val);//为val创立Observer实例     Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get: function reactiveGetter () {      if (childOb) {           childOb.dep.depend();//收集依赖         }        return val      },      set: function reactiveSetter (newVal) {         if(val === newVal){            return         }         val = newVal;      }    });  }

3.向依赖发送告诉(通过this.__ob__拜访Observer实例,调用Observer中Dep的notify发送告诉依赖)

var methodsToPatch = [    'push',    'pop',    'shift',    'unshift',    'splice',    'sort',    'reverse'  ];  /**   * Intercept mutating methods and emit events   */  methodsToPatch.forEach(function (method) {    def(arrayMethods, method, function mutator () {      var ob = this.__ob__;//重点      var inserted;//获取数组中的新增元素      switch (method) {        case 'push':        case 'unshift':          inserted = args;          break        case 'splice':          inserted = args.slice(2);          break      }      //侦听新增元素的变动      if (inserted) { ob.observeArray(inserted); }      // notify change      ob.dep.notify();//重点      return result    });  });

4.监听数组中元素的变动

var Observer = function Observer (value) {    if (Array.isArray(value)) {      this.observeArray(value);    } else {      this.walk(value);    }  };  //侦听每一项  Observer.prototype.observeArray = function observeArray (items) {    for (var i = 0, l = items.length; i < l; i++) {      observe(items[i]);    }  };

5.因为Array的变动侦测是通过拦挡原型的形式实现的,对于this.lsit[0]=2或this.list.length=0;是无奈拦挡侦测的。

总结:

  • Array和Object都是在getter触发收集依赖的
  • Object通过Observer(创立实例)递归所有数据的侦测,当从外界读取数据,在watcher读取数据,触发getter,将依赖(watcher)收集到Dep中,当数据发生变化时,会触发setter,从而向Dep中的watcher依赖发送告诉,watcher接管到告诉之后,告诉外界视图或者回调函数
  • Array通过Observer(创立实例)递归所有数据并创立拦截器笼罩原型的形式进行侦测,在Obserer办法中给value(侦测得数组)新增一个不可枚举的__ob__属性,并且该属性的值就是Observer实例;在getter中将依赖收集在Observer实例中的Dep去,当数据发生变化时,通过this.__ob__.dep拜访Observer实例的dep去向依赖发送告诉。