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