首页 > 代码库 > Vue2.0源码阅读笔记--生命周期

Vue2.0源码阅读笔记--生命周期

一、Vue2.0的生命周期

Vue2.0的整个生命周期有八个:分别是 1.beforeCreate,2.created,3.beforeMount,4.mounted,5.beforeUpdate,6.updated,7.beforeDestroy,8.destroyed。

用官方的一张图就可以清晰的了解整个生命周期:

技术分享

Vue最新源码下载:地址

二:源码分析

1.先看new Vue实例的方法

创建Vue实例的文件是: src/core/instance/index.js

function Vue (options) {  if (process.env.NODE_ENV !== ‘production‘ &&    !(this instanceof Vue)) {    warn(‘Vue is a constructor and should be called with the `new` keyword‘)  }  this._init(options)}

Vue的构造函数调用了this._init()方法,this._init()方法存在Vue的原型链中。在src/core/instance/init.js文件中:

export function initMixin (Vue: Class<Component>) {  Vue.prototype._init = function (options?: Object) {    const vm: Component = this    // a uid    vm._uid = uid++    // a flag to avoid this being observed    vm._isVue = true    // merge options 第一步: options参数的处理    if (options && options._isComponent) {      // optimize internal component instantiation      // since dynamic options merging is pretty slow, and none of the      // internal component options needs special treatment.      initInternalComponent(vm, options)    } else {      vm.$options = mergeOptions(        resolveConstructorOptions(vm.constructor),        options || {},        vm      )    }    /* istanbul ignore else 第二步:renderProxy */    if (process.env.NODE_ENV !== ‘production‘) {      initProxy(vm)    } else {      vm._renderProxy = vm    }    // expose real self    vm._self = vm    // 第三步:vm的生命周期相关变量初始化    initLifecycle(vm)    // 第四步:vm的事件监听初始化    initEvents(vm)    // 第五步: render    initRender(vm)    callHook(vm, ‘beforeCreate‘)    // 第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化    initState(vm)    callHook(vm, ‘created‘)    if (vm.$options.el) {      vm.$mount(vm.$options.el)    }  }}

接下来继续分析每一步的详细实现。

第一步: options参数的处理

 // merge options 第一步: options参数的处理    if (options && options._isComponent) {      // optimize internal component instantiation 优化内部组件实例      // since dynamic options merging is pretty slow, and none of the 因为动态options融合比较慢,而内部组件options不需要特别处理      // internal component options needs special treatment.      initInternalComponent(vm, options)    } else {      vm.$options = mergeOptions(        resolveConstructorOptions(vm.constructor),        options || {},        vm      )    }

initInternalComponent的方法为:

function initInternalComponent (vm: Component, options: InternalComponentOptions) {  const opts = vm.$options = Object.create(vm.constructor.options)  // doing this because it‘s faster than dynamic enumeration. 做这些是因为它比动态计数要快  opts.parent = options.parent  opts.propsData = options.propsData  opts._parentVnode = options._parentVnode  opts._parentListeners = options._parentListeners  opts._renderChildren = options._renderChildren  opts._componentTag = options._componentTag  opts._parentElm = options._parentElm  opts._refElm = options._refElm  if (options.render) {    opts.render = options.render    opts.staticRenderFns = options.staticRenderFns  }}

Vue是一套组件化系统,子组件的options必然受到父组件的影响、即使是同一个组件,我们也有公用的options(挂载在构造器上)和差异的options(实例传入的options),因此处理options时我们要处理四个相关的options:

  • 父组件构造器上的options
  • 父组件实例上的options
  • 当前组件构造器上的options
  • 当前组件实例化传入的options
vm.$options = mergeOptions(        resolveConstructorOptions(vm.constructor),        options || {},        vm      )

resolveConstructorOptions的方法为:

export function resolveConstructorOptions (Ctor: Class<Component>) {  let options = Ctor.options  if (Ctor.super) { // 如果有父级    const superOptions = Ctor.super.options // 获取父级的options    const cachedSuperOptions = Ctor.superOptions // 获取父级缓存的options    const extendOptions = Ctor.extendOptions // 获取自身的options    if (superOptions !== cachedSuperOptions) { // 如果父级options有变化      // super option changed      Ctor.superOptions = superOptions // 更新缓存      extendOptions.render = options.render      extendOptions.staticRenderFns = options.staticRenderFns      extendOptions._scopeId = options._scopeId      options = Ctor.options = mergeOptions(superOptions, extendOptions)      if (options.name) {        options.components[options.name] = Ctor      }    }  }  return options}

接下来就重点看mergeOptions(文件位置在src\core\util\options.js)的实现了:

/** * Merge two option objects into a new one. 将两个参数融合成一个 * Core utility used in both instantiation and inheritance. 核心公用的会被用于实例和继承中 */export function mergeOptions (  parent: Object,  child: Object,  vm?: Component): Object {  if (process.env.NODE_ENV !== ‘production‘) {    checkComponents(child)  }  // 统一props格式  normalizeProps(child)  // 统一directives的格式  normalizeDirectives(child)  const extendsFrom = child.extends  // 如果存在child.extends  if (extendsFrom) {    parent = typeof extendsFrom === ‘function‘      ? mergeOptions(parent, extendsFrom.options, vm)      : mergeOptions(parent, extendsFrom, vm) // 递归调用该方法  }  if (child.mixins) {    //如果存在child.mixins    for (let i = 0, l = child.mixins.length; i < l; i++) {      let mixin = child.mixins[i]      if (mixin.prototype instanceof Vue) {        mixin = mixin.options      }      parent = mergeOptions(parent, mixin, vm)    }  }  //针对不同的键值,采用不同的merge策略  const options = {}  let key  for (key in parent) {    mergeField(key)  }  for (key in child) {    if (!hasOwn(parent, key)) {      mergeField(key)    }  }  function mergeField (key) {    const strat = strats[key] || defaultStrat    options[key] = strat(parent[key], child[key], vm, key)  }  return options}

上面采取了对不同的field采取不同的策略,Vue提供了一个strats对象,其本身就是一个hook,如果strats有提供特殊的逻辑,就走strats,否则走默认merge逻辑。

/** * Option overwriting strategies are functions that handle * how to merge a parent option value and a child option * value into the final value. */const strats = config.optionMergeStrategies

 第二步:renderProxy 

主要是定义了vm._renderProxy,这是后期为render做准备的

/* istanbul ignore else 第二步:renderProxy */    if (process.env.NODE_ENV !== ‘production‘) {      initProxy(vm)    } else {      vm._renderProxy = vm    }

作用是在render中将this指向vm._renderProxy。一般而言,vm._renderProxy是等于vm的,但在开发环境,Vue动用了Proxy这个新API

看下initProxy(存放于src\core\instance\proxy.js) 这个方法

 initProxy = function initProxy (vm) {    if (hasProxy) {      // determine which proxy handler to use 确定用哪个proxy handler      const options = vm.$options      const handlers = options.render && options.render._withStripped        ? getHandler        : hasHandler // getHandler和hasHandler在上面有定义      vm._renderProxy = new Proxy(vm, handlers)    } else {      vm._renderProxy = vm    }  }
Proxy 学习资源

ES6规范定义了一个全新的全局构造函数:代理(Proxy)。它可以接受两个参数:目标对象(vm)和句柄对象(handlers)。

一个简单示例:

var target = {}, handler = {};var proxy = new Proxy(target, handler);

1.代理和目标对象之间的关系:

代理的行为很简单:将代理的所有内部方法转发至目标。简单来说,如果调用proxy.[[Enumerate]](),就会返回target.[[Enumerate]]()

现在,让我们尝试执行一条能够触发调用proxy.[[Set]]()方法的语句。

技术分享

此时target的结果看看

技术分享

2.代理和句柄对象的关系:

句柄对象的方法可以覆写任意代理的内部方法。举个例子,定义一个handler.set()方法来拦截所有给对象属性赋值的行为:

 var target = {};    var handler = {      set: function (target, key, value, receiver) {        throw new Error("请不要为这个对象设置属性。");      }    };    var proxy = new Proxy(target, handler);

结果:

技术分享

第三步:vm的生命周期相关变量初始化

// 第三步:vm的生命周期相关变量初始化    initLifecycle(vm)

initLifecycle该方法存在于src\core\instance\lifecycle.js文件中

export function initLifecycle (vm: Component) {  const options = vm.$options  // locate first non-abstract parent  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._inactive = false  vm._isMounted = false  vm._isDestroyed = false  vm._isBeingDestroyed = false}

第四步:vm的事件监听初始化

// 第四步:vm的事件监听初始化    initEvents(vm)

initEvents方法存在于src\core\instance\event.js中

export function initEvents (vm: Component) {  vm._events = Object.create(null)  vm._hasHookEvent = false  // init parent attached events  const listeners = vm.$options._parentListeners  if (listeners) {    updateComponentListeners(vm, listeners)  }}
export function updateComponentListeners (  vm: Component,  listeners: Object,  oldListeners: ?Object) {  target = vm  updateListeners(listeners, oldListeners || {}, add, remove, vm)}

updateListeners方法存在于src\core\vdom\helper\update-listeners.js

export function updateListeners (  on: Object,  oldOn: Object,  add: Function,  remove: Function,  vm: Component) {  let name, cur, old, event  for (name in on) {    cur = on[name]    old = oldOn[name]    event = normalizeEvent(name)    if (!cur) {      process.env.NODE_ENV !== ‘production‘ && warn(        `Invalid handler for event "${event.name}": got ` + String(cur),        vm      )    } else if (!old) {      // 新添加的listener      if (!cur.invoker) {        cur = on[name] = createEventHandle(cur)      }      add(event.name, cur.invoker, event.once, event.capture)    } else if (cur !== old) {      // 替换旧的事件监听      old.fn = cur      on[name] = old    }  }  // 删除无用的listeners  for (name in oldOn) {    if (!on[name]) {      event = normalizeEvent(name)      remove(event.name, oldOn[name].invoker, event.capture)    }  }}

第五步: render 

// 第五步: render     initRender(vm)

initRender存放于src\core\instance\render.js

export function initRender (vm: Component) {  vm.$vnode = null // the placeholder node in parent tree 在父级树上的提示节点?  vm._vnode = null // the root of the child tree 子级的根节点  vm._staticTrees = null  const parentVnode = vm.$options._parentVnode  const renderContext = parentVnode && parentVnode.context  vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)  vm.$scopedSlots = emptyObject  // bind the createElement fn to this instance 绑定创建元素到这个实例  // so that we get proper render context inside it. 以方便我们能获得正确的渲染内容  // args order: tag, data, children, normalizationType, alwaysNormalize  参数提供: tag,data, children,normalizationType,alwaysNormalize  // internal version is used by render functions compiled from templates 内部版本用来编译从templates来的函数?  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // 箭头函数,相当于function(a,b,c,d) { return createElement(vm, a, b, c, d, false) }  // normalization is always applied for the public version, used in  // user-written render functions.  统一化?  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)}

createElement方法存放于src\core\vdom\create-element.js

// wrapper function for providing a more flexible interface 封装方法用来提供一个可扩展性的接口// without getting yelled at by flow 不是流程式export function createElement (  context: Component,  tag: any,  data: any,  children: any,  normalizationType: any,  alwaysNormalize: boolean): VNode {  if (Array.isArray(data) || isPrimitive(data)) {    normalizationType = children    children = data    data = undefined  }  if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE  return _createElement(context, tag, data, children, normalizationType)}

_createElement方法也在这个文件中

技术分享
export function _createElement (  context: Component,  tag?: string | Class<Component> | Function | Object,  data?: VNodeData,  children?: any,  normalizationType?: number): VNode {  if (data && data.__ob__) {    process.env.NODE_ENV !== ‘production‘ && warn(      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +      ‘Always create fresh vnode data objects in each render!‘,      context    )    return createEmptyVNode()  }  if (!tag) {    // in case of component :is set to falsy value    return createEmptyVNode()  }  // support single function children as default scoped slot  if (Array.isArray(children) &&      typeof children[0] === ‘function‘) {    data = data || {}    data.scopedSlots = { default: children[0] }    children.length = 0  }  if (normalizationType === ALWAYS_NORMALIZE) {    children = normalizeChildren(children)  } else if (normalizationType === SIMPLE_NORMALIZE) {    children = simpleNormalizeChildren(children)  }  let vnode, ns  if (typeof tag === ‘string‘) {    let Ctor    ns = config.getTagNamespace(tag)    if (config.isReservedTag(tag)) {      // platform built-in elements      vnode = new VNode(        config.parsePlatformTagName(tag), data, children,        undefined, undefined, context      )    } else if ((Ctor = resolveAsset(context.$options, ‘components‘, tag))) {      // component      vnode = createComponent(Ctor, data, context, children, tag)    } else {      // unknown or unlisted namespaced elements      // check at runtime because it may get assigned a namespace when its      // parent normalizes children      vnode = new VNode(        tag, data, children,        undefined, undefined, context      )    }  } else {    // direct component options / constructor    vnode = createComponent(tag, data, context, children)  }  if (vnode) {    if (ns) applyNS(vnode, ns)    return vnode  } else {    return createEmptyVNode()  }}
View Code

文件中引用了VNode这个类和createEmptyVNode方法,这两个东西存放于src\core\vdom\vnode.js

技术分享
export default class VNode {  tag: string | void;  data: VNodeData | void;  children: ?Array<VNode>;  text: string | void;  elm: Node | void;  ns: string | void;  context: Component | void; // rendered in this component‘s scope  functionalContext: Component | void; // only for functional component root nodes  key: string | number | void;  componentOptions: VNodeComponentOptions | void;  componentInstance: Component | void; // component instance  parent: VNode | void; // component placeholder node  raw: boolean; // contains raw HTML? (server only)  isStatic: boolean; // hoisted static node  isRootInsert: boolean; // necessary for enter transition check  isComment: boolean; // empty comment placeholder?  isCloned: boolean; // is a cloned node?  isOnce: boolean; // is a v-once node?  constructor (    tag?: string,    data?: VNodeData,    children?: ?Array<VNode>,    text?: string,    elm?: Node,    context?: Component,    componentOptions?: VNodeComponentOptions  ) {    this.tag = tag    this.data =http://www.mamicode.com/ data    this.children = children    this.text = text    this.elm = elm    this.ns = undefined    this.context = context    this.functionalContext = undefined    this.key = data && data.key    this.componentOptions = componentOptions    this.componentInstance = undefined    this.parent = undefined    this.raw = false    this.isStatic = false    this.isRootInsert = true    this.isComment = false    this.isCloned = false    this.isOnce = false  }  // DEPRECATED: alias for componentInstance for backwards compat.  /* istanbul ignore next */  get child (): Component | void {    return this.componentInstance  }}
View Code

createEmptyVNode方法

export const createEmptyVNode = () => {  const node = new VNode()  node.text = ‘‘  node.isComment = true  return node}

第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化 

 // 第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化    initState(vm)

initState存放于src\core\instance\state.js

export function initState (vm: Component) {  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 {    observe(vm._data = {}, true /* asRootData */)  }  if (opts.computed) initComputed(vm, opts.computed)  if (opts.watch) initWatch(vm, opts.watch)}

vm的状态初始化是整个初始化中最复杂的异步,其data、props、methods、computed、watch都在这一步进行初始化,因此这一步也是Vue真正的创建。

initProps
技术分享
function initProps (vm: Component, propsOptions: Object) {  const propsData = vm.$options.propsData || {}  const props = vm._props = {}  // cache prop keys so that future props updates can iterate using Array  // instead of dynamic object key enumeration.  const keys = vm.$options._propKeys = []  const isRoot = !vm.$parent  // root instance props should be converted  observerState.shouldConvert = isRoot  for (const key in propsOptions) {    keys.push(key)    const value = validateProp(key, propsOptions, propsData, vm)    /* istanbul ignore else */    if (process.env.NODE_ENV !== ‘production‘) {      if (isReservedProp[key]) {        warn(          `"${key}" is a reserved attribute and cannot be used as component prop.`,          vm        )      }      defineReactive(props, key, value, () => { // 监控prop的变化        if (vm.$parent && !observerState.isSettingProps) {          warn(            `Avoid mutating a prop directly since the value will be ` +            `overwritten whenever the parent component re-renders. ` +            `Instead, use a data or computed property based on the props ` +            `value. Prop being mutated: "${key}"`,            vm          )        }      })    } else {      defineReactive(props, key, value)    }    // static props are already proxied on the components prototype    // during Vue.extend(). We only need to proxy props defined at    // instantiation here.    if (!(key in vm)) {      proxy(vm, `_props`, key)    }  }  observerState.shouldConvert = true}
View Code
initMethods
技术分享
function initMethods (vm: Component, methods: Object) {  for (const key in methods) {    vm[key] = methods[key] == null ? noop : bind(methods[key], vm) // 作用域重新绑定    if (process.env.NODE_ENV !== ‘production‘ && methods[key] == null) {      warn(        `method "${key}" has an undefined value in the component definition. ` +        `Did you reference the function correctly?`,        vm      )    }  }}
View Code
initData
技术分享
function initData (vm: Component) {  let data = vm.$options.data  data = vm._data = http://www.mamicode.com/typeof data =http://www.mamicode.com/== ‘function‘"color: #000000;"> data.call(vm)    : data || {}  if (!isPlainObject(data)) {    // 保证data必须为纯对象    data =http://www.mamicode.com/ {}    process.env.NODE_ENV !== ‘production‘ && warn(      ‘data functions should return an object:\n‘ +      ‘https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function‘,      vm    )  }  // proxy data on instance  const keys = Object.keys(data)  const props = vm.$options.props  let i = keys.length  while (i--) {    if (props && hasOwn(props, keys[i])) {      process.env.NODE_ENV !== ‘production‘ && warn(        `The data property "${keys[i]}" is already declared as a prop. ` +        `Use prop default value instead.`,        vm      )    } else if (!isReserved(keys[i])) {      proxy(vm, `_data`, keys[i]) //将属性代理到vm上    }  }  // observe data 将data转换为监控对象  observe(data, true /* asRootData */)}
View Code
initComputed
技术分享
const computedWatcherOptions = { lazy: true }function initComputed (vm: Component, computed: Object) {  const watchers = vm._computedWatchers = Object.create(null)  for (const key in computed) {    const userDef = computed[key]    const getter = typeof userDef === ‘function‘ ? userDef : userDef.get    // create internal watcher for the computed property.    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)    // component-defined computed properties are already defined on the    // component prototype. We only need to define computed properties defined    // at instantiation here.    if (!(key in vm)) {      defineComputed(vm, key, userDef)    }  }}export function defineComputed (target: any, key: string, userDef: Object | Function) {  if (typeof userDef === ‘function‘) {    sharedPropertyDefinition.get = createComputedGetter(key)    sharedPropertyDefinition.set = noop  } else {    sharedPropertyDefinition.get = userDef.get      ? userDef.cache !== false        ? createComputedGetter(key)        : userDef.get      : noop    sharedPropertyDefinition.set = userDef.set      ? userDef.set      : noop  }  Object.defineProperty(target, key, sharedPropertyDefinition)}
View Code

computed其实本身也是一种特殊的并且lazy的watcher,在get时它作为所计算的属性依赖而被收集,同时它把依赖自己的watcher也添加到属性的依赖中去,这样当原属性变化时,就会通知到依赖computed的依赖重新获取最新值。

function createComputedGetter (key) {  return function computedGetter () {    const watcher = this._computedWatchers && this._computedWatchers[key]    if (watcher) {      if (watcher.dirty) {        // 将自己添加到属性的依赖列表中去        watcher.evaluate()      }      if (Dep.target) {        // 将依赖watcher的依赖也收集到属性依赖列表中去        watcher.depend()      }      return watcher.value    }  }}
initWatch
技术分享
function initWatch (vm: Component, watch: Object) {  for (const key in watch) {    const handler = watch[key]    // 可以是数组,为key创建多个watcher    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: Component, key: string, handler: any) {  let options  if (isPlainObject(handler)) {    options = handler    handler = handler.handler  }  // 如果handle传入为字符串,则直接找vm上的方法,一般是methods中定义的方法,这也是methods的初始化要先于watch初始化的原因  if (typeof handler === ‘string‘) {    handler = vm[handler]  }  vm.$watch(key, handler, options) // 没找到$watch在原型上的定义}
View Code

经过这些初始化的步骤,一个组件就被创造出来了,紧接着就可以callHook(vm, ‘created‘)。

至此,我们主要了解了Vue整个生命周期,以及Vue创建(created)前的过程,从生命周期图中可以看到还有重要一步 Observe Data没有分析,状态初始化过程中用到监控对象如observe(data),依赖收集Dep等等,分析完Observe Data 应该就可以了解到Vue的数据绑定原理,这个分析留给下一篇文章。

参考资料:http://blog.cgsdream.org/2016/11/11/vue-source-analysis-2/

Vue2.0源码阅读笔记--生命周期