残缺代码 (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.2function 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.17function 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.2export 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.17class 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》