接上篇,咱们看到了VUE分了不少模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),经过使用Mixin模式,都是使用了JavaScript原型继承的原理,在Vue的原型上面增长属性和方法。咱们继续跟着this._init(options)走,这个一点击进去就知道了是进入了init.js文件是在initMixin函数里面给Vue原型添加的_init方法。首先来从宏观看看这个init文件,能够看出主要是导出了两个函数:initMixin和resolveConstructorOptions,具体做用咱们一步步来讨论。咋的一看这个文件,可能有些童鞋会看不明白函数参数括号里面写的是什么鬼,这个实际上是应用了flow的类型检查,具体flow的使用这里就不介绍了,有兴趣的请移步:https://flow.org/en/html
let uid = 0 export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ 【**注:istanbul 是代码覆盖率检测工具,此注释为代码测试用**】 if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-init:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // 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 ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = 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') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
咱们本着宏观简化原则,这个函数里面前面有三个if判断工做咱们能够先不细化讨论【有兴趣可移步:http://www.cnblogs.com/QH-Jimmy/p/6862539.html】,大体第一个是用performance作性能监测,第二个合并option,第三个是作代理拦截,是ES6新特性,可参考阮一峰大神关于proxy的介绍【http://es6.ruanyifeng.com/#docs/proxy】。那么就进入了初始化函数主要点:vue
initLifecycle(vm) //生命周期变量初始化 initEvents(vm) //事件监听初始化 initRender(vm) //初始化渲染 callHook(vm, 'beforeCreate') //回调钩子beforeCreate initInjections(vm) //初始化注入 initState(vm) // prop/data/computed/method/watch状态初始化 initProvide(vm) // resolve provide after data/props callHook(vm, 'created') //回调钩子created /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) }
V2.1.8及之前的版本】这里比较方便理解在生命周期created以后再作render,那么在created以前就没法获取DOM。这也是在有些源码解析文章里面很容易见到的分析,也是正确的node
initLifecycle(vm) initEvents(vm) callHook(vm, 'beforeCreate') initState(vm) callHook(vm, 'created') initRender(vm)
v2.1.9及之后的版本】但到这里一开始就懵逼了好久render提到beforeCreate以前去了,那岂不是DOM在beforeCreate以前就能获取到了?显然不对了,请注意render虽然提早了,可是后面多了一个if这个if里面才获取DOM的关键,这个if在2.1.8版本以前是在render函数里面的,在2.1.9以后被提出来,而后render函数提早了,至于为什么提早暂未了解,此处只是踩了一个看其余源码解析不一样版本带来的坑!es6
initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initState(vm) callHook(vm, 'created') if (vm.$options.el) { vm.$mount(vm.$options.el) }
function initLifecycle (vm: Component) { const options = vm.$options // locate first non-abstract parent let parent = options.parent //我理解为父实例或者父组件 if (parent && !options.abstract) { //例子中没有parent,断点代码的时候自动跳过 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 = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false }
这个函数主要是有父实例的状况下处理vm.$parent和vm.$children这俩个实例属性,我此处没有就跳过,其余的就是新增了一些实例属性ide
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) } }
又新增两个属性,后面那个if条件里面是有父组件的事件时初始化,估计就是props和events父子组件通讯的事件内容。函数
function initRender (vm: Component) { vm._vnode = null // the root of the child tree vm._staticTrees = null const parentVnode = vm.$vnode = vm.$options._parentVnode const renderContext = parentVnode && parentVnode.context vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext) vm.$scopedSlots = emptyObject vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) const parentData = parentVnode && parentVnode.data /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, '$attrs', parentData && parentData.attrs, () => { !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) }, true) defineReactive(vm, '$listeners', vm.$options._parentListeners, () => { !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) }, true) } else { defineReactive(vm, '$attrs', parentData && parentData.attrs, null, true) defineReactive(vm, '$listeners', vm.$options._parentListeners, null, true) } }
此函数也是初始化了节点属性信息,绑定createElement函数到实例【并未挂载】,接下来调用beforeCreate回调钩子;——TODO1:后续专题分析VUE渲染逻辑工具
function initInjections (vm: Component) { const result = resolveInject(vm.$options.inject, vm) if (result) { observerState.shouldConvert = false Object.keys(result).forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], () => { warn( `Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm ) }) } else { defineReactive(vm, key, result[key]) } }) observerState.shouldConvert = true } }
此函数也是当有inject属性时作处理,源码例子无inject断点跑暂时跳过性能
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) } }
能够看出此处是对options传入的props/methods/data/computed/watch属性作初始化————TODO2:分析每一个属性的初始化测试
function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide } }
这个函数跟4.initInjections在同一个inject.js中,也是在传入参数有provide属性时作处理,暂时跳过,而后就到了created回调钩子,最后的vm.$mount接入TODO1;ui