Vue2.0的整個生命週期有八個:分別是 1.beforeCreate,2.created,3.beforeMount,4.mounted,5.beforeUpdate,6.updated,7.beforeDestroy,8.destroyed。html
用官方的一張圖就能夠清晰的瞭解整個生命週期:vue
Vue最新源碼下載:地址node
建立Vue實例的文件是: src/core/instance/index.jsgit
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文件中:es6
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) } } }
接下來繼續分析每一步的詳細實現。github
// 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:緩存
vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm )
resolveConstructorOptions的方法爲:app
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)的實現了:dom
/** * 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
主要是定義了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 } }
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的生命週期相關變量初始化 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的事件監聽初始化 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 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() } }
文件中引用了VNode這個類和createEmptyVNode方法,這兩個東西存放於src\core\vdom\vnode.js
export default class VNode { tag: string | void; data: VNodeData | void; children: ?Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // rendered in this component's scope functionalContext: Component | void; // only for functional component root nodes key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? constructor ( tag?: string, data?: VNodeData, children?: ?Array<VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions ) { this.tag = tag this.data = data this.children = children this.text = text this.elm = elm this.ns = undefined this.context = context this.functionalContext = undefined this.key = data && data.key this.componentOptions = componentOptions this.componentInstance = undefined this.parent = undefined this.raw = false this.isStatic = false this.isRootInsert = true this.isComment = false this.isCloned = false this.isOnce = false } // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ get child (): Component | void { return this.componentInstance } }
createEmptyVNode方法
export const createEmptyVNode = () => { const node = new VNode() node.text = '' node.isComment = true return node }
// 第六步:vm的狀態初始化,prop/data/computed/method/watch都在這裏完成初始化 initState(vm)
initState存放於src\core\instance\state.js
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) initWatch(vm, opts.watch) }
vm的狀態初始化是整個初始化中最複雜的異步,其data、props、methods、computed、watch都在這一步進行初始化,所以這一步也是Vue真正的建立。
function initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted observerState.shouldConvert = isRoot for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { if (isReservedProp[key]) { warn( `"${key}" is a reserved attribute and cannot be used as component prop.`, vm ) } defineReactive(props, key, value, () => { // 監控prop的變化 if (vm.$parent && !observerState.isSettingProps) { warn( `Avoid mutating a prop directly since the value will be ` + `overwritten whenever the parent component re-renders. ` + `Instead, use a data or computed property based on the prop's ` + `value. Prop being mutated: "${key}"`, vm ) } }) } else { defineReactive(props, key, value) } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { proxy(vm, `_props`, key) } } observerState.shouldConvert = true }
function initMethods (vm: Component, methods: Object) { for (const key in methods) { vm[key] = methods[key] == null ? noop : bind(methods[key], vm) // 做用域從新綁定 if (process.env.NODE_ENV !== 'production' && methods[key] == null) { warn( `method "${key}" has an undefined value in the component definition. ` + `Did you reference the function correctly?`, vm ) } } }
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? data.call(vm) : data || {} if (!isPlainObject(data)) { // 保證data必須爲純對象 data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props let i = keys.length while (i--) { if (props && hasOwn(props, keys[i])) { process.env.NODE_ENV !== 'production' && warn( `The data property "${keys[i]}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(keys[i])) { proxy(vm, `_data`, keys[i]) //將屬性代理到vm上 } } // observe data 將data轉換爲監控對象 observe(data, true /* asRootData */) }
const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get // create internal watcher for the computed property. watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions) // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } } } export function defineComputed (target: any, key: string, userDef: Object | Function) { if (typeof userDef === 'function') { sharedPropertyDefinition.get = createComputedGetter(key) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop sharedPropertyDefinition.set = userDef.set ? userDef.set : noop } Object.defineProperty(target, key, sharedPropertyDefinition) }
computed其實自己也是一種特殊的而且lazy的watcher,在get時它做爲所計算的屬性依賴而被收集,同時它把依賴本身的watcher也添加到屬性的依賴中去,這樣當原屬性變化時,就會通知到依賴computed的依賴從新獲取最新值。
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { // 將本身添加到屬性的依賴列表中去 watcher.evaluate() } if (Dep.target) { // 將依賴watcher的依賴也收集到屬性依賴列表中去 watcher.depend() } return watcher.value } } }
function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] // 能夠是數組,爲key建立多個watcher if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } function createWatcher (vm: Component, key: string, handler: any) { let options if (isPlainObject(handler)) { options = handler handler = handler.handler } // 若是handle傳入爲字符串,則直接找vm上的方法,通常是methods中定義的方法,這也是methods的初始化要先於watch初始化的緣由 if (typeof handler === 'string') { handler = vm[handler] } vm.$watch(key, handler, options) // 沒找到$watch在原型上的定義 }
通過這些初始化的步驟,一個組件就被創造出來了,緊接着就能夠callHook(vm, 'created')。
至此,咱們主要了解了Vue整個生命週期,以及Vue建立(created)前的過程,從生命週期圖中能夠看到還有重要一步 Observe Data沒有分析,狀態初始化過程當中用到監控對象如observe(data),依賴收集Dep等等,分析完Observe Data 應該就能夠了解到Vue的數據綁定原理,這個分析留給下一篇文章。
參考資料:http://blog.cgsdream.org/2016/11/11/vue-source-analysis-2/