实例学习Vue源码第三篇-Vue的响应式原理

实例学习Vue源码第三篇-Vue的响应式原理

  • 1.观察者(Observer)
    • 构造函数
      • walk函数
        • defineReactive$$1()函数
          • Object.defineProperty()函数
  • 2.发布者(Dep)
    • 构造函数
  • 3.订阅者(Watcher)
    • 构造函数
    • Watcher的原型链函数
      • get函数
      • cleanupDeps函数
      • update函数
        • queueWatcher函数
          • nextTick函数
            • flushSchedulerQueue函数
      • run函数

vue的响应式采用的是观察者+发布订阅相结合的模式来实现
1 观察者利用defineProperty劫持属性数据的get,set方法,实现变化的监听;
2 发布者存储每个属性数据相关的订阅者,一旦数据有更新,通知关联的订阅者执行更新操作。
3 订阅者接受发布者的数据变更通知,调用更新函数实现视图的更新。

实例学习Vue源码第三篇-Vue的响应式原理_第1张图片

1.观察者(Observer)

构造函数

//观察者构造函数
var Observer = function Observer (value) {
     
   this.value = value;
   this.dep = new Dep();
   //初始值为0;根数据对象的vmCount为1
   this.vmCount = 0;
   //订阅器和数据对象相互关联:订阅器为数据对象的_ob_属性,数据对象为订阅器的  value属性
   def(value, '__ob__', this);
   if (Array.isArray(value)) {
     
     if (hasProto) {
     
       protoAugment(value, arrayMethods);
     } else {
     
       copyAugment(value, arrayMethods, arrayKeys);
     }
     this.observeArray(value);
   } else {
     
   	 //调用walk方法
     this.walk(value);
   }
 };

上面源码中,首先只关注walk函数;

walk函数

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

执行walk函数时,此时函数调用链如下:

实例学习Vue源码第三篇-Vue的响应式原理_第2张图片

defineReactive$$1()函数

defineReactive$$1()函数会给对象中的每一个属性都定义一个监听;

function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
     
    var dep = new Dep();
    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
     
      return
    }
    // cater for pre-defined getter/setters
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
     
      val = obj[key];
    }

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
     
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
     
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) {
     
          dep.depend();
          if (childOb) {
     
            childOb.dep.depend();
            if (Array.isArray(value)) {
     
              dependArray(value);
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
     
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
     
          return
        }
        /* eslint-enable no-self-compare */
        if (customSetter) {
     
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) {
      return }
        if (setter) {
     
          setter.call(obj, newVal);
        } else {
     
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }
});

defineReactive$$1()函数实现核心是内部调用了Object.defineProperty()函数;

Object.defineProperty()函数

get属性:
1 函数中的get方法Dep.target是数据属性关联的订阅者(Watcher);
2 Dep.target是targetStack栈中取出的当前值,初始时值为null;

  Dep.target = null;
  var targetStack = [];
  function pushTarget (target) {
     
    targetStack.push(target);
    Dep.target = target;
  }
  function popTarget () {
     
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1];
  }

3 订阅者(Watcher)的get方法会往targetStack中push元素;(下面的订阅者源码会展示)
4 dep.depend()会把关联订阅者(Watcher)加入属性发布者dep的subs数组中;
set属性:
1 函数中的set方法会比较改变前后的新旧值;
2 如果值发生变化,会调用属性的发布者的notify通知关联订阅器(Watcher)进行视图更新;
小结:
1 实现观察的核心方法是 Object.defineProperty();
2 Object.defineProperty()中的get方法属性会把订阅者(Watcher)加入属性发布者(Dep)中;
3 Object.defineProperty()中的set方法,通知订阅器进行更新视图操作;

2.发布者(Dep)

构造函数

  //发布者构造函数
  var Dep = function Dep () {
     
  	//id为发布者的唯一标识
    this.id = uid++;
    //保存关联订阅者(Watcher)的数组
    this.subs = [];
  };
  //添加订阅者(Watcher)
  Dep.prototype.addSub = function addSub (sub) {
     
    this.subs.push(sub);
  };
  //删除加订阅者(Watcher)
  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();
    if (!config.async) {
     
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort(function (a, b) {
      return a.id - b.id; });
    }
    for (var i = 0, l = subs.length; i < l; i++) {
     
      //调用关联的订阅者(Watcher)的更新函数
      subs[i].update();
    }
  };

代码中的注释已经解释了构造函数和原型链中函数的作用

3.订阅者(Watcher)

构造函数

var Watcher = function Watcher (
    vm,
    expOrFn,//表达式或函数,如果为函数的话,就赋值为订阅者的getter方法
    cb,
    options,//计算属性的optiongs.lazy值为true
    isRenderWatcher//计算属性的watcher这个参数不传值
  ) {
     
    this.vm = vm;
    if (isRenderWatcher) {
     
      vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    if (options) {
     
      this.deep = !!options.deep;
      this.user = !!options.user;
      this.lazy = !!options.lazy;
      this.sync = !!options.sync;
      this.before = options.before;
    } else {
     
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.cb = cb;
    this.id = ++uid$2; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // for lazy watchers
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // parse expression for getter
    if (typeof expOrFn === 'function') {
     
      this.getter = expOrFn;
    } else {
     
      this.getter = parsePath(expOrFn);
      ...
    }
	//计算订阅者的this.lazy为true,value为undefined
    this.value = this.lazy
      ? undefined
      : this.get();
  };

关注上面源码的最后一行代码;当this.lazy为true的时候不会调用订阅者的get方法;当this.lazy为false时,才会调用订阅者的get方法;
注意点:
initComputed方法中新建计算属性订阅者(Watcher)的lazy属性为true,所以执行不会调用get方法;
当访问计算属性时,通过Watcher的evaluate方法调用Watcher的get方法;

Watcher的原型链函数

get函数

Watcher.prototype.get = function get () {
     
	//把Watcher放入targetStack数组中,并作为当前的Dep.target值;
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
     
		//调用wacher定义时,传入的第二个参数里面的函数
		//传入两个vm,第一个是this的替换,第二个是参数
      value = this.getter.call(vm, vm);
    } catch (e) {
     
      if (this.user) {
     
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
     
        throw e
      }
    } finally {
     
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
     
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  };

1 计算属性的订阅者(Watcher)的getter方法为计算属性的定义函数;
在实例学习Vue源码第一篇-实例实例中,getter方法内容如下:

reversedChildMessage: function () {
     
 // `this` 指向 vm 实例
  return this.childMessage.split('').reverse().join('');
}

2 数据属性的订阅者(Watcher)的getter方法为渲染的Watcher,gtter方法的内容如下:

updateComponent = function () {
     
  vm._update(vm._render(), hydrating);
 };

3 当Watcher和发布者的依赖关系建立完毕后,就会把当前Watcher出栈;

popTarget();

cleanupDeps函数

/**
   * Clean up for dependency collection.
   */
  Watcher.prototype.cleanupDeps = function cleanupDeps () {
     
    var i = this.deps.length;
    while (i--) {
     
      var dep = this.deps[i];
      if (!this.newDepIds.has(dep.id)) {
     
        dep.removeSub(this);
      }
    }
    var tmp = this.depIds;
    this.depIds = this.newDepIds;
    this.newDepIds = tmp;
    this.newDepIds.clear();
    tmp = this.deps;
    this.deps = this.newDeps;
    this.newDeps = tmp;
    this.newDeps.length = 0;
  };

1 保存newDepIds到depIds后,清掉或重置newDepIds ;
2 保存newDeps到deps 后,清掉或重置newDeps ;

update函数

/**
   * 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);
    }
  };

1 当给数据属性重新设新值时,会属性的发布者(Dep)会notify订阅者(Watcher)调用update函数;
2 计算属性的订阅者(W atcher)的lazy值为true,因为不会调用调用queueWatcher方法;
3 数据属性会调用queueWatcher方法,把更新操作放入微观任务队列中,等待主线程空闲时,通过回调函数执行订阅者(Watcher)的run方法;
4 如果this.sync为true,就直接调用run方法,一般不会采用同步调用;

queueWatcher函数

/**
   * Push a watcher into the watcher queue.
   * Jobs with duplicate IDs will be skipped unless it's
   * pushed when the queue is being flushed.
   */
  function queueWatcher (watcher) {
     
    var id = watcher.id;
    if (has[id] == null) {
     
      has[id] = true;
      if (!flushing) {
     
        queue.push(watcher);
      } else {
     
        // if already flushing, splice the watcher based on its id
        // if already past its id, it will be run next immediately.
        var i = queue.length - 1;
        while (i > index && queue[i].id > watcher.id) {
     
          i--;
        }
        queue.splice(i + 1, 0, watcher);
      }
      // queue the flush
      if (!waiting) {
     
        waiting = true;

        if (!config.async) {
     
          flushSchedulerQueue();
          return
        }
        nextTick(flushSchedulerQueue);
      }
    }
  }

把订阅者(Watcher)放入任务队列(queue)中;
has[id] == null的判断,用来对同一个时间片内的重复的设置操作进行去重,只执行一次;

nextTick函数
function nextTick (cb, ctx) {
     
    var _resolve;
    callbacks.push(function () {
     
      if (cb) {
     
        try {
     
          cb.call(ctx);
        } catch (e) {
     
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
     
        _resolve(ctx);
      }
    });
    if (!pending) {
     
      pending = true;
      timerFunc();
    }
    // $flow-disable-line
    if (!cb && typeof Promise !== 'undefined') {
     
      return new Promise(function (resolve) {
     
        _resolve = resolve;
      })
    }
  }

1 nextTick函数会把flushSchedulerQueue函数放入callbacks队列中;
2 nextTick函数调用timerFunc产生一个微观任务,微观任务的回调函数:

function flushCallbacks () {
     
    pending = false;
    var copies = callbacks.slice(0);
    callbacks.length = 0;
    for (var i = 0; i < copies.length; i++) {
     
      copies[i]();
    }
  }

循环遍历callbacks队列,执行队列中保存的所有回调函数(flushSchedulerQueue)

flushSchedulerQueue函数
function flushSchedulerQueue () {
     
    currentFlushTimestamp = getNow();
    flushing = true;
    var watcher, id;
    queue.sort(function (a, b) {
      return a.id - b.id; });

    // do not cache length because more watchers might be pushed
    // as we run existing watchers
    for (index = 0; index < queue.length; index++) {
     
      watcher = queue[index];
      if (watcher.before) {
     
        watcher.before();
      }
      id = watcher.id;
      has[id] = null;
      watcher.run();
      ...
    }

当主线程空闲时间,从队列中取出订阅者,调用订run方法

run函数

/**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  Watcher.prototype.run = function run () {
     
    if (this.active) {
     
      var value = this.get();
      if (
        value !== this.value ||

        isObject(value) ||
        this.deep
      ) {
     
        // set new value
        var oldValue = this.value;
        this.value = value;
        if (this.user) {
     
          try {
     
            this.cb.call(this.vm, value, oldValue);
          } catch (e) {
     
            handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
          }
        } else {
     
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  };

run函数中会调用订阅者(Watcher)的get函数,重新更新视图

updateComponent = function () {
     
  vm._update(vm._render(), hydrating);
 };

你可能感兴趣的:(VUE,vue)