深入淺出vue.js----實例方法與全局API的實現原理---生命週期相關的實例方法-vm.$destroy、vm.$forceUpdate

一、生命週期相關的實例方法

(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();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章