一、生命週期相關的實例方法
(1)vm.$destroy、vm.$forceUpdate
從lifecycleMixin中掛載到Vue構造函數的prototype屬性上。
export function lifecycleMixin(Vue){
Vue.prototype.$forceUpdate = function(){
//做點什麼
}
Vue.prototype.$destory = function(){
//做點什麼
}
}
(2)vm.$nextTick
從renderMixin中掛載到Vue構造函數的prototype屬性上的。
export function renderMixin(Vue){
Vue.prototype.$nextTick = function(fn){
//做點什麼
}
}
(3)vm.$mount
在跨平臺的代碼中掛載到Vue構造函數的prototype屬性上的。
二、vm.$forceUpdate
(1)作用
迫使Vue.js實例重新渲染。注意它僅僅影響實例本身以及插入插槽內容的子組件,而不是所有子組件。
(2)實現
1、只需要執行watcher的update方法,就可以讓實例重新渲染。
2、Vue.js的每一個實例都有一個watcher。當狀態發生改變時,會通知到組件級別,然後組件內部使用虛擬DOM進行更詳細的重新渲染操作。
3、事實上,組件就是Vue.js實例,所以組件幾倍的watcher和Vue.js實例上的watcher說的是同一個watcher。
4、手動執行實例watcher的update方法,就可以使Vue.js實例重新渲染。
Vue.prototype.$forceUpdate = function(){
const vm = this;
if(vm._watcher){
vm._watcher.update();
}
}
5、vm._watcher就是Vue.js實例的watcher,每當組件內依賴的數據發生變化時,都會自動觸發Vue.js實例中_watcher的update方法。
6、重新渲染的實現原理並不難,Vue.js的自動渲染通過變化偵測來偵測數據,即當數據發生變化時,Vue.js實例重新渲染。而vm.$forceUpdate是手動通知Vue.js實例重新渲染。
二、vm.$destroy
(1)作用
完全銷燬一個實例,它會清理該實例與其他實例的連接,並解綁其全部指令及監聽器,同時會觸發beforeDestory和destroyed的鉤子函數。
(2)這個方法並不是很常用,大部分場景下並不需要銷燬組件,只需要使用v-if或則v-for等指令以數據驅動的方式控制子組件的生命週期即可。
(3)實現原理
Vue.prototype.$destory = function(){
const vm = this;
if(vm._isBeingDestroyed){
return;
}
callHook(vm,"beforeDestroy");
vm._isBeingDestroyed = = true;
}
1、爲了防止vm.$destroy被反覆執行,先對屬性_isBeingDestroyed進行判斷,如果它爲true,說明Vue.js實例正在被銷燬,直接使用return語句退出函數執行邏輯。因爲銷燬只需要銷燬一次即可,不需要反覆銷燬。
2、然後調用callHook函數觸發beforeDestroy的鉤子函數(callHook會觸發參數中提供的鉤子函數)。
(4)銷燬實例的邏輯1
首先,需要清理當前組件與父組件之間的連接。組件就是Vue.js實例,所以要清理當前組件與父組件之間的連接,只需要將當前組件實例從父組件實例的$children屬性中刪除即可。
說明:Vue.js實例的$children屬性存儲了所有子組件
const parent = vm.$parent;
if(parent && !parent._isBeingDestroyed && !vm.$options.abstract){
remove(parent.$children,vm)
}
1、如果當前實例有父級,同時父級沒有被銷燬且不是抽象組件,那麼將自己從父級的子列表中刪除,也就是將自己的實例從父級的$children屬性中刪除。
2、事實上,子組件在不同父組件中是不同的Vue.js實例,所以一個子組件實例的父級只有一個,銷燬操作也只需要從父級的子組件列表中銷燬當前這個Vue.js實例。
export function remove(arr,item){
if(arr.length){
const index = arr.indexOf(item);
if(index>-1){
return arr.splice(index,1);
}
}
}
(5)銷燬實例的邏輯2
1、父子組件間的鏈接斷掉之後,需要銷燬實例上的所有watcher,也就是說需要將實例上所有的依賴追蹤斷掉。
2、狀態會收集一些依賴,當狀態發生改變時會向這些依賴發送通知,而被收集的依賴就是watcher實例。因此,當Vue.js實例被銷燬時,應該將實例所監聽的狀態都取消掉,也就是從狀態的依賴列表中將watcher移除。
3、watcher的teardown方法,它的作用是從所有依賴項的Dep列表中將自己移除。即只要執行這個方法,就可以斷掉這個watcher所監聽的所有狀態。
4、斷掉Vue.js實例自身的watcher實例監聽的所有狀態。
if(vm._watcher){
vm._watcher.teardown();
}
5、執行了組件自身的watcher實例的teardown方法,從所有依賴項的訂閱列表中刪除watcher實例。刪除之後,當狀態發生變化時,watcher實例就不會再得到通知。
6、vm._watcher來源
當執行new Vue()時,會執行一系列初始化操作並渲染組件到實體上,其中就包括vm._watcher的處理
7、從Vue.js2.0開始,變化偵測的粒度調整爲中等粒度,它只會發送通知到組件級別,然後組件使用虛擬DOM進行重新渲染。組件其實就是Vue.js實例。
8、怎麼通知到組件級別
在Vue.js實例上,有一個watcher,也就是vm._watcher,它會監聽這個組件中用到的所有狀態,即這個組件內用到的所有狀態的依賴列表中都會收集到vm._watcher。當這些狀態發生變化時,也都會通知vm._watcher,然後這個watcher再調用虛擬DOM進行重新渲染。
(6)銷燬實例的邏輯3
1、只從狀態的依賴列表中刪除Vue.js實例上的watcher實例是不夠的。Vue.js提供了vm.$watch方法,它允許用戶監聽某個狀態。因此,還需要銷燬用戶使用vm.$watch所創建的watcher實例。
2、從狀態的依賴列表中銷燬用戶創建的watcher實例和銷燬Vue實例上的watcher實例相同,只需要執行watcher的teardown方法。
3、問題:如何知道用戶創建了多少個watcher?
1)Vue.js的解決方案是執行new Vue()時,在初始化的流程中,在this上添加一個_watchers屬性
vm._watchers = [];
2)每當創建watcher實例時,都會將watcher實例添加到vm._watchers中
export default class Watcher{
constructor(vm,expOrFn,cb){
<!-- 每當創建watcher實例時,都將watcher實例添加到vm._watchers中 -->
vm._watchers.push(this);
}
}
4、只需要遍歷vm._watchers並依次執行每一項watcher實例的teardown方法,就可以將watcher實例從它所監聽的狀態的依賴列表中移除。
let i = vm._watchers.length;
while(i--){
vm._watchers[i].teardown();
}
(7)向Vue.js實例添加_isDestroyed屬性來表示Vue.js實例已經被銷燬。
vm._isDestroyed = true;
(8)當vm.$destroy執行時,Vue.js不會將已經渲染到頁面中的DOM節點移除,但會將模板中的所有指令解綁。
vm._patch_(vm._vnode,null)
(9)觸發destroyed鉤子函數
callHook(vm,'destroyed')
(10)最後,移除實例上的所有事件監聽器。
vm.$off()
(11)完整代碼
Vue.prototype.$destory = function(){
const vm = this;
<!-- 防止重複銷燬 -->
if(vm._isBeingDestroyed){
return;
}
<!-- 調用鉤子函數beforeDestroy -->
callHook(vm,"beforeDestroy");
vm._isBeingDestroyed = = true;
<!-- 刪除自己與父級之間的連接 -->
const parent = vm.$parent;
if(parent && !parent._isBeingDestroyed && !vm.$options.abstract){
remove(parent.$children,vm);
}
<!-- 從watcher監聽的所有狀態的依賴列表中移除watcher -->
if(vm._watcher){
vm._watcher.teardown();
}
let i = vm._watchers.length;
<!-- 將從vm.$watcher創建的watcher實例從它所監聽的狀態的依賴列表中移除 -->
while(i--){
vm._watchers[i].teardown();
}
<!-- 表示實例已經被銷燬 -->
vm._isDestroyed = true;
<!-- 將模板中的所有指令解綁 -->
vm._patch_(vm._vnode,null)
<!-- 觸發destroyed鉤子函數 -->
callHook(vm,'destroyed')
<!-- 移除實例上的所有事件監聽器 -->
vm.$off();
}