Vue源码分析——生命周期

wxuande 2019-11-04

背景

vue版本

2.5.21

本篇内容

Vue的生命周期

源码开始

1. package.json

scrpit内,npm run dev的命令:

"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
  • 其中rollup是一个打包工具,类似webpack。

    rollup: Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。
  • 打包的代码在scripts/config.js

2. scripts/config.js

根据TARGET:web-full-dev找到如下代码:

// Runtime+compiler development build (Browser)
'web-full-dev': {
  entry: resolve('web/entry-runtime-with-compiler.js'),
  dest: resolve('dist/vue.js'),
  format: 'umd',
  env: 'development',
  alias: { he: './entity-decoder' },
  banner
}

找到入口文件在web/entry-runtime-with-compiler.js

3. src\platforms\web\entry-runtime-with-compiler.js

vue来自于./runtime/index

import Vue from './runtime/index'

4. src\platforms\web\runtime\index.js

Vue来自于core/index

import Vue from 'core/index'
...
// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

5. src\core\index.js

Vue来自于./instance/index

import Vue from './instance/index'

6. src\core\instance\index.js

一直跟到这里,Vue终于露出庐山真面目。

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后,接下来的代码:

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

我们一一分析。

6.1 initMixin src\core\instance\init.js

这里代码的作用是给Vue的原型链上定义_init方法。而这个_init方法在Vue对象创建时被调用(回看6里的代码this._init(options))。
接下来我们分析_init方法里做了什么,就明白了Vue对象创建时,到底经历了什么。
刚开始是一些参数的初始化,直到merge options。

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    ...
    // merge 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
      )
    }
    
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    
    ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
6.1.1 src\core\util\options.js

mergeOptions的方法做了什么?顾名思义,将options挂到Vue对象上。看下面的图:
Vue源码分析——生命周期
merge前vm.$options是空的;merge后,$options已经有值了。
执行完mergeOptions后,我们继续往下看。

6.1.2 initLifecycle src\core\instance\lifecycle.js

initLifecycle顾名思义,肯定与Vue的生命周期有关!真的是吗?同样看图:
Vue源码分析——生命周期
发现,只是多了一些参数,还没有到我们熟悉的created,mounted。但是这里是为生命周期做准备,做了一下初始化工作。

6.1.3 initEvents src\core\instance\events.js

这个方法的代码不多,主要是调用了updateComponentListeners方法。这个方法的作用是更新组件的侦听事件,与生命周期无关,暂不分析。

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)
  }
}
6.1.4 initRender src\core\instance\render.js

initRender方法初始化了渲染的参数和方法(此时还没有渲染)。如下图:
Vue源码分析——生命周期

6.1.5 callHook(vm, 'beforeCreate')

终于到了第一个生命周期beforeCreate
接下来肯定是create了吧。

6.1.6 initInjections src\core\instance\inject.js

initInjections与provide/inject有关,与生命周期无关,这里暂不介绍。《Vue官方文档:provide/inject》

6.1.7 initState src\core\instance\state.js

代码不多,但是一看就知道是初始化Props,Methods,data,computed,watch的。

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 && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

如图,将data(demo中没有methods等)的值注入:
Vue源码分析——生命周期

6.1.8 initProvide src\core\instance\inject.js

initProvide与provide/inject有关,与生命周期无关,这里暂不介绍。《Vue官方文档:provide/inject》
initProvide

6.1.9 callHook(vm, 'created')

第二个生命周期created

6.1.10 vm.$mount(vm.$options.el)

回看第4步中,给Vue的原型链上挂上了$mount方法:

import { mountComponent } from 'core/instance/lifecycle'
// public mount method
Vue.prototype.$mount = function (
  ...
}
6.1.10.1 mountComponent src\core\instance\lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  ...
  callHook(vm, 'beforeMount')
  ...
  // updateComponent
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  ...
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

这段代码里,有关生命周期的就很多了。一共三个:beforeMount,beforeUpdate,mounted
真正mount的方法在updateComponent

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

那么updateComponent何时调用?updateComponentnew Watcher时传进去,作为getter方法,在每次获取vm时执行,其中执行了vm._update(vm._render(), hydrating),用以mount渲染。
TODO: 这块代码具体会另起文章详细介绍。

原创说明

相关推荐

lyjava / 0评论 2020-07-30