Vue原始碼分析之Observer

2021-09-17 00:01:29 字數 4289 閱讀 1534

四月份真是慵懶無比的乙個月份,看著手頭上沒啥事幹,只好翻翻**啥的,看了一會vue的原始碼,忽而有點感悟,於是便記錄一下。

觀察者模式一般包含發布者(publisher)和訂閱者(subscriber)兩種角色;顧名思義發布者負責發布訊息,訂閱者通過訂閱訊息響應動作了。

回到vue中,在vue原始碼core/oberver目錄下分析**可以知道有三個類分別是oberver,watcher和dep;那這三個類中誰是publisher,誰是subscriber尼?

觀察者,這個觀察者究竟觀察什麼的尼?

還是用最簡單粗暴的方式,目錄搜尋一下**用到這個類,步步追尋,大致是這樣乙個呼叫過程。

initstate()-->observe(data)-->new observer()
基本上vue在我們的data物件上都會定義乙個__ob__屬性指向新建立的observer物件,就像這樣子:

c: //也是有__ob__屬性的

__ob__: [observer object]

}__ob__: [observer object]

}

這裡可以知道其實物件或者陣列裡面vue都會幫你新增乙個__ob__屬性,但是這個__ob__屬性或者這個observer物件究竟是幹嘛用的尼?

先舉個栗子:

在模板裡面我們遍歷陣列內容,很明顯陣列有多少元素就會輸出多少個li;那麼我們陣列元素增加和刪除的時候怎麼通知到元件去重新渲染尼?

恩,答案就是通過這個__ob__屬性。

好,直接上**:

function definereactive (

obj: object,

key: string,

val: any,

customsetter?: function

) if (array.isarray(value))

}return value

},set: function reactivesetter (newval)

})}

第4步相當重要,如果沒有第4步,我們新增或者刪除元素,剛才那個元件是不會重新渲染的;我們一般情況下都會想到去攔截屬性的get和set方法,在get的方法我們可以收集訂閱者,set的方法我們簡單的判斷舊的值和新的值是否相等我們就可以通知訂閱者去更新;但是對於引用的值(類似object或者array)這樣就不行了,我們得讓他們內容發生變化(主要是增加刪除內容,物件增加乙個屬性時候)的時候也要通知訂閱者去更新,所以__ob__上的dep屬性主要用於監控物件屬性增加和刪減而第1步所建立的dep用於監控屬性值的更新。

但在這裡的例子也導致另外乙個行為,我們剛才在例子中很明顯並沒有實際用到陣列的內容,然而在for迴圈的過程中,也就等同於我們遍歷物件所有內容,vue就會認為我們會「關心」這些內容的變化,所以當物件的內容(假設這個物件裡的元素也是物件,在某個子物件上增加或者刪除乙個屬性)發生變化的時候也會觸發重新渲染;

還有的是vue對陣列的處理跟物件還是有挺大的不同,length是陣列的乙個很重要的屬性,無論陣列增加元素或者刪除元素(通過splice,push等方法操作)length的值必定會更新,那麼豈不是一勞永逸,不需要攔截splice,push等方法就可以知道陣列的狀態更新,但是當我試著在陣列length屬性上用defineproperty攔截的時候,冒出了這樣的錯誤:

uncaught typeerror: cannot redefine property: length
不能重定義length屬性??再用object.getownpropertydescriptor(arr, 'length')檢視一下:

configurable為false,看來object.defineproperty真的不行了,而mdn上也說重定義陣列的length屬性在不同瀏覽器上表現也是不一致的,所以還是老老實實攔截splice,push等方法,要麼就等es6的proxy才可以做到了。

那麼陣列的下標可以使用defineproperty攔截嗎? 答案:是可以的。

那麼vue也是是對待普通物件一樣對陣列所有下標進行了攔截嗎? 答案:是否定的。

所以像這樣:

this.arr[0] = 1;
完全不行的。

那麼為啥不直接遍歷陣列然後攔截陣列的下標尼,我大概想了一下答案:

效能的考慮,陣列可能很大,一次性都對下標進行攔截,會有效能影響;陣列可能執行時變化很大,增刪頻繁。

[2019.01.25]其實是因為用object.defineproperty方法攔截下標的話會讓陣列進入字典模式,效率會極其低下,參考文章最後一段

還有沒有其他原因尼,這個還有待學習,但是看到原始碼其中是這樣收集陣列的依賴的:

/**

* collect dependencies on array elements when the array is touched, since

* we cannot intercept array element access like property getters.

*/function dependarray (value: array)

}}

遞迴收集陣列的依賴了,所有子陣列的變化也會觸發當前觀察者,這是個值得注意的地方。

所以我們可以再看新增乙個元素的時候:

function set (target: array| object, key: any, val: any): any
最終會讓observer的dep屬性去通知更新。

observer物件的作用可以讓乙個普通的物件變成"reactive",而dep則是充當最終的發布者角色。

當dep的notify方法調起時,便遍歷subs(訂閱者陣列就是array)呼叫訂閱者的update方法。

watcher的update方法調起,便把watcher壓入schedule佇列中,等待nexttick非同步執行,當然我們可以使用同步模式,直接執行watcher的run方法方便我們除錯。

vue中主要有兩種型別的watcher,一種是render watcher,另外一種是user watcher;

user watcher是通過vm.$watch 或者 options中的watch屬性定義的。

render watcher又是啥尼,看了一下initrender()方法,追蹤一下呼叫過程,來到vue.prototype._mount方法,可以看到:

vm._watcher = new watcher(vm, () => , noop)
這個就應該是render watcher了;

我們定義在options中的watch物件是在initstate方法中初始化,而initstate又比initrender先呼叫,所以元件中user watcher肯定比render watcher優先順序高(user watcher的id比render watcher小);

但是我們在mounted生命週期中使用vm.$watch定義的watcher就不一定了(個人推測),因為render watcher已經建立。

一般訂閱者模式都是一對多的關係(乙個發布者對應多個訂閱者),但是在這裡dep和watcher是多對多的關係,所以就有;

乙個watcher可以偵測多個屬性的變化(在render的時候,renderwatcher就收集了我們在模板裡面所使用的各種屬性的依賴,所以當我們修改模板裡面任意乙個變數時都會觸發renderwatcher重新render)

dep可以被多個watcher收集(例如我們可以定義多個vm.$watch同乙個屬性,當屬性變化時就可以觸發多個watcher)

另外props定義的屬性預設是不會偵測的(但是如果props有預設值,也是會呼叫observe),因為props的屬性都是由父元件傳遞給子元件,當props屬性修改時,父元件會先自己重新render,也會導致子元件render,然後開始diff流程。

在render watcher中wachter.run方法會調起vm._render()方法,這樣情況下我們在模板中訪問的屬性例如a.b這樣,會在物件的getter中把render watcher新增到訂閱者列表中。

get: function reactivegetter () 

if (array.isarray(value))

}return value

}

所以以後我們改動相關的屬性時,物件的setter自動會通知到render watcher讓dom結構更新。

好了,基本結束,如有錯漏,望指正。

Vue原始碼分析

在開始原始碼分析工作之前,我們在當前篇章做好相應的準備工作,以便更好地展開分析。將原始碼fork到自己的github倉庫中 git clone 自己的github vue 位址 dist 打包之後的結果 examples 示例 src compiler 編譯相關 core vue 核心庫 compo...

VUE原始碼分析之eventBus原理

vue中eventbus可以用來進行任何元件之間的通訊,我們可以把eventbus當成乙個管道,這個管道兩端可以接好多元件,兩端的任何乙個元件都可以進行通訊。其實這個管道就是vue例項,例項中的 on,off,emit方法來實現此功能。還是老樣子,先通過簡單例子看看eventbus怎麼用。我們例項化...

Vue原始碼分析(流程分析)

使用步驟 1.編寫 頁面 模板 1.直接在html標籤中寫 2.使用template 3.使用單檔案 2.建立vue例項 1.在vue 的建構函式中 data,methods,computer,watcher,props,3.將vue掛載到頁面中 mount 資料驅動模型 vue執行流程 1.獲得模...