剖析vue原理及運行機制(上)

這是筆者最近一段時間的學習收穫,將其整理成文,但願能對各位有些許幫助。文章稍長,但絕對精粹。也算是筆者爲各位獻上的一份「元旦大禮」吧~~
(因文章過長,故將其整理爲上、下兩篇,之間連續貫通)javascript


Vue運行機制全局預覽

【初始化及掛載】=>【編譯】(parse-optimize-generate)=>【render function渲染】(響應式)=>【Virtual DOM】=>【更新視圖】html

初始化及掛載
在這裏插入圖片描述
在new了vue以後,Vue會調用_init函數進行(全局)初始化。初始化生命週期、事件、props、methods、data、computed、watch等。其中最重要的是經過Object.defineProperty設置setter和getter函數,用來實現【響應式】和【依賴收集】。
初始化後調用 $mount 掛載組件 —— 若是是運行時編譯,即不存在 render function 可是存在templete的狀況,(此後)還需進行【編譯】步驟。vue

編譯
在這裏插入圖片描述java

  • parse :用正則等方法解析templete模板中的指令、class、style等數據,造成AST(抽象語法樹——源代碼的抽象語法結構的樹狀表現形式)
  • optimize :主要爲了標記static節點。這是vue的一處優化——後面當update更新界面時,會有一個 patch 的過程,diff算法會直接跳過靜態節點,從而減小了比較的過程,優化了patch的性能
  • generate :將AST轉化爲 render function 字符串的過程。獲得 render & staticRenderFns 字符串

經歷以上三個階段後,組件中就會存在渲染VNode所需的render function了。react

響應式
Vue的響應式中作出卓越貢獻的也就getter和setter了。
當 render function 被渲染時,由於會讀取所需對象的值,因此會觸發getter進行【依賴收集】,其目的是將觀察者Watcher對象存放到當前閉包中的訂閱者Dep的subs中,造成以下關係:
在這裏插入圖片描述
在修改對象值時,會觸發對應的setter,setter通知以前【依賴收集】獲得的Dep中每個Watcher,告訴他們本身的值改變了,須要從新渲染視圖。這時候這些Watcher就開始調用update來更新視圖 —— 固然,這中間還有一個patch的過程,以及使用隊列來異步更新的策略。web

Virtual DOM
前面說 render function 會轉化爲VNode節點。而Virtual DOM實際上是一棵以JavaScript對象爲基礎的樹。用對象屬性描述節點。它實際上只是一層對真是DOM的抽象。
正是因爲Virtual DOM是以JavaScript對象爲基礎而不依賴平臺環境。因此使他擁有了跨平臺的能力。算法

一個簡單的例子:express

// AST
{ 
 
   
	tag:'div',
	children:[
		{ 
 
   
			tag:'a',
			data:{ 
 
   
				directives:[   //屬性
					{ 
 
   
						rawName:'v-show',
						expression:'isShow',
						name:'show',
						value:true
					}
				],
				staticClass:'demo'
			},
			text:'click me'
		}
	]
}

渲染以後就獲得:閉包

<div>
	<a class="demo" v-show="isShow">click me</a>
</div>

這兩段代碼的逆向過程,其實就是一個HTML解析器解析標籤的過程。讓咱們來逐步看看:異步


響應式系統

對vue稍有深究的人都必定知道,Vue的【響應式】是基於Object.defineProperty實現的。
其使用方法:

Object.defineProperty(obj,prop,descriptor)
                //目標對象、須要操做的目標對象的屬性名、描述符(屬性彙集地)

好比,通常更喜歡這樣寫:

Object.defineProperty(obj,prop,{ 
 
   
	enumerable:是否能夠枚舉
	configurable:是否能夠被修改或刪除
	get:
	set:
})

實現Observer
首先定義一個函數cb,用來模擬視圖更新。內部是一些更新視圖的方法:

function cb(val){ 
 
   
	//渲染視圖
	console.log('視圖更新了...');
}

而後咱們定義一個defineReactive,這個方法經過Object.defineProperty來實現對對象的【響應式】化,入參固然是obj(須要綁定的對象)、key(obj的一些屬性)、val(具體值),通過defineReactive處理後,obj的key屬性會在讀的時候觸發reactiveGetter方法,在寫的時候觸發reactiveSetter方法:

function defineReactive(obj,key,val){ 
 
   
	Object.defineProperty(obj,key,{ 
 
   
		enumable:true,
		configurable:true,
		get:function reactiveGetter(){ 
 
   
			return val;
		},
		set:function reactiveSetter(newVal){ 
 
   
			if(newVal===val) return;
			cb(newVal);
		}
	});
}

這些固然是不夠的。咱們須要在這上面再封裝一層observer —— 它傳入一個value(須要「響應式」化的對象)參數,經過遍歷他全部屬性的方式來對該對象的每個屬性作defineReactive處理:

function observer(value){ 
 
   
	if(!value || (typeof value !== 'object')){ 
 
   
		return;
	}
	Object.keys(value).forEach((key)=>{ 
 
   
		defineReactive(value,key,value[key]);
	});
}

最後,咱們把它們封裝到「vue」中:

class Vue{ 
 
   
	constructor(options){ 
 
   
		this._data=options.data;
		observer(this._data);
	}
}

這時,就能夠「隨心所欲了」:

let vm=new Vue({ 
 
   
	data:{ 
 
   
		test:"I'm mxc"
	}
});
vm._data.test="hello world!";   //「視圖更新了...」

這樣代碼就「完美」了嗎?


響應式系統的依賴收集追蹤原理

爲何要「追蹤」?
假如咱們如今有一個Vue對象:

new Vue({ 
 
   
	templete:`<div> <span>{ 
    {text1}}</span> <span>{ 
    {text2}}</span> </div>`,
	data:{ 
 
   
		text1:'text1',
		text2:'text2',
		text3:'text3'
	}
});

而後咱們這麼作了:

this.text3="I'm text3";

如上,咱們修改了text3的數據,但視圖中並不須要text3,因此咱們並不須要調用cb函數。

要解決這個問題,就須要大名鼎鼎的【訂閱】 & 【觀察者】模式了:
訂閱者Dep —— 存放Watcher對象 (->其實這能夠說成是一個「消息管理中心」)

class Dep{ 
 
   
	constructor(){ 
 
   
		this.subs=[]
	}
	addSub(sub){ 
 
   
		this.subs.push(sub);
	}
	//通知全部Watcher更新視圖
	notify(){ 
 
   
		this.subs.forEach((sub)=>{ 
 
   
			sub.update();
		});
	}
}

觀察者Watcher:

class Watcher{ 
 
   
	constructor(){ 
 
   
		//在new一個Watcher對象時將該對象賦值給Dep.target,在get中會用到
		Dep.target=this;
	}
	update(){ 
 
   
		console.log('視圖更新啦...');
	}
}
Dep.target=null;

接下來要去修改一下defineReactive以及Vue的構造函數,來完成【依賴收集的注入】:(咱們再也不須要「cb」了)

function defineReactive(obj,key,val){ 
 
   
	const dep=new Dep();
	Object.defineProperty(obj,key,{ 
 
   
		enumable:true,
		configurable:true,
		get:function reactiveGetter(){ 
 
      //getter【依賴收集】
			dep.addSub(Dep.target);
			return val;
		},
		set:function reactiveSetter(newVal){ 
 
   
			if(newVal===val) return;
			dep.notify();
		}
	});
}
class Vue({ 
 
   
	constructor(options){ 
 
   
		this._data=options.data;
		observer(this._data);
		new Watcher();
		//這裏模擬render的過程,爲了觸發test屬性的get函數
		console.log('render',this._data.test);
	}
})

咱們在閉包中增長了一個Dep類的對象,用來收集Watcher對象。在對象被「讀」的時候,會觸發reactiveGetter,把當前Watcher對象(存放在Dep.target中)收集到Dep類中去;以後「寫」的時候,觸發reactiveSetter,通知類Dep調用notify來觸發全部Watcher的update更新對應視圖。


專欄連接(免費):vue實現原理及運行機制分析

本文同步分享在 博客「行舟客」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索