observer | 天天讀一點Vue源碼

前言

面試的時候常常被問一些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的調用順序和實現原理, 本部分主要幫助你們瞭解閱讀源碼的整個順序,幫助你們節約閱讀源碼的時間,後面簡單介紹下Vue中得到數據變化的原理(老生常談)
  • 發佈事件的基礎, 事件是如何冒泡的幫助你們瞭解,信息是如何傳輸通訊的。

源碼中數組與對象observe的內部調用順序

observe對象的內部函數調用順序: observe->watch->(watchObject/watchArray) -> (convent & conventKey) -> observees6

observe數組的內部函數調用順序: watch -> (watchObject/watchArray) -> (convent & conventKey) -> observe -> watchgithub

下面大體說下這些函數的做用:面試

  1. observe和watch爲入口函數,observe相對與watch須要傳Emitter對象,這個Emitter負責接收管理被observed對象的set事件,而watch數組中,數組的__emitter__負責接收數組內部元素的set事件,這是observe和watch的不一樣,相同之處能夠看到上面都是 observe -> observe, watch -> watch想表達的意思是對象和數組層層遞歸observe的。
  2. (watchObject/watchArray)添加__emitter__屬性,使每層對象或數組能夠發佈信息(觸發事件)
  3. (convent & conventKey) conventKey用於set/get的攔截,當set的值爲數組或者對象時重複上面的操做,調用watch/observe
  4. 其實還有個方法沒有列出來,watchMutation該方法在代碼打包的時候就直接執行了,該方法做用在ArrayProxy裏重寫覆蓋pop, push..., 攔截Array.prototype上操做數組元素的方法, 讓pop, push, unshift, shift ...這些方法在調用時觸發mutate事件。
  5. 這版裏面爲數組元素直接賦值是不會被監聽的,那個時候es6的proxy語法還沒出,數組只攔截了prototype上的方法,那怎麼辦了了,其實能夠調用在ArrayProxy裏面定義的$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和objProxybash

// 代碼爲了舉例,作了修改
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函數裏。函數

  1. 若是上層是數組, child.__emitter__.owners爲數組存放的爲父節點,本例存放的爲A
// 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)
    }
})
複製代碼
  1. 若是上層是對象,上層的對象的__emitter__中有「層事件代理」,child.__emitter__觸發set時,觸發「事件代理」裏的處理函數,裏面的處理函數在觸發上層對象的set事件,直接看代碼:
// 以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多行代碼, 我比較笨,反覆看了幾遍才徹底梳理清楚,我內心認爲我徹底讀透了,可是把它寫成文章發佈,感受仍是差了一些,不能徹底表達清楚我想表達的😅😅若是你們有時間的話,建議本身翻閱源碼,必定會有不同的收穫💪。

observer.js

observer.test.js

持續更新...❤️

相關文章
相關標籤/搜索