vue v2.5.0源码-初始化流程
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 vm 11 ) 12 // expose real self 13 vm._self = vm 14 15 initLifecycle(vm) 16 //给vm添加了一些虚拟dom、slot等相关的属性和方法。 17 initRender(vm) 18 //调用beforeCreate钩子函数。 19 callHook(vm, 'beforeCreate') 20 //初始化数据 props,methods,data,computed,watch 21 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<Component>) { 2 let options = Ctor.options 3 return options 4 }
合并策略存储在optionMergeStrategies对象中,strats[key]就是key属性的合并方法。
1 /** 2 * Option overwriting strategies are functions that handle 3 * how to merge a parent option value and a child option 4 * 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 options 24 }
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'] = mergeAssets 13 })
data属性合并策略。
1 strats.data = function ( 2 parentVal: any, 3 childVal: any, 4 vm?: Component 5 ): ?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 merge 11 const instanceData = typeof childVal === 'function' 12 ? childVal.call(vm) 13 : childVal 14 const defaultData = typeof parentVal === 'function' 15 ? parentVal.call(vm) 16 : parentVal 17 if (instanceData) { 18 return mergeData(instanceData, defaultData) 19 } else { 20 return defaultData 21 } 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 to 19 }
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 : vm 10 11 vm.$children = [] 12 vm.$refs = {} 13 14 vm._watcher = null 15 vm._inactive = null 16 vm._directInactive = false 17 vm._isMounted = false 18 vm._isDestroyed = false 19 vm._isBeingDestroyed = false 20 }
给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, alwaysNormalize 11 // internal version is used by render functions compiled from templates 12 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) 13 // normalization is always applied for the public version, used in 14 // 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.$options 13 // resolve template/el and convert to render function 14 if (!options.render) { 15 let template = options.template 16 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 this 25 ) 26 } 27 } 28 } else if (template.nodeType) { 29 template = template.innerHTML 30 } else { 31 if (process.env.NODE_ENV !== 'production') { 32 warn('invalid template option:' + template, this) 33 } 34 return this 35 } 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.comments 45 }, this) 46 options.render = render 47 options.staticRenderFns = staticRenderFns 48 } 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.innerHTML 12 } 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.staticRenderFns 12 } 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?: CompilerOptions 24 ): CompiledResult { 25 ... 26 const compiled = baseCompile(template, finalOptions) 27 ... 28 return compiled 29 } 30 31 function compileToFunctions ( 32 template: string, 33 options?: CompilerOptions, 34 vm?: Component 35 ): CompiledFunctionResult { 36 options = options || {} 37 ... 38 // compile 39 const compiled = compile(template, options) 40 ... 41 return (functionCompileCache[key] = res) 42 } 43 44 return { 45 compile, 46 compileToFunctions 47 } 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 method 2 Vue.prototype.$mount = function ( 3 el?: string | Element, 4 hydrating?: boolean 5 ): Component { 6 el = el && inBrowser ? query(el) : undefined 7 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?: boolean 10 ): Component { 11 vm.$el = el 12 //调用beforeMount钩子函数 13 callHook(vm, 'beforeMount') 14 15 let updateComponent 16 updateComponent = () => { 17 vm._update(vm._render(), hydrating) 18 } 19 //新建watcher对象,绑定在vm._watcher上 20 vm._watcher = new Watcher(vm, updateComponent, noop) 21 hydrating = false 22 // manually mounted instance, call mounted on self 23 // mounted is called for render-created child components in its inserted hook 24 if (vm.$vnode == null) { 25 vm._isMounted = true 26 callHook(vm, 'mounted') 27 } 28 return vm 29 }
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