vue 源碼之雙向綁定

vue 雙向綁定

vue 雙向綁定,問就是 Object.defineProperty 劫持數據獲得狀態變更,發佈訂閱者模式進行變更通知。好吧,現在地球人都知道這兩句話了,要是別人再問你實現細節,我們應該如何回答呢。也是爲了讓自己更加了解,從自己大概知道和能與人清楚表達,這就是今天寫文章的目的了。

閱讀前,你需要具備 vue 生命週期,Object.defineProperty,以及發佈訂閱者模式(相關博客)的一些知識。

劫持數據

......
callHook(vm, 'beforeCreate')
initInjections(vm)  //注入 Data/prop 數據
initProvide(vm)		//注入完成
callHook(vm, 'created')

在進入 beforeCreate 週期後,會注入 data/prop 等數據,數據劫持就是發生在此時(initInjections 函數)。

function initInjections(){
	......
	Object.keys(result).forEach(key => {
		defineReactive(vm, key, result[key])
	})
}

export function defineReactive( obj: Object, key: string, val: any ......){
	......
	Object.defineProperty(obj, key, {
		getfunction(){
			......
		},
		set:function(){
			......
		}	
	}
}

把對象的屬性鍵轉換爲getter/setter,一旦劫持了數據,我們就可以爲所欲爲了。

但是這裏只轉換了第一層數據,我們還要嵌套遍歷對象。

Observer 監聽類

在轉化對象屬性 defineReactive 函數中,會先 new 一個監聽類(Observer)。監聽類通過遞歸數據,把 getter/setter 附加到每個被觀察對象(讓數據變化綁定訂閱和通知這兩個行爲)。

function observe(value){
	let ob
	if (hasOwn(value, '__ob__') 	//通過有無__ob__屬性判斷是否處於監聽中
		ob = value.__ob__
	else
		ob = new Observer(value)
	return ob	
}

export class Observer{
	......
	def(value, '__ob__', this)		//爲屬性添加屬性__ob__,指向監聽實例
	this.walk(value)
}

walk (obj: Object) {
    const keys = Object.keys(obj)
   	for (let i = 0; i < keys.length; i++) {
		defineReactive(obj, keys[i], obj[keys[i]])	//通過第三個參數遞歸,繼續監聽對象
   	}
}

單單只是獲取數據狀態變更不行,還得發送通知。

Dep 調度中心類

瞭解發佈訂閱者模式就該知道,訂閱者(觀察者)把自己想訂閱的事件註冊到調度中心,當該事件觸發時候,發佈者(目標)發佈該事件到調度中心,由調度中心統一調度訂閱者註冊到調度中心的處理代碼。

我們通過 getter 爲每個訂閱者訂閱事件,setter 來發送通知。

export function defineReactive( obj, key, val ......){
	......
	const dep = new Dep()		//每一個數據都會有一個專門的 Dep 實例。
	observe(val)				//遞歸遍歷數據
	Object.defineProperty(obj, key, {
		getfunction(){
			......
			dep.depend()		//訂閱事件
			return value
		},
		set:function(){
			......
			 dep.notify()		//發送通知
		}	
	}
}

在 defineReactive 的 get 裏面訂閱事件。然後在 set 中發送通知。

export class Dep{
	......
	constructor () {
	    this.subs = []		//依賴隊列
  	}
	//訂閱
	depend () {
	    if (Dep.target) {
	      Dep.target.addDep(this)
	    }
	  }
	//發送通知
	notify () {	
	    const subs = this.subs.slice()
	    for (let i = 0, l = subs.length; i < l; i++) {
	      subs[i].update()
	    }
	}
	//添加依賴(訂閱者)
	addSub (sub: Watcher) {
	    this.subs.push(sub)
  	}
}

Dep.target 後續會講到,指向目標 watcher 實例

Watcher 訂閱者類

......
callHook(vm, 'beforeMount') 
new Watcher()
......

在 beforeMount 生命週期後,會 new 一個 Watcher 對象。

export class Watcher {
	......
	constructor (){
		this.cb = cb
		this.get()
	}
	
	get(){
		......
		Dep.target = this;   			//讓當前組件實例的 Dep.target 指向這個 Watcher 對象
		this.getter.call(vm, vm)		//執行一次 getter
		Dep.target = null
		return value
	}

	addDep(dep){						//爲調度中心依賴隊列添加當前 Watcher 對象
		dep.addSub(this)
	}

	update(){
		this.cb.call(this.vm, value, oldValue)		//執行 setter
	}
}

生成訂閱者 watcher,會執行一次數據的 getter(通過監聽類遞歸,監聽並讓每一個屬性都有一個調度中心),然後通過訂閱事件,把這個訂閱者關聯到所有調度中心的依賴內(通過 Dep.target 的指向互相調用)。當數據變化時,該處的調度中心就會通知相應的訂閱者。

每個數據都有一個調度中心,每個組件實例都有一個訂閱者

在這裏插入圖片描述

總結

實現數據的雙向綁定,首先要對數據進行劫持監聽,所以需要設置一個監聽器Observer,用來監聽所有屬性。如果屬性發上變化了,就需要告訴訂閱者Watcher看是否需要更新。我們通過消息調度中心Dep來專門收集這些訂閱者併發布消息通知,以此在監聽器Observer和訂閱者Watcher之間進行統一管理。

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