vue响应式原理

2020-04-22  本文已影响0人  晗笑书生
image.png

注意右侧的调用队列 进行初始化的时候 this._init() 初始化的时候调用 initState() initData() initData中会调用根节点的observe 将数据变成响应式对象


image.png
// initData()中调用根节点的 observe 
// observe data
  observe(data, true /* asRootData */);
  observe只处理 对象 
 */
function observe (value, asRootData) {
// 不是对象 或 是 VNode就返回 这是递归的出口
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 这是一个observe的自己
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
//  创建Observer的对象
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

// Observer 对象
var Observer = function Observer (value) {
  this.value = value;
// watch的管理者 收集依赖 和派发更新中使用
  this.dep = new Dep();
  this.vmCount = 0;
// 定义不可枚举的值 作为标识
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    if (hasProto) {
      protoAugment(value, arrayMethods);
    } else {
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
  
    this.walk(value);
  }
};
// 如果是对象 每个对象中调用 defineReactive
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj, keys[i]);
  }
};
// 如果是数组 递归调用observe每一个对象 所以都会走到 defineReactive
Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

下面来看看defineReactive的代码

function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
// Dep是一个watch收集管理器
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
// 如果是不可枚举的 就直接return 所以不可变更对象可以使用Object.frezone
  if (property && property.configurable === false) {
    return
  }
 // ...  
  var childOb = !shallow && observe(val);
// 利用es5的defineProperty  劫持getter/setter  getter的时候 通过dep.depend()建立依赖收集 setter的时候dep.notify() 派发更新
  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;

      if (getter && !setter) { return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}
// 派发更新 执行堆栈dep.notify() watch.update() queueWatcher(this) flushSchedulerQueue() 执行watcher的run方法 如下源码所示
dep.notify
Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  if (process.env.NODE_ENV !== 'production' && !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++) {
// subs是一个watch的数组
    subs[i].update();
  }
};


/**
 * Flush both queues and run the watchers.
 */
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow();
  flushing = true;
  var watcher, id;

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  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();
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1;
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? ("in watcher with expression \"" + (watcher.expression) + "\"")
              : "in a component render function."
          ),
          watcher.vm
        );
        break
      }
    }
  }
image.png

Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
// 执行用户定义的watch的回调 参数 newValue, oldValue
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);
}
}
}
};

// mountComponent vue数据更新驱动视图的原理
// 定义渲染watcher  watch的get函数 对应的updateComponent  会改变视图的变化 既是渲染watch初始化的时候被订阅了对应的响应式数据 数据发生了变化会触发这个视图更新 
// 这里会执行 watch的run 会执行 this.get 求职 会执行 updateComponent函数 这样会触发视图的更新
简单来说 响应数据发生变化 会触发 defineReactive 的setter中this.get 然后会触发 渲染watch的updateComponent 从而达到视图的变化
    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
  hydrating = false;

在vue中, 数据模型下的所有属性,会被Vue使用Object.defineProperty(Vue3使用Proxy)进行数据劫持代码里。响应式的机制是观察者模式, 数据是被观察的一方, 一旦发生变化, 通知所有的观察者, 这样观察者可以做出响应, 当观察者为视图的时候,会更新视图。

Vue的响应式系统的三个重要概念, Dep, Observer, Watcher.

Dep 调度中心-订阅器

Dep是负责调度和订阅watcher的 dep在getter触发了dep.depend 收集依赖 在setter的时候 触发了dep.notify 派发更新

Observer 发布者

Observer是发布者, 主要作用是vm初始化的时候, 调用defineReactive函数, 使用Object.defineProperty方法对对象的每一个子属性进行数据劫持、监听, 就是为每个属性添加上gettersetter, 使属性变成响应式。

Watcher-观察者

Watcher 扮演的角色是订阅者/观察者,他的主要作用是为观察属性提供回调函数以及收集依赖,当被观察的值发生变化时,会接收到来自调度中心Dep的通知,从而触发回调函数。

而Watcher又分为三类,normal-watchercomputed-watcherrender-watcher

normal-watcher:在组件钩子函数watch中定义,即监听的属性改变了,都会触发定义好的回调函数。
computed-watcher:在组件钩子函数computed中定义的,每一个computed属性,最后都会生成一个对应的Watcher对象,但是这类Watcher有个特点:当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备lazy(懒计算)特性。
render-watcher:每一个组件都会有一个render-watcher, 当data/computed中的属性改变的时候,会调用该Watcher来更新组件的视图。
这三种Watcher也有固定的执行顺序,分别是:computed-render -> normal-watcher -> render-watcher。这样就能尽可能的保证,在更新组件视图的时候,computed 属性已经是最新值了,如果 render-watcher 排在 computed-render 前面,就会导致页面更新的时候 computed 值为旧数据。

小结

image.png

Observer 负责将数据进行拦截,Watcher 负责订阅,观察数据变化, Dep 负责接收订阅并通知 Observer 和接收发布并通知所有 Watcher。

整理的文章 可以参考我的语雀

上一篇下一篇

猜你喜欢

热点阅读