Vue 源码分析

本文是根据源码 Vue.js v2.0.2 进行分析,内容主要针对options中最常见数据项props、data、computed、methods、watch的工作原理及流程进行详细说明。描述上,重于关键点的罗列,部分未能详细阐述。建议与这篇文章对比学习。

function initState (vm) {
  vm._watchers = [];
  initProps(vm);
  initData(vm);
  initComputed(vm);
  initMethods(vm);
  initWatch(vm);
}

initState

  • new Vue() 时,调用 _init 初始化,=> initState
_watchers
  • 在每个vue实例上定义一个_watchers空数组
  • 在该实例的数据属性发生 new Watcher() 时,将当前watcher放入数组中
  • 当该实例销毁或主动调用unwatch时,将当前watcher从数组中移除
initProps
  • 单独处理Boolean类型的值 (validateProp) ,三种写法:
    1. <x-component :xxx="true"></x-component>
    2. <x-component xxx></x-component>
    3. <x-component xxx="xxx"></x-component>
  • 校验prop值的合法性 assertProp 方法
  • 核心方法 defineReactive$$1 来设置属性的getter和setter,但当props属性被setter时,触发回调来warn不可修改
initData
  • vm._data = data 将当前实例属性$options的data赋值给实例的_data属性
  • 判断data中是否有与props中相同的属性,warning
  • proxy 代理处理,将data中的每个属性getter和setter到vm实例上,getter和setter直接转向了_data的getter和setter方法
  • 观察_data到设置getter和setter,observe(data) => new Observe() => observe.walk => defineReactive$$1
插入Watcher 先看下方脚注
initComputed

initComputed 其实也是将computed中的每个属性通过Object.defineProperty定义到vm上,只不过每个属性的getter是在computed中自定义的,而默认setter为空。当属性为对象时,可显示声明set和get方法。

  • isFunction => 设置getter和setter,setter默认为空。getter通过new Watcher过度一层到自定义的get方法
  • isObject => 设置getter和setter,setter直接bind到自定义set方法,getter通过new Watcher过度一层到自定义的get方法
initMethods

将methods中的方法一一绑定到vm实例上

initWatch
  • createWatcher => 判断handler类型
    • isObject => handler = handler.handler 其他属性:deep, immediate, sync, lazy
      • deep: true 递归watch
      • immediate: true 在创建watcher时就执行一次handler方法
      • sync: true 同步,直接执行watcher.run,否则要排队到queue数组中,等到nextTick异步到这里再依次执行
      • lazy: true 在创建watcher时是否需要立即计算当前watcher的value值,通过computed属性创建的watcher默认lazy=true不需要计算
    • isString => handler = vm[method] handler在vm的methods中查找对应的方法
    • isFunction => handler = handler
  • vm.$watch => 执行实例的$watch方法,创建watcher,同时在这里判断immediate来决定是否执行一次handler

渲染流程

在页面挂载前所new的Watcher会被立即调用this.get(),来回调vm.update更新$el。而在更新前会先执行_render创建vnode。此时就会真正执行页面生成的render方法,获取页面所需的值,从而进入定义好的getter方法。

渲染流程图

更新流程

当data中的属性进入到setter阶段,即进入_data属性的set方法时,会执行依赖修改dep.notify()。依赖修改的最终目的还是通过执行watcher.run来获取属性值,并回调handler,最终更新到vm的$el上。

更新流程图
流程:

  • 改变属性值进入 set
  • 依赖修改 dep.notify
  • watcher更新 watcher.update
  • 直接执行run或者排队进入queue后异步nextTick执行flushSchedulerQueue
  • 根据watcher.id排序queue后,执行watcher.run

nextTick: 异步兼容

  • Promise
  • MutationObserver
  • setTimeout

脚注

Watcher
var Watcher = function Watcher (
  vm,
  expOrFn,
  cb,
  options
) {
  if ( options === void 0 ) options = {};

  this.vm = vm;
  vm._watchers.push(this);
  // options
  this.deep = !!options.deep;
  this.user = !!options.user;
  this.lazy = !!options.lazy;
  this.sync = !!options.sync;
  this.expression = expOrFn.toString();
  this.cb = cb;
  this.id = ++uid$1; // 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();
  // parse expression for getter
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = function () {};
      "development" !== 'production' && warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  this.value = this.lazy
    ? undefined
    : this.get();
};
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 {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          "development" !== 'production' && warn(
            ("Error in watcher \"" + (this.expression) + "\""),
            this.vm
          );
          /* istanbul ignore else */
          if (config.errorHandler) {
            config.errorHandler.call(null, e, this.vm);
          } else {
            throw e
          }
        }
      } else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
};
Logo

前往低代码交流专区

更多推荐