摘要:中的觀察者模式觀察者模式一般包含發(fā)布者和訂閱者兩種角色顧名思義發(fā)布者負責(zé)發(fā)布消息,訂閱者通過訂閱消息響應(yīng)動作了。中主要有兩種類型的,一種是另外一種是是通過或者中的屬性定義的。結(jié)束好了,基本結(jié)束,如有錯漏,望指正。
碎碎念
四月份真是慵懶無比的一個月份,看著手頭上沒啥事干,只好翻翻代碼啥的,看了一會Vue的源碼,忽而有點感悟,于是便記錄一下。
Vue中的觀察者模式觀察者模式一般包含發(fā)布者(Publisher)和訂閱者(Subscriber)兩種角色;顧名思義發(fā)布者負責(zé)發(fā)布消息,訂閱者通過訂閱消息響應(yīng)動作了。
回到Vue中,在Vue源碼core/oberver目錄下分析代碼可以知道有三個類分別是Oberver,Watcher和Dep;那這三個類中誰是Publisher,誰是Subscriber尼?
觀察者,這個觀察者究竟觀察什么的尼?
還是用最簡單粗暴的方式,目錄搜索一下哪里用到這個類,步步追尋,大致是這樣一個調(diào)用過程。
initState()-->observe(data)-->new Observer()
基本上Vue在我們的data對象上都會定義一個__ob__屬性指向新創(chuàng)建的Observer對象,就像這樣子:
{ a: { b: { d: 1 __ob__: [Observer Object] } c: { e: 1, f: 2, g: 3 } //也是有__ob__屬性的 __ob__: [Observer Object] } __ob__: [Observer Object] }
這里可以知道其實對象或者數(shù)組里面Vue都會幫你添加一個__ob__屬性,但是這個__ob__屬性或者這個Observer對象究竟是干嘛用的尼?
先舉個栗子:
在模板里面我們遍歷數(shù)組內(nèi)容,很明顯數(shù)組有多少元素就會輸出多少個li;那么我們數(shù)組元素增加和刪除的時候怎么通知到組件去重新渲染尼?
恩,答案就是通過這個__ob__屬性。
好,直接上代碼:
function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function ) { const dep = new Dep() //1. 為屬性創(chuàng)建一個發(fā)布者 ... let childOb = observe(val) //2. 獲取屬性值的__ob__屬性 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { ... if (Dep.target) { dep.depend() //3. 添加訂閱者 if (childOb) { childOb.dep.depend() //4. 也為屬性值添加同樣的訂閱者 } if (Array.isArray(value)) { dependArray(value) // 同上 } } return value }, set: function reactiveSetter (newVal) { ... childOb = observe(newVal) dep.notify() } }) }
第4步相當(dāng)重要,如果沒有第4步,我們添加或者刪除元素,剛才那個組件是不會重新渲染的;我們一般情況下都會想到去攔截屬性的get和set方法,在get的方法我們可以收集訂閱者,set的方法我們簡單的判斷舊的值和新的值是否相等我們就可以通知訂閱者去更新;但是對于引用的值(類似Object或者Array)這樣就不行了,我們得讓他們內(nèi)容發(fā)生變化(主要是增加刪除內(nèi)容,對象增加一個屬性時候)的時候也要通知訂閱者去更新,所以__ob__上的dep屬性主要用于監(jiān)控對象屬性增加和刪減而第1步所創(chuàng)建的dep用于監(jiān)控屬性值的更新。
但在這里的例子也導(dǎo)致另外一個行為,我們剛才在例子中很明顯并沒有實際用到數(shù)組的內(nèi)容,然而在for循環(huán)的過程中,也就等同于我們遍歷對象所有內(nèi)容,Vue就會認為我們會“關(guān)心”這些內(nèi)容的變化,所以當(dāng)對象的內(nèi)容(假設(shè)這個對象里的元素也是對象,在某個子對象上增加或者刪除一個屬性)發(fā)生變化的時候也會觸發(fā)重新渲染;
還有的是Vue對數(shù)組的處理跟對象還是有挺大的不同,length是數(shù)組的一個很重要的屬性,無論數(shù)組增加元素或者刪除元素(通過splice,push等方法操作)length的值必定會更新,那么豈不是一勞永逸,不需要攔截splice,push等方法就可以知道數(shù)組的狀態(tài)更新,但是當(dāng)我試著在數(shù)組length屬性上用defineProperty攔截的時候,冒出了這樣的錯誤:
Uncaught TypeError: Cannot redefine property: length
不能重定義length屬性??再用Object.getOwnPropertyDescriptor(arr, "length")查看一下:
{ configurable: false enumerable: false value: 0 writable: true }
configurable為false,看來Object.defineProperty真的不行了,而MDN上也說重定義數(shù)組的length屬性在不同瀏覽器上表現(xiàn)也是不一致的,所以還是老老實實攔截splice,push等方法,要么就等ES6的Proxy才可以做到了。
那么數(shù)組的下標可以使用defineProperty攔截嗎? 答案:是可以的。
那么Vue也是是對待普通對象一樣對數(shù)組所有下標進行了攔截嗎? 答案:是否定的。
所以像這樣:
this.arr[0] = 1;
完全不行的。
那么為啥不直接遍歷數(shù)組然后攔截數(shù)組的下標尼,我大概想了一下答案:性能的考慮,數(shù)組可能很大,一次性都對下標進行攔截,會有性能影響;數(shù)組可能運行時變化很大,增刪頻繁。
[2019.01.25]其實是因為用Object.defineProperty方法攔截下標的話會讓數(shù)組進入字典模式,效率會極其低下,參考文章最后一段
還有沒有其他原因尼,這個還有待學(xué)習(xí),但是看到源碼其中是這樣收集數(shù)組的依賴的:
/** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ function dependArray (value: Array) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } }
遞歸收集數(shù)組的依賴了,所有子數(shù)組的變化也會觸發(fā)當(dāng)前觀察者,這是個值得注意的地方。
所以我們可以再看添加一個元素的時候:
function set (target: Array| Object, key: any, val: any): any { ... const ob = (target : any).__ob__ ... ... defineReactive(ob.value, key, val) ob.dep.notify() return val }
最終會讓Observer的dep屬性去通知更新。
Observer對象的作用可以讓一個普通的對象變成"Reactive",而Dep則是充當(dāng)最終的發(fā)布者角色。
Dep當(dāng)Dep的notify方法調(diào)起時,便遍歷subs(訂閱者數(shù)組就是Array
Watcher的update方法調(diào)起,便把Watcher壓入schedule隊列中,等待nextTick異步執(zhí)行,當(dāng)然我們可以使用同步模式,直接執(zhí)行Watcher的run方法方便我們調(diào)試。
Vue中主要有兩種類型的Watcher,一種是Render Watcher,另外一種是User Watcher;
User Watcher是通過vm.$watch 或者 options中的watch屬性定義的。
Render Watcher又是啥尼,看了一下initRender()方法,追蹤一下調(diào)用過程,來到Vue.prototype._mount方法,可以看到:
vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating) }, noop)
這個就應(yīng)該是Render Watcher了;
我們定義在options中的watch對象是在initState方法中初始化,而initState又比initRender先調(diào)用,所以組件中User Watcher肯定比Render Watcher優(yōu)先級高(User Watcher的id比Render Watcher?。?br>但是我們在mounted生命周期中使用vm.$watch定義的Watcher就不一定了(個人推測),因為Render Watcher已經(jīng)創(chuàng)建。
一般訂閱者模式都是一對多的關(guān)系(一個發(fā)布者對應(yīng)多個訂閱者),但是在這里Dep和Watcher是多對多的關(guān)系,所以就有;
一個Watcher可以偵測多個屬性的變化(在Render的時候,RenderWatcher就收集了我們在模板里面所使用的各種屬性的依賴,所以當(dāng)我們修改模板里面任意一個變量時都會觸發(fā)RenderWatcher重新Render)
Dep可以被多個Watcher收集(例如我們可以定義多個vm.$watch同一個屬性,當(dāng)屬性變化時就可以觸發(fā)多個Watcher)
另外Props定義的屬性默認是不會偵測的(但是如果Props有默認值,也是會調(diào)用Observe),因為Props的屬性都是由父組件傳遞給子組件,當(dāng)Props屬性修改時,父組件會先自己重新Render,也會導(dǎo)致子組件Render,然后開始Diff流程。
關(guān)于渲染時依賴收集在Render Watcher中Wachter.run方法會調(diào)起vm._render()方法,這樣情況下我們在模板中訪問的屬性例如a.b這樣,會在對象的getter中把Render Watcher添加到訂閱者列表中。
get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } } return value }
所以以后我們改動相關(guān)的屬性時,對象的setter自動會通知到Render Watcher讓Dom結(jié)構(gòu)更新。
結(jié)束好了,基本結(jié)束,如有錯漏,望指正。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/82459.html
摘要:所以,我們是不是應(yīng)該寫一個消息訂閱器呢這樣的話,一觸發(fā)方法,我們就發(fā)一個通知出來,然后,訂閱這個消息的,就會怎樣。。。截止到現(xiàn)在,在我們只考慮最簡單情況下。。關(guān)于的新文章行代碼,理解和分析的響應(yīng)式架構(gòu) 本文能幫你做什么?。。好奇vue雙向綁定的同學(xué),可以部分緩解好奇心還可以幫你了解如何實現(xiàn)$watch 前情回顧 我之前寫了一篇沒什么干貨的文章。。并且刨了一個大坑。。今天。。打算來填一天...
摘要:分享前啰嗦我之前介紹過如何實現(xiàn)和。我們采用用最精簡的代碼,還原響應(yīng)式架構(gòu)實現(xiàn)以前寫的那篇源碼分析之如何實現(xiàn)和可以作為本次分享的參考。到現(xiàn)在為止,我們再看那張圖是不是就清楚很多了總結(jié)我非常喜歡,以上代碼為了好展示,都采用最簡單的方式呈現(xiàn)。 分享前啰嗦 我之前介紹過vue1.0如何實現(xiàn)observer和watcher。本想繼續(xù)寫下去,可是vue2.0橫空出世..所以 直接看vue2.0吧...
摘要:巴拉巴拉省略大法,去除無關(guān)代碼巴拉巴拉省略大法,去除無關(guān)代碼核心就這一句話。文章鏈接源碼分析系列源碼分析系列之環(huán)境搭建源碼分析系列之入口文件分析源碼分析系列之響應(yīng)式數(shù)據(jù)一 前言 接著上一篇的初始化部分,我們細看initData中做了什么。 正文 initData function initData (vm: Component) { let data = vm.$options.d...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理依賴收集源碼版之引用數(shù)據(jù)類型上 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于...
摘要:所以最近攻讀了其源碼的一部分,先把雙向數(shù)據(jù)綁定這一塊的內(nèi)容給整理一下,也算是一種學(xué)習(xí)的反芻。設(shè)計思想觀察者模式的雙向數(shù)據(jù)綁定的設(shè)計思想為觀察者模式,為了方便,下文中將被觀察的對象稱為觀察者,將觀察者對象觸發(fā)更新的稱為訂閱者。 雖然工作中一直使用Vue作為基礎(chǔ)庫,但是對于其實現(xiàn)機理僅限于道聽途說,這樣對長期的技術(shù)發(fā)展很不利。所以最近攻讀了其源碼的一部分,先把雙向數(shù)據(jù)綁定這一塊的內(nèi)容給整理...
閱讀 2675·2021-08-20 09:38
閱讀 1428·2019-08-30 15:43
閱讀 653·2019-08-29 17:13
閱讀 1670·2019-08-29 14:01
閱讀 1374·2019-08-29 13:29
閱讀 2396·2019-08-23 18:29
閱讀 2113·2019-08-23 17:51
閱讀 1993·2019-08-23 17:16