乐趣区

Vuejs源码学习生命周期


残缺代码 (https://github.com/mfaying/si…

初始化阶段

new Vue() 到 created 之间的阶段叫作初始化阶段,这个阶段的次要目标是在 Vue.js 实例上初始化一些属性、事件以及响应式数据。如 props、methods、data、computed、watch、provide 和 inject

模板编译阶段

在 created 钩子函数与 beforeMout 钩子函数之间的阶段是模板编译阶段。
这个阶段的目标是将模板编译为渲染函数,只存在于完整版中。

挂载阶段

beforeMount 钩子函数到 mounted 钩子函数之间是挂载阶段。
在这个阶段,Vue.js 会将其实例挂载到 DOM 元素上,艰深地讲,就是将模板渲染到指定的 DOM 元素中。在挂载的过程中,Vue.js 会开启 Watcher 来继续追踪依赖的变动。
当数据(状态)发生变化时,Watcher 会告诉虚构 DOM 从新渲染视图,并且会在渲染视图前触发 beforeUpdate 钩子函数,渲染结束后触发 updated 钩子函数。

卸载阶段

利用调用 vm.$destroy 办法后,Vue.js 的生命周期会进入卸载阶段。
在这个阶段,Vue.js 会将本身从父组件中删除,勾销实例上所有依赖的追踪并且移除所有的事件监听器。

卸载阶段

原理就是 vm.$destroy 办法的外部原理

模板编译阶段和挂载阶段

也是后面介绍过的。

new Vue() 被调用时产生了什么

当 new Vue() 被调用时,会首先进行一些初始化操作,而后进入模板编译阶段,最初进入挂载阶段。
其具体实现是这样的

function Vue(options) {this._init(options);
}

this._init(options) 执行了生命周期的初始化流程。

_init 办法的外部原理

export function initMixin(Vue) {Vue.prototype._init = function(options) {
    vm.$options = mergeOptions(
      // 获取以后实例中构造函数的 options 及其所有父级实例构造函数的 options
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )

    // 初始化 lifecycle
    initLifecycle(vm);
    // 初始化事件
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate')
    // 初始化 inject
    initInjections(vm); // 在 data/props 前初始化 inject
    // 初始化状态,这里的状态指的是 props、methods、data、computed 以及 watch
    iniiState(vm);
    // 初始化 provide
    initProvide(vm); // 在 data/props 后初始化 provide
    callHook(vm, 'created')


    // 如果有 el 选项,则主动开启模板编译阶段与挂载阶段
    // 如果没有传递 el 选项,则不进入下一个生命周期流程
    // 用户须要执行 vm.$mount 办法,手动开启模板编译阶段与挂载阶段

    if (vm.$options.el) {vm.$mount(vm.$options.el)
    }
  };
}

callHook 函数的外部原理

Vue.js 通过 callHook 函数来触发生命周期钩子

Vue.js 在合并 options 的过程中会找出 options 中所有 key 是否钩子函数的名字,并将它转换成数组。数组因为 Vue.mixin 办法,同一生命周期,会执行多个同名生命周期办法。

上面列出了所有生命周期钩子的函数名

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeUpdate
  6. updated
  7. beforeDestroy
  8. destroyed
  9. activated
  10. deactivated
  11. errorCaptured

实现代码如下:

export function callHook(vm, hook) {const handlers = vm.$options[hook];
  if (handlers) {for (let i = 0, j = handlers.length; i < j; i++) {
      try {handlers[i].call(vm);
      } catch (e) {handleError(e, vm, `${hook} hook`);
      }
    }
  }
}

errorCaptured 与错误处理

它能够实现

  1. 将谬误发送给 config.errorHandler
  2. 如果一个组件继承的链路或其父级隶属链路中存在多个 errorCaptured 钩子函数,则它们将会被雷同的谬误一一唤起。
  3. 一个 errorCaptured 钩子函数可能返回 false 来阻止谬误持续向上流传。它会阻止其余被这个谬误唤起的 errorCaptured 钩子函数和全局的 config.errorHandler。
function handleError(err, vm, info) {if (vm) {
    let cur = vm;
    while ((cur = cur.$parent)) {
      const hooks = cur.$options.errorCaptured;
      if (hooks) {for (let i = 0; i < hooks.length; i++) {
          try {const capture = hooks[i].call(cur, err, vm, info) === false;
            if (capture) return;
          } catch (e) {globalHandleError(e, cur, "errorCaptured hook");
          }
        }
      }
    }
  }
  globalHandleError(err, vm, info);
}

function globalHandleError(err, vm, info) {if (config.errorHandler) {
    try {return config.errorHandler.call(null, err, vm, info);
    } catch (e) {logError(e);
    }
  }
  logError(err);
}

function logError(err) {console.log(err);
}

初始化实例属性

Vue.js 通过 initLifecycle 函数向实例中挂载属性。

export function initLifecycle(vm) {
  const options = vm.$options;

  // 找出第一个非形象父类
  let parent = options.parent;
  if (parent && !options.abstract) {while (parent.$options.abstract && parent.$parent) {parent = parent.$parent;}
    parent.$children.push(vm);
  }

  vm.$parent = parent;
  vm.$root = parent ? parent.$root : vm;

  vm.$children = [];
  vm.$refs = {};

  vm._watcher = null;
  vm._isDestroyed = false;
  vm._isBeingDestroyed = false;
}

初始化事件

初始化事件是指将父组件在模板中应用的 v -on 注册的事件增加到子组件的事件零碎(Vue.js 的事件零碎)中。
这里的事件是父组件在模板中应用 v -on 监听子组件内触发的事件,不是浏览器事件。在初始化 Vue.js 实例时,有可能会接管父组件向子组件注册的事件。而子组件本身在模板中注册的事件,只有在渲染的时候才会依据虚构 DOM 的比照后果来确定是注册事件还是解绑事件。

Vue.js 通过 initEvents 函数来执行初始化事件相干的逻辑

import {updateComponentListeners} from "./updateComponentListeners";

export function initEvents(vm) {vm._events = Object.create(null);
  // 初始化父组件附加的事件
  const listeners = vm.$options._parentListeners;
  if (listeners) {updateComponentListeners(vm, listeners);
  }
}

updateComponentListeners

  1. 如果 listeners 对象中存在某个 key(也就是事件名)在 oldListeners 中不存在,那么阐明这个事件是须要新增的事件;如果 oldListeners 中存在某些 key 在 listeners 中不存在,那么阐明这个事件是须要从事件零碎中移除的。
let target;

function add(event, fn, once) {if (once) {target.$once(event, fn);
  } else {target.$on(event, fn);
  }
}

function remove(event, fn) {target.$off(event, fn);
}

export function updateComponentListeners(vm, listeners, oldListeners) {
  target = vm;
  updateListeners(listeners, oldListeners || {}, add, remove, vm);
}

function isUndef(i) {return i === null || i === undefined;}

function normalizeEvent(name) {const passive = name.charAt(0) === "&";
  name = passive ? name.slice(1) : name;
  const once = name.charAt(0) === "~";
  name = once ? name.slice(1) : name;
  const capture = name.charAt(0) === "!";
  name = capture ? name.slice(1) : name;
  return {
    name,
    once,
    capture,
    passive
  };
}

function updateListeners(on, oldOn, add, remove, vm) {
  let name, cur, old, event;
  for (name in on) {cur = on[name];
    old = oldOn[name];
    event = normalizeEvent(name);
    if (isUndef(cur)) {console.warn("");
    } else if (isUndef(old)) {if (isUndef(cur.fns)) {cur = on[name] = createFnInvoker(cur);
      }
      add(event.name, cur, event.once, event.capture, event, passive);
    } else if (cur !== old) {
      old.fns = cur;
      on[name] = old;
    }
  }
  for (name in oldOn) {if (isUndef(on[name])) {event = normalizeEvent(name);
      remove(event.name, oldOn[name], event.capture);
    }
  }
}

初始化 inject

inject 和 provide 选项须要一起应用,它们容许先人组件向其所有子孙后代注入依赖,并在其上下游关系成立的工夫里始终失效(无论组件档次有多深)。和 react 的上下文个性很类似。

inject 在 data/props 之前初始化,而 provide 在 data/props 前面初始化。这样做的目标是让用户能够在 data/props 中应用 inject 所注入的内容。

初始化 inject 就是应用 inject 配置的 key 从以后组件读取内容,读取不到则读取它的父组件,以此类推。它是一个自底向上获取内容的过程,最终将找到的内容保留到实例(this)中,这样就能够间接在 this 上读取通过 inject 导入的注入内容。

export function initInjections(vm) {const result = resolveInject(vm.$options.inject, vm);
  if (result) {
    // 原理之前介绍过,不将内容转换为响应式。observableState.shouldConvert = false;
    Object.keys(result).forEach(key => {defineReactive(vm, key, result[key]);
    });
    observerState.shouldConvert = true;
  }
}

function resolveInject(inject, vm) {if (inject) {const result = Object.create(null);
    const keys = hasSymbol
      ? Reflect.ownKeys(inject).filter(key => {return Object.getOwnPropertyDescriptor(inject, key).enumerable;
        })
      : Object.keys(inject);

    for (let i = 0; i < keys.length; i++) {const key = keys[i];
      const provideKey = inject[key].from;
      let source = vm;
      while (source) {if (source._provided && provideKey in source._provided) {result[key] = source._provided[provideKey];
          break;
        }
        source = source.$parent;
      }
      if (!source) {if ("default" in inject[key]) {const provideDefault = inject[key].default;
          result[key] =
            typeof provideDefault === "function"
              ? provideDefault.call(vm)
              : provideDefault;
        } else if (process.env.NODE_ENV !== "production") {// warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    return result;
  }
}

初始化状态

props、methods、data、computed、watch 都是状态。

export function initState(vm) {vm._watchers = [];
  const opts = vm.$options;
  if (opts.props) initProps(vm, opts.props);
  if (opts.methods) initMethods(vm, opts.methods);
  if (opts.data) {initData(vm);
  } else {observable((vm._data = {}), true /* asRootData */);
  }
  if (opts.computed) initComputed(vm, opts.computed);
  if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch);
  }
}

初始化 props

props 是父组件提供数据,子组件通过 props 字段抉择本人须要哪些内容,vue.js 外部通过子组件的 props 选项将须要的数据筛选进去之后增加到子组件的上下文中。

1. 子组件被实例化时,会先对 props 进行规格化解决,规格化之后的 props 为对象的格局。

function normalizeProps(options, vm) {
  const props = options.props;
  if (!props) return;
  const res = {};
  let i, val, name;
  if (Array.isArray(props)) {
    i = props.length;
    while (i--) {val = props[i];
      if (typeof val === "string") {name = camelize(val);
        res[name] = {type: null};
      } else if (process.env.NODE_ENV !== "production") {console.warn("props must be strings when using array syntax");
      }
    }
  } else if (isPlainObject(props)) {for (const key in props) {val = props[key];
      name = camelize(key);
      res[name] = isPlainObject(val) ? val : {type: val};
    }
  } else if (process.env.NODE_ENV !== "production") {console.warn("");
  }
  options.props = res;
}

2. 初始化 props
通过规格化之后的 props 从父组件传入的 props 数据中或从应用 new 创立实例时传入的 propsData 参数中,筛选出须要的数据保留在 vm._props 中,而后在 vm 上设置一个代理,实现通过 vm.x 拜访 vm._props.x 的目标。

function initProps(vm, propsOptions) {const propsData = vm.$options.propsData || {};
  const props = (vm._props = {});
  // 缓存 props 的 key
  const keys = (vm.$options._propKeys = []);
  const isRoot = !vm.$parent;
  // root 实例的 props 属性应该被转换成响应式数据
  if (!isRoot) {toggleObserving(false);
  }
  for (const key in propsOptions) {keys.push(key);
    const value = validateProp(key, propsOptions, propsData, vm);
    defineReactive(props, key, value);
    if (!(key in vm)) {proxy(vm, `_props`, key);
    }
  }
  toggleObserving(true);
}

validateProp 能够失去 prop key 对应的 value。

function validateProp(key, propsOptions, propsData, vm) {const prop = propOptions[key];
  const absent = !hasOwn(propsData, key);
  let value = propsData[key];
  // 解决布尔类型的 props
  if (isType(Boolean, prop.type)) {if (absent && !hasOwn(prop, "default")) {value = false;} else if (!isType(String, prop.type) &&
      // hyphenate 驼峰转换
      (value === "" || value === hyphenate(key))
    ) {value = true;}
  }
  // 查看默认值
  if (value === undefined) {value = getPropDefaultValue(vm, prop, key);
    // 因为默认值是新的数据,所以须要将它转换成响应式的
    const prevShouldConvert = observerState.shouldConvert;
    observerState.shouldConvert = true;
    observe(value);
    observerState.shouldConvert = prevShouldConvert;
  }
  if (process.env.NODE_ENV !== "production") {
    // 断言判断 prop 是否无效
    assertProp(prop, key, value, vm, absent);
  }
  return value;
}

assertProp 的作用是当 prop 验证失败的时候,在非生产环境下,Vue.js 将会产生一个控制台正告。

function assertProp(prop, name, value, vm, absent) {if (prop.required && absent) {console.warn('Missing required prop:"' + name + '"', vm);
    return;
  }
  if (value === null && !prop.required) {return;}
  let type = prop.type;
  let valid = !type || type === true;
  const expectedTypes = [];
  if (type) {if (!Array.isArray(type)) {type = [type];
    }
    for (let i = 0; i < type.length && !valid; i++) {const assertedType = assertType(value, type[i]);
      expectedTypes.push(assertedType.expectedTypes || "");
      valid = assertedType.valid;
    }
  }
  if (!valid) {console.warn("");
    return;
  }
  const validator = prop.validator;
  if (validator) {if (!validator(value)) {console.warn("");
    }
  }
}

初始化 methods

循环选项中的 methods 对象,并将每个属性顺次挂载到 vm 上即可。

function initMethods(vm, methods) {
  const props = vm.$options.props;
  for (const key in methods) {if (process.env.NODE_ENV !== "production") {if (methods[key] == null) {console.warn("");
      }
      if (props && hasOwn(props, key)) {console.warn("");
      }
      // isReserved 判断字符串是否以 $ 或_结尾
      if (key in vm && isReserved(key)) {console.warn("");
      }
    }
    vm[key] = methods[key] == null ? noop : bind(methods[key], vm);
  }
}

初始化 data

data 中的数据最终会被保留到 vm._data 中,而后在 vm 上设置一个代理,使得通过 vm.x 能够拜访到 vm._data 中的属性。最初调用 observe 函数将 data 转换成响应式数据。

function initData(vm) {
  let data = vm.$options.data;
  data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
  if (!isPlainObject(data)) {data = {};
    process.env.NODE_ENV !== "production" && console.warn("");
  }

  const keys = object.keys(data);
  const props = vm.$options.props;
  const methods = vm.$options.methods;
  let i = keys.length;
  while (i--) {const key = keys[i];
    if (process.env.NODE_ENV !== "production") {if (methods && hasOwn(methods, key)) {console.warn("");
      }
    }
    if (props && hasOwn(props, key)) {process.env.NODE_ENV !== "production" && console.warn("");
    } else if (!isReserved(key)) {proxy(vm, `_data`, key);
    }
  }
  // 察看数据
  observe(data, true /* asRootData */);
}

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function proxy(target, sourceKey, key) {sharedPropertyDefinition.get = function proxyGetter() {return this[sourceKey][key];
  };
  sharedPropertyDefinition.set = function proxySetter() {this[sourceKey][key] = val;
  };
  Object.defineProperties(target, key, sharedPropertyDefinition);
}

初始化 computed

computed 是定义在 vm 上的一个非凡的 getter 办法,get 并不是用户提供的函数,而是 vue.js 外部的一个代理函数。在代理函数中能够联合 Watcher 实现缓存与收集依赖等性能。

当 dirty 属性为 true 时,阐明须要从新计算”计算属性“的返回值,当 dirty 属性为 false 时,阐明计算属性的值并没有变,不须要从新计算。

当计算属性中的内容发生变化后,计算属性的 Watcher 与组件的 Watcher 都会失去告诉。计算属性的 Watcher 会将本人的 dirty 属性设置为 true, 当下一次读取计算属性时,就会从新计算一次值。而后组件的 Watcher 也会收到告诉,从而执行 render 函数进行从新渲染的操作。因为要从新执行 render 函数,所以会从新计算读取计算属性的值,这时候计算属性的 Watcher 曾经把本人的 dirty 属性设置为 true,所以会从新计算一次计算属性的值,用于本次渲染。

v2.5.17 批改为判断最终计算属性的返回值是否变动。

const computedWatcherOptions = {lazy: true};

function initComputed(vm, computed) {const watchers = (vm._computedWatchers = Object.create(null));
  // 计算属性在 SSR 环境中,只是一个一般的 getter 办法
  const isSSR = isServerRendering();

  for (const key in computed) {const userDef = computed[key];
    const getter = typeof userDef === "function" ? userDef : userDef.get;
    if (process.env.NODE_ENV !== "production" && getter === null) {console.warn("");
    }

    // 在非 SSR 环境中,为计算属性创立外部观察器
    if (!isSSR) {watchers[key] = new watchers(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    if (!(key in vm)) {defineComputed(vm, key, userDef);
    } else if (process.env.NODE_ENV !== "prodution") {if (key in vm.$data) {console.warn();
      } else if (vm.$options.props && key in vm.$options.props) {console.warn();
      }
    }
  }
}

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function defineComputed(target, key, userDef) {const shouldCache = !isServerRendering();
  if (typeof userDef === "function") {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef;
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop;

    sharedPropertyDefinition.set = userDef.set ? userDef.set : noop;
  }
  if (
    process.env.NODE_ENV !== "production" &&
    sharedPropertyDefinition.set === noop
  ) {sharedPropertyDefinition.set = function() {console.warn();
    };
  }
  Object.defineProperties(target, key, sharedPropertyDefinition);
}

// v.2.5.2
function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {if (watcher.dirty) {watcher.evaluate();
      }
      if (Dep.target) {watcher.depend();
      }
      return watcher.value;
    }
  };
}

// v2.5.17
function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {watcher.depend();
      return watcher.evaluate();}
  };
}

// Watcher 专门定义了 depend 和 evaluate 办法用于实现计算属性相干的性能

// v.2.5.2
export default class Watcher {constructor(vm, expOrFn, cb, options) {
    // 暗藏无关代码
    if (options) {this.lazy = !!options.lazy;} else {this.lazy = false;}

    this.dirty = this.lazy;

    this.value = this.lazy ? undefined : this.get();}

  evaluate() {this.value = this.get();
    this.dirty = false;
  }

  depend() {
    let i = this.deps.length;
    while (i--) {this.deps[i].depend();}
  }
}

// v2.5.17
class Watcher {constructor(vm, expOrFn, cb, options) {
    // 暗藏无关代码
    if (options) {this.computed = !!options.computed;} else {this.computed = false;}

    this.dirty = this.computed;

    if (this.computed) {
      this.value = undefined;
      this.dep = new Dep();} else {this.value = this.get();
    }
  }

  update() {if (this.computed) {if (this.dep.subs.length === 0) {this.dirty = true;} else {this.getAndInvoke(() => {this.dep.notify();
        });
      }
    }
    // 暗藏无关代码
  }

  getAndInvoke(cb) {const value = this.get();
    if (value !== this.value || isObject(value) || this.deep) {
      const oldValue = this.value;
      this.value = value;
      this.dirty = false;
      if (this.user) {
        try {cb.call(this.vm, value, oldValue);
        } catch (e) {console.error("");
        }
      } else {cb.call(this.vm, value, oldValue);
      }
    }
  }

  evaluate() {if (this.dirty) {this.value = this.get();
      this.dirty = false;
    }
    return this.value;
  }

  depend() {if (this.dep && Dep.target) {this.dep.depend();
    }
  }
}

初始化 watch

只须要循环 watch 选项,将对象中的每一项顺次调用 vm.$watch 办法来察看表达式即可。

function initWatch(vm, watch) {for (const key in watch) {const handler = watch[key];
    if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i]);
      }
    } else {createWatcher(vm, key, handler);
    }
  }
}

function createWatcher(vm, expOrFn, handler, options) {if (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === "string") {handler = vm[handler];
  }
  return vm.$watch(expOrFn, handler, options);
}

初始化 provide

赋值给 vm._provided 即可

function initProvide(vm) {
  const provide = vm.$options.provide;
  if (provide) {vm._provided = typeof provide === "function" ? provide.call(vm) : provide;
  }
}

残缺代码 (https://github.com/mfaying/si…

参考

《深入浅出 Vue.js》

退出移动版