wxuande 2019-11-04
2.5.21
Vue的生命周期
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
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
src\platforms\web\entry-runtime-with-compiler.js
vue来自于./runtime/index
import Vue from './runtime/index'
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) }
src\core\index.js
Vue来自于./instance/index
import Vue from './instance/index'
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)
我们一一分析。
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) } } }
src\core\util\options.js
mergeOptions的方法做了什么?顾名思义,将options挂到Vue对象上。看下面的图:
merge前vm.$options是空的;merge后,$options已经有值了。
执行完mergeOptions后,我们继续往下看。
src\core\instance\lifecycle.js
initLifecycle顾名思义,肯定与Vue的生命周期有关!真的是吗?同样看图:
发现,只是多了一些参数,还没有到我们熟悉的created,mounted。但是这里是为生命周期做准备,做了一下初始化工作。
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) } }
src\core\instance\render.js
initRender
方法初始化了渲染的参数和方法(此时还没有渲染)。如下图:
callHook(vm, 'beforeCreate')
终于到了第一个生命周期beforeCreate
!
接下来肯定是create了吧。
src\core\instance\inject.js
initInjections与provide/inject有关,与生命周期无关,这里暂不介绍。《Vue官方文档:provide/inject》
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等)的值注入:
src\core\instance\inject.js
initProvide与provide/inject有关,与生命周期无关,这里暂不介绍。《Vue官方文档:provide/inject》
initProvide
callHook(vm, 'created')
第二个生命周期created
vm.$mount(vm.$options.el)
回看第4步中,给Vue的原型链上挂上了$mount方法:
import { mountComponent } from 'core/instance/lifecycle' // public mount method Vue.prototype.$mount = function ( ... }
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
何时调用?updateComponent
在new Watcher
时传进去,作为getter
方法,在每次获取vm时执行,其中执行了vm._update(vm._render(), hydrating)
,用以mount渲染。
TODO: 这块代码具体会另起文章详细介绍。
本篇文章参考自
background-color: blue;background-color: yellow;<input type="button" value="变蓝" @click="changeColorT