Vue2.0源碼閱讀筆記--生命週期

1、Vue2.0的生命週期

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

用官方的一張圖就能夠清晰的瞭解整個生命週期:vue

Vue最新源碼下載:地址node

二:源碼分析

1.先看new Vue實例的方法

建立Vue實例的文件是: src/core/instance/index.jsgit

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文件中:es6

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)
    }
  }
}

接下來繼續分析每一步的詳細實現。github

第一步: 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的方法爲:app

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)的實現了:dom

/**
 * 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 = 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 prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's 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 = typeof data === 'function'
    ? data.call(vm)
    : data || {}
  if (!isPlainObject(data)) {
    // 保證data必須爲純對象
    data = {}
    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/

相關文章
相關標籤/搜索