一、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() } }