在咱們平時使用各類框架的時候,都避免不了使用到一種特性,就是 生命週期 鉤子,這些鉤子,能夠給咱們提供不少便利,讓咱們在數據更新的每個階段,均可以捕捉到它的變化。javascript
咱們最主要講的是 vue
的生命週期,先來一份大綱:vue
今天,我就來分析一下,vue
在調用到每個生命週期前,到底都在作了什麼?java
來看看官方的生命週期流程圖:node
這張圖其實已經大概的告訴了咱們,每一個階段作了什麼,可是我以爲還有必要詳細的去分析一下,這樣在將來若是咱們要實現相似於 vue
這種框架的時候,能夠知道在什麼時間,應該去作什麼,怎麼去實現。api
function initInternalComponent (vm, options) {
var opts = vm.$options = Object.create(vm.constructor.options);
// doing this because it's faster than dynamic enumeration.
var parentVnode = options._parentVnode;
opts.parent = options.parent;
opts._parentVnode = parentVnode;
opts._parentElm = options._parentElm;
opts._refElm = options._refElm;
var vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
function resolveConstructorOptions (Ctor) {
var options = Ctor.options;
if (Ctor.super) {
var superOptions = resolveConstructorOptions(Ctor.super);
var cachedSuperOptions = Ctor.superOptions;
if (superOptions !== cachedSuperOptions) {
// super 選項已更改,須要解決新選項。
Ctor.superOptions = superOptions;
// 檢查是否有任何後期修改/附加選項
var modifiedOptions = resolveModifiedOptions(Ctor);
// 更新基本擴展選項
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions);
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
if (options.name) {
options.components[options.name] = Ctor;
}
}
}
return options
}
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
if (process.env.NODE_ENV !== 'production') {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
複製代碼
在一開始,先作了一個屬性的合併處理,若是 options
存在而且 _isComponent
爲 true
,那麼就調用 initInternalComponent
方法,這個方法最主要是優化內部組件實例化,由於動態選項合併不是常緩慢,而且沒有內部組件選項須要特殊處理;緩存
若是不知足上述條件,就調用 mergeOptions
方法去作屬性合併,最後的返回值賦值給 $options
, mergeOptions
的實現原理,在 Vue 源碼解析(實例化前) - 初始化全局API(一) 這裏作過詳細的講解,有不瞭解的朋友,能夠跳轉這裏去看;bash
作一個渲染攔截,這裏的攔截,最主要是爲了在調用 render
方法的時候,經過 vm.$createElement
方法進行 dom
的建立;框架
function initLifecycle (vm) {
var options = vm.$options;
// 找到第一個非抽象父級
var 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 = null;
vm._directInactive = false;
vm._isMounted = false;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}
複製代碼
初始化了一些參數;dom
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init父級附加事件
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
function updateComponentListeners ( vm, listeners, oldListeners ) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
target = undefined;
}
複製代碼
初始化事件,若是 _parentListeners
存在的話,更新組件的事件監聽;ide
function initRender (vm) {
vm._vnode = null; // 子樹的根
vm._staticTrees = null; // v-once緩存的樹
var options = vm.$options;
var parentVnode = vm.$vnode = options._parentVnode; // 父樹中的佔位符節點
var renderContext = parentVnode && parentVnode.context;
vm.$slots = resolveSlots(options._renderChildren, renderContext);
vm.$scopedSlots = emptyObject;
// 將createElement fn綁定到此實例,以便咱們在其中得到適當的渲染上下文。
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
// 規範化始終應用於公共版本,在用戶編寫的渲染函數中使用。
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
// 暴露了$ attrs和$ listeners以便更容易建立HOC。
// 他們須要被動反應,以便使用它們的HOC始終更新
var parentData = parentVnode && parentVnode.data;
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
}, true);
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
}, true);
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true);
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true);
}
}
複製代碼
初始化渲染,defineReactive
的使用和做用,在 Vue 源碼解析(實例化前) - 響應式數據的實現原理 這裏有講解,你們想了解能夠看一下;
到了這裏執行完畢後,就調用到了 beforeCreate
方法。
initInjections(vm); // 在數據/道具以前解決注入
initState(vm);
initProvide(vm); // 解決後提供的數據/道具
callHook(vm, 'created');
複製代碼
function resolveInject (inject, vm) {
if (inject) {
// 由於流量不足以弄清楚緩存
var result = Object.create(null);
var keys = hasSymbol
? Reflect.ownKeys(inject).filter(function (key) {
return Object.getOwnPropertyDescriptor(inject, key).enumerable
})
: Object.keys(inject);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var provideKey = inject[key].from;
var source = vm;
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break
}
source = source.$parent;
}
if (!source) {
if ('default' in inject[key]) {
var provideDefault = inject[key].default;
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault;
} else if (process.env.NODE_ENV !== 'production') {
warn(("Injection \"" + key + "\" not found"), vm);
}
}
}
return result
}
}
var shouldObserve = true;
function toggleObserving (value) {
shouldObserve = value;
}
function initInjections (vm) {
var result = resolveInject(vm.$options.inject, vm);
if (result) {
toggleObserving(false);
Object.keys(result).forEach(function (key) {
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], function () {
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]);
}
});
toggleObserving(true);
}
}
複製代碼
在這裏,其實最主要就是用來作不須要響應式的數據,官方文檔:provide / inject ;
function initState (vm) {
vm._watchers = [];
var 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);
}
}
複製代碼
在處理完 inject
後,緊接着就作了 props
、methods
、data
、computed
和 watch
的初始化處理;
function initProvide (vm) {
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
複製代碼
Provide
和 Inject
做用實際上是同樣的,只是處理的方式不同,具體區別請看官方文檔:provide / inject ;
到這裏執行完畢後,就要走到 created
鉤子了。
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
複製代碼
在渲染 dom
,先檢查了是否存在渲染位置,若是不存在的話,也就不會註冊了;
Vue.prototype.$mount = function ( el, hydrating ) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
function mountComponent ( vm, el, hydrating ) {
vm.$el = el;
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
}
callHook(vm, 'beforeMount');
}
複製代碼
在 beforeMount
這裏,基本沒作什麼事情,只是作了一個 render
方法若是存在就綁定一下 createEmptyVNode
函數;
綁定完畢後,就執行了 beforeMount
鉤子;
var updateComponent;
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = function () {
var name = vm._name;
var id = vm._uid;
var startTag = "vue-perf-start:" + id;
var endTag = "vue-perf-end:" + id;
mark(startTag);
var vnode = vm._render();
mark(endTag);
measure(("vue " + name + " render"), startTag, endTag);
mark(startTag);
vm._update(vnode, hydrating);
mark(endTag);
measure(("vue " + name + " patch"), startTag, endTag);
};
} else {
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
}
// 咱們在觀察者的構造函數中將其設置爲vm._watcher,由於觀察者的初始補丁可能會調用$ forceUpdate(例如,在子組件的掛載掛鉤內),這依賴於已定義的vm._watcher
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
hydrating = false;
// 手動掛載的實例,在本身掛載的調用掛載在其插入的掛鉤中爲渲染建立的子組件調用
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
複製代碼
在 new Watcher
的時候,調用了 _render
方法,實現了 dom
的渲染,具體 _render
都作了什麼,點擊查看 vue 源碼解析(實例化前) - 初始化全局 API(最終章);
在執行完實例化 Watcher
之後,若是 $node
不存在,就說明是初始化渲染,執行 mounted
鉤子;
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
};
複製代碼
若是當前的 vue
實例的 _isMounted
爲 true
的話,直接調用 beforeUpdate
鉤子;
_isMounted 在
mounted
鉤子執行前就已經設置爲 true 了。
執行 beforeUpdate
鉤子;
function callUpdatedHooks (queue) {
var i = queue.length;
while (i--) {
var watcher = queue[i];
var vm = watcher.vm;
if (vm._watcher === watcher && vm._isMounted) {
callHook(vm, 'updated');
}
}
}
複製代碼
由於有多個組件的時候,會有不少個 watcher
,在這裏,就是檢查當前的得 watcher
是哪一個,是當前的話,就直接執行當前 updated
鉤子。
Vue.prototype.$destroy = function () {
var vm = this;
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy');
};
複製代碼
在卸載前,檢查是否已經被卸載,若是已經被卸載,就直接 return
出去;
執行 beforeDestroy
鉤子;
vm._isBeingDestroyed = true;
// 從父級那裏刪除本身
var parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// 拆解觀察者
if (vm._watcher) {
vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
// 從凍結對象的數據中刪除引用可能沒有觀察者。
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
// 準備執行最後一個鉤子
vm._isDestroyed = true;
// 在當前渲染的樹上調用destroyed hook
vm.__patch__(vm._vnode, null);
callHook(vm, 'destroyed');
複製代碼
其實這裏就是把全部有關本身痕跡的地方,都給刪除掉;
執行 destroyed
鉤子。
到這裏,其實每個生命週期的鉤子作了什麼,咱們已經瞭解的差很少了,那這樣大量的代碼看起來可能不是很方便,因此咱們作一個總結的 list
:
beforeCreate
:初始化了部分參數,若是有相同的參數,作了參數合併,執行 beforeCreate
;created
:初始化了 Inject
、Provide
、 props
、methods
、data
、computed
和 watch
,執行 created
;beforeMount
:檢查是否存在 el
屬性,存在的話進行渲染 dom
操做,執行 beforeMount
;mounted
:實例化 Watcher
,渲染 dom
,執行 mounted
;beforeUpdate
:在渲染 dom
後,執行了 mounted
鉤子後,在數據更新的時候,執行 beforeUpdate
;updated
:檢查當前的 watcher
列表中,是否存在當前要更新數據的 watcher
,若是存在就執行 updated
;beforeDestroy
:檢查是否已經被卸載,若是已經被卸載,就直接 return
出去,不然執行 beforeDestroy
;destroyed
:把全部有關本身痕跡的地方,都給刪除掉;Vue
生命週期實現,就先講到這裏了,裏面有些地方,細節講的不是不少,由於這個文章和以前的源碼解析方向和目的不同,源碼講解的目的是爲了讓你們一步一步的去了解,都寫了什麼,而這篇文章的目的是爲了讓你們瞭解到每一個生命週期的階段,都作了什麼。
若是你們有以爲有問題的地方,或者寫的很差的地方,還請直接下方評論指出,謝謝了。