vue的生命周期
代码
运行结果
源码分析
1 function Vue (options) {2 this._init(options)3 }
1 Vue.prototype._init = function (options?: Object) { 2 const vm: Component = this 3 4 //监听对象变化时用于过滤vm 5 vm._isVue = true 6 // 合并对象 7 vm.$options = mergeOptions( 8 resolveConstructorOptions(vm.constructor), 9 options || {},10 vm11 )12 // expose real self13 vm._self = vm14 15 initLifecycle(vm)16 //给vm添加了一些虚拟dom、slot等相关的属性和方法。17 initRender(vm)18 //调用beforeCreate钩子函数。19 callHook(vm, 'beforeCreate')20 //初始化数据 props,methods,data,computed,watch21 initState(vm)22 //调用created钩子函数。23 callHook(vm, 'created')24 25 if (vm.$options.el) {26 vm.$mount(vm.$options.el)27 }28 }
beforeCreate阶段和create阶段
create阶段,基本就是对传入数据的格式化、数据的双向绑定、以及一些属性的初始化。
1 export function resolveConstructorOptions (Ctor: Class) {2 let options = Ctor.options3 return options4 }
合并策略存储在optionMergeStrategies对象中,strats[key]就是key属性的合并方法。
1 /**2 * Option overwriting strategies are functions that handle3 * how to merge a parent option value and a child option4 * value into the final value.5 */6 const strats = config.optionMergeStrategies
合并属性
1 /** 2 * Merge two option objects into a new one. 3 */ 4 function mergeOptions ( 5 parent, 6 child, 7 vm 8 ) { 9 var options = {};10 var key;11 for (key in parent) {12 mergeField(key);13 }14 for (key in child) {15 if (!hasOwn(parent, key)) {16 mergeField(key);17 }18 }19 function mergeField (key) {20 var strat = strats[key] || defaultStrat;21 options[key] = strat(parent[key], child[key], vm, key);22 }23 return options24 }
1 function mergeAssets ( 2 parentVal: ?Object, 3 childVal: ?Object, 4 vm?: Component, 5 key: string 6 ): Object { 7 const res = Object.create(parentVal || null) 8 return res 9 }10 //ASSET_TYPES=['components','directives','filters'];11 ASSET_TYPES.forEach(function (type) {12 strats[type + 's'] = mergeAssets13 })
data属性合并策略。
1 strats.data = function (2 parentVal: any,3 childVal: any,4 vm?: Component5 ): ?Function {6 return mergeDataOrFn(parentVal, childVal, vm)7 }
1 /** 2 * Data 3 */ 4 export function mergeDataOrFn ( 5 parentVal: any, 6 childVal: any, 7 vm?: Component 8 ): ?Function { 9 return function mergedInstanceDataFn () {10 // instance merge11 const instanceData = typeof childVal === 'function'12 ? childVal.call(vm)13 : childVal14 const defaultData = typeof parentVal === 'function'15 ? parentVal.call(vm)16 : parentVal17 if (instanceData) {18 return mergeData(instanceData, defaultData)19 } else {20 return defaultData21 }22 }23 }
1 /** 2 * Helper that recursively merges two data objects together. 3 */ 4 function mergeData (to: Object, from: ?Object): Object { 5 if (!from) return to 6 let key, toVal, fromVal 7 const keys = Object.keys(from) 8 for (let i = 0; i < keys.length; i++) { 9 key = keys[i]10 toVal = to[key]11 fromVal = from[key]12 if (!hasOwn(to, key)) {13 set(to, key, fromVal)14 } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {15 mergeData(toVal, fromVal)16 }17 }18 return to19 }
mergeOption后vm.$options对象如下所示:
给vm对象添加了$parent、$root、$children属性,以及它的生命周期相关的标识。
1 //主要给vm对象添加了$parent、$root、$children属性,以及一些其它的生命周期相关的标识。 2 export function initLifecycle (vm: Component) { 3 const options = vm.$options 4 5 // locate first non-abstract parent 6 let parent = options.parent 7 8 vm.$parent = parent 9 vm.$root = parent ? parent.$root : vm10 11 vm.$children = []12 vm.$refs = {}13 14 vm._watcher = null15 vm._inactive = null16 vm._directInactive = false17 vm._isMounted = false18 vm._isDestroyed = false19 vm._isBeingDestroyed = false20 }
给vm添加了一些虚拟dom、slot等相关的属性和方法。
1 export function initRender (vm: Component) { 2 vm._vnode = null // the root of the child tree 3 const options = vm.$options 4 const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree 5 const renderContext = parentVnode && parentVnode.context 6 vm.$slots = resolveSlots(options._renderChildren, renderContext) 7 vm.$scopedSlots = emptyObject 8 // bind the createElement fn to this instance 9 // so that we get proper render context inside it.10 // args order: tag, data, children, normalizationType, alwaysNormalize11 // internal version is used by render functions compiled from templates12 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)13 // normalization is always applied for the public version, used in14 // user-written render functions.15 vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)16 }
主要是操作数据,props、methods、data、props、computed、watch。
1 export function initState (vm: Component) { 2 vm._watchers = [] 3 const opts = vm.$options 4 if (opts.props) initProps(vm, opts.props) 5 if (opts.methods) initMethods(vm, opts.methods) 6 if (opts.data) { 7 initData(vm) 8 } else { 9 observe(vm._data = {}, true /* asRootData */)10 }11 if (opts.computed) initComputed(vm, opts.computed)12 if (opts.watch && opts.watch !== nativeWatch) {13 initWatch(vm, opts.watch)14 }15 }
beforemounted阶段和mounted阶段
1 const mount = Vue.prototype.$mount 2 /* 3 * 判断是否有render函数,如果有直接处理。如果没有render函数,则生成render。 4 * 5 * */ 6 Vue.prototype.$mount = function ( 7 el?: string | Element, 8 hydrating?: boolean 9 ): Component {10 el = el && query(el)11 12 const options = this.$options13 // resolve template/el and convert to render function14 if (!options.render) {15 let template = options.template16 if (template) {17 if (typeof template === 'string') {18 if (template.charAt(0) === '#') {19 template = idToTemplate(template)20 /* istanbul ignore if */21 if (process.env.NODE_ENV !== 'production' && !template) {22 warn(23 `Template element not found or is empty: ${options.template}`,24 this25 )26 }27 }28 } else if (template.nodeType) {29 template = template.innerHTML30 } else {31 if (process.env.NODE_ENV !== 'production') {32 warn('invalid template option:' + template, this)33 }34 return this35 }36 } else if (el) {37 template = getOuterHTML(el)38 }39 if (template) {40 41 const { render, staticRenderFns } = compileToFunctions(template, {42 shouldDecodeNewlines,43 delimiters: options.delimiters,44 comments: options.comments45 }, this)46 options.render = render47 options.staticRenderFns = staticRenderFns48 }49 }50 return mount.call(this, el, hydrating)51 }
1. query(el)(类似为document.querySeector)判断el是不是字符串,不是字符串直接返回,是字符串转为dom。
2.判断是否有render函数,如果有,不做其他处理直接执行mount.call(this,el,hydrating)。如果没有,则获取template,template可以是#id、模板字符串、dom元素。如果没有template,则获取el及其子内容作为template。complieToFunctions是对最后生成的模板的解析,生成render。
1 /** 2 * Get outerHTML of elements, taking care 3 * of SVG elements in IE as well. 4 */ 5 function getOuterHTML (el: Element): string { 6 if (el.outerHTML) { 7 return el.outerHTML 8 } else { 9 const container = document.createElement('div')10 container.appendChild(el.cloneNode(true))11 return container.innerHTML12 }13 }
compileToFunctions中调用了compile,compile中调用了baseCompile。主要操作就是baseCompile中的三步。
1 function baseCompile ( 2 template: string, 3 options: CompilerOptions 4 ): CompiledResult { 5 const ast = parse(template.trim(), options) 6 optimize(ast, options) 7 const code = generate(ast, options) 8 return { 9 ast,10 render: code.render,11 staticRenderFns: code.staticRenderFns12 }13 }14 15 16 export function createCompiler (baseOptions: CompilerOptions) {17 const functionCompileCache: {18 [key: string]: CompiledFunctionResult;19 } = Object.create(null)20 21 function compile (22 template: string,23 options?: CompilerOptions24 ): CompiledResult {25 ...26 const compiled = baseCompile(template, finalOptions)27 ...28 return compiled29 }30 31 function compileToFunctions (32 template: string,33 options?: CompilerOptions,34 vm?: Component35 ): CompiledFunctionResult {36 options = options || {}37 ...38 // compile39 const compiled = compile(template, options)40 ...41 return (functionCompileCache[key] = res)42 }43 44 return {45 compile,46 compileToFunctions47 }48 }
第一步: const ast = parse(template.trim(), options),解析template生成ast(抽象语法树)。例子中生成的ast如下:
{ type: 1, tag: 'div', plain: false, parent: undefined, attrs: [{name:'id', value: '"app"'}], attrsList: [{name:'id', value: 'app'}], attrsMap: {id: 'app'}, children: [{ type: 1, tag: 'p', plain: true, parent: ast, attrs: [], attrsList: [], attrsMap: {}, children: [{ expression: "_s(message)", text: "{ {message}}", type: 2 }]}
第二步:optimize(ast, options)主要对ast进行优化,分析出静态不变的内容部分,增加了部分属性。
{ type: 1, tag: 'div', plain: false, parent: undefined, attrs: [{name:'id', value: '"app"'}], attrsList: [{name:'id', value: 'app'}], attrsMap: {id: 'app'}, static: false, staticRoot: false, children: [{ type: 1, tag: 'p', plain: true, parent: ast, attrs: [], attrsList: [], attrsMap: {}, static: false, staticRoot: false, children: [{ expression: "_s(message)", text: "{ {message}}", type: 2, static: false }] }
因为这里只有一个动态的{
{message}},所以static和staticRoot都是false。最后一步:code=generate(ast, options),根据ast生成render函数和staticRenderFns数组。
最后生成的render如下:
render = function () { with(this){ return _c('div',{attrs:{"id":"app"}},[_c('p',[_v(_s(message))])])}}
定义的原始的$mount方法
1 // public mount method2 Vue.prototype.$mount = function (3 el?: string | Element,4 hydrating?: boolean5 ): Component {6 el = el && inBrowser ? query(el) : undefined7 return mountComponent(this, el, hydrating)8 }
1 /* 2 * (1)调用beforeMount钩子函数 3 * (2)新建一个Watcher对象,绑定在vm._watcher上 4 * (3)调用mounted钩子函数 5 * */ 6 export function mountComponent ( 7 vm: Component, 8 el: ?Element, 9 hydrating?: boolean10 ): Component {11 vm.$el = el12 //调用beforeMount钩子函数13 callHook(vm, 'beforeMount')14 15 let updateComponent16 updateComponent = () => {17 vm._update(vm._render(), hydrating)18 }19 //新建watcher对象,绑定在vm._watcher上20 vm._watcher = new Watcher(vm, updateComponent, noop)21 hydrating = false22 // manually mounted instance, call mounted on self23 // mounted is called for render-created child components in its inserted hook24 if (vm.$vnode == null) {25 vm._isMounted = true26 callHook(vm, 'mounted')27 }28 return vm29 }
vm._render()方法中,主要是调用了vm.$options.render方法,返回一个VNode对象。
总结
初始化流程
vm对象属性添加合并 =>beforeCreate=>数据操作(props,data,methods,computed等)=> created => template转为render函数=>beforeMount=>render函数转为VNode,新建watcher =>mounted
[1] https://github.com/liutao/vue2.0-source/blob/master/%E4%BB%8E%E4%B8%80%E4%B8%AA%E5%B0%8F%E6%A0%97%E5%AD%90%E6%9F%A5%E7%9C%8BVue%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.md