面試的時候常常被問一些Vue源碼相關的問題,一般狀況下, 我會在面試前惡補掘金上的麪筋來對付面試,什麼雙向綁定的原理呀,什麼虛擬dom樹呀,實際上我壓根兒就沒仔細研究過,其一是本身真的比較菜,其二工做上也用不上,別本身給本身添堵。但後面想一下,不少事情,爲之則易,不爲則難,給本身設立困難(負重)才能進步,決定天天多一點Vue的源碼,在Vue的源碼選擇上,我選擇了最老的版本(0.1)😬(真的怕本身看起來吃力), 閱讀的模式爲通讀,從易到難一個文件一個文件的看,看完一個文件後再看它的單元測試,等徹底吃透後複製粘貼代碼到本地運行測試用例爲代碼塊寫一些中文註釋,打上tag推到本身的倉庫,開始梳理寫文章總結(以前有猶豫過是否應該在掘金上寫文章,由於這類Vue源碼解析的文章已經不少了,並且還寫的很好,我再寫一遍是否還存在乎義,後面想仍是寫吧,流水總結也不錯💧)。javascript
這是我發的第四篇關於Vue源碼的文章, 本文介紹observer(發佈者), 顧名思義observer用於發佈消息, 在Object或者Array發生變化時,被observe的Object或Array經過其掛載的Emitter觸發特定的事件。若Object或Array有層級關係,事件會繼續冒泡到上層的Object或Array。(第一篇關於Vue源碼的文章介紹過Emitter傳送門)java
下文主要分二部分:git
observe對象的內部函數調用順序: observe->watch->(watchObject/watchArray) -> (convent & conventKey) -> observe
es6
observe數組的內部函數調用順序: watch -> (watchObject/watchArray) -> (convent & conventKey) -> observe -> watch
github
下面大體說下這些函數的做用:面試
observe -> observe
, watch -> watch
想表達的意思是對象和數組層層遞歸observe的。(watchObject/watchArray)
添加__emitter__屬性,使每層對象或數組能夠發佈信息(觸發事件)(convent & conventKey)
conventKey用於set/get的攔截,當set的值爲數組或者對象時重複上面的操做,調用watch/observewatchMutation
該方法在代碼打包的時候就直接執行了,該方法做用在ArrayProxy裏重寫覆蓋pop, push...
, 攔截Array.prototype上操做數組元素的方法, 讓pop, push, unshift, shift ...
這些方法在調用時觸發mutate事件。$set
進行賦值, $set
在內部又調用了splice
。observe的實現基礎是經過攔截對象的set/get方法,攔截數組的pop, push, shift, unshift, splice
方法得到對象和數組發生了變化,經過每層掛載的__emitter__
進行信息發佈, 順便說下: 對象與對象,對象與數組之間存在依賴關係,這麼處理他們之間的依賴關係了,在個人第二篇文章傳輸門裏的Binding裏面有subs, deps,其中的deps爲這個Binding的依賴,要這麼來收集這些依賴呢?能夠經過上面的攔截對象get方法來收集依賴, 能夠自行移步看下deps-parser.js的代碼。😄😄數組
經過上面的簡介,你們知道當數組或對象發生變化時會觸發事件(發佈消息),觸發事件是經過Emitter實現的,Emitter是數組或對象上的屬性, 因此在observe前, 爲對象添加__emitter__屬性,同時可能要爲對象或數組添加一些特定的方法(方法是共享的),再者像observe數組是經過攔截push,pop,shift,unshift,splice
等方法,因此不能直接修改原型鏈上的方法,這是咱們須要數組和對象代理,數組代理arrayProxy.__proto__ === Array.prototype
,對象代理objProxy.__proto__ === Object.prototype
,自定義的方法(好比: $add, $delete
)放入arrayProxy和objProxy
。bash
// 代碼爲了舉例,作了修改
var ArrayProxy = Object.create(Array.prototype)
var ObjProxy = Object.create(Object.prototype)
// 這裏添加__emitter__屬性
function convert(obj) {
if (obj.__emitter__) return true
var emitter = new Emitter()
def(obj, '__emitter__', emitter) // defineProperty
// ...
}
function augment(target, src) {
target.__proto__ = src // 若是有隱式原型鏈,src裏面的key不可枚舉
convert(target)
}
function watchObject(obj) {
augment(obj, ObjProxy) // obj.__proto__ === ObjProxy,
// ...
}
function watchArray(arr) {
augment(arr, ArrayProxy) // 註釋同上
// ...
}
複製代碼
在observer裏面數組和對象的冒泡內部實現不一樣,數組內部子元素的__emitter__的owners裏存放了父數組的引用,子元素改變時直接通知owners裏存放的父元素,父元素自身觸發事件。對象內部子元素改變,事件冒泡,父元素中有層事件代理,事件代理收到了子元素的事件,而後父元素自身在觸發事件。下面經過爲了舉例來清楚說明數組與對象事件冒泡的不一樣之處,如今有child = {a: 'a'}
, 數組A: A = [child]
, 對象B: B = {child:child}
。dom
var child = {a: 'a'}
var A = [child]
var B = {child:child}
var ob = new Emitter()
Observer.watch(A) // observe數組
Observer.observe(B, 'test', ob) // observe對象
// 如今改變child
// A.__emitter__收到一個set
// ob收到3個set事件, test, test.child, test.child.a
child.a = 'b'
複製代碼
當child.a發生改變時, child.__emitter__會觸發set事件, child.__emitter__中默認有2個監聽set事件的處理函數,一個用於數組,一個用於對象分別在covert和observe函數裏。函數
// child.__emitter__.owners 爲[A]
// obj爲child, child.__emitter__中監聽了事件set,
// 這個處理函數針父節點爲數組,代碼部分我直接寫死了,
// 源碼實現細節在convert的propagateChange裏面
child.__emitter__.on('set', function(){
// owners 爲 [A]
var owners = child.__emitter__.owners,
i = owners.length
while (i--) {
// A自身觸發事件
owners[i].__emitter__.emit('set', '', '', true)
}
})
複製代碼
// 以B代理child作代碼示例,因此我把代碼寫死了
// 實際源碼在observe中
B.__emitter__.proxies = {}
var proxies = B.__emitter__.proxies[‘child’] = {
set: function (key, val, propagate) {
// 告訴父對象,這裏變化了,不冒泡到上一層
// 這裏的key爲 a
if (key) ob.emit('set', 'child.'+key, val)
if (propagate) {
ob.emit('set', 'child', B.child, true)
}
},
}
child.__emitter__.on('set', proxies.set)
複製代碼
observer.js裏面大約有400多行代碼, 我比較笨,反覆看了幾遍才徹底梳理清楚,我內心認爲我徹底讀透了,可是把它寫成文章發佈,感受仍是差了一些,不能徹底表達清楚我想表達的😅😅若是你們有時間的話,建議本身翻閱源碼,必定會有不同的收穫💪。
持續更新...❤️