摘要:中的觀察者模式觀察者模式一般包含發布者和訂閱者兩種角色顧名思義發布者負責發布消息,訂閱者通過訂閱消息響應動作了。中主要有兩種類型的,一種是另外一種是是通過或者中的屬性定義的。結束好了,基本結束,如有錯漏,望指正。
碎碎念
四月份真是慵懶無比的一個月份,看著手頭上沒啥事干,只好翻翻代碼啥的,看了一會Vue的源碼,忽而有點感悟,于是便記錄一下。
Vue中的觀察者模式觀察者模式一般包含發布者(Publisher)和訂閱者(Subscriber)兩種角色;顧名思義發布者負責發布消息,訂閱者通過訂閱消息響應動作了。
回到Vue中,在Vue源碼core/oberver目錄下分析代碼可以知道有三個類分別是Oberver,Watcher和Dep;那這三個類中誰是Publisher,誰是Subscriber尼?
觀察者,這個觀察者究竟觀察什么的尼?
還是用最簡單粗暴的方式,目錄搜索一下哪里用到這個類,步步追尋,大致是這樣一個調用過程。
initState()-->observe(data)-->new Observer()
基本上Vue在我們的data對象上都會定義一個__ob__屬性指向新創建的Observer對象,就像這樣子:
{ a: { b: { d: 1 __ob__: [Observer Object] } c: { e: 1, f: 2, g: 3 } //也是有__ob__屬性的 __ob__: [Observer Object] } __ob__: [Observer Object] }
這里可以知道其實對象或者數組里面Vue都會幫你添加一個__ob__屬性,但是這個__ob__屬性或者這個Observer對象究竟是干嘛用的尼?
先舉個栗子:
在模板里面我們遍歷數組內容,很明顯數組有多少元素就會輸出多少個li;那么我們數組元素增加和刪除的時候怎么通知到組件去重新渲染尼?
恩,答案就是通過這個__ob__屬性。
好,直接上代碼:
function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function ) { const dep = new Dep() //1. 為屬性創建一個發布者 ... 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步相當重要,如果沒有第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 enumerable: false value: 0 writable: true }
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) { 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) } } }
遞歸收集數組的依賴了,所有子數組的變化也會觸發當前觀察者,這是個值得注意的地方。
所以我們可以再看添加一個元素的時候:
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則是充當最終的發布者角色。
Dep當Dep的notify方法調起時,便遍歷subs(訂閱者數組就是Array
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, () => { vm._update(vm._render(), hydrating) }, 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 () { 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 }
所以以后我們改動相關的屬性時,對象的setter自動會通知到Render Watcher讓Dom結構更新。
結束好了,基本結束,如有錯漏,望指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82459.html
摘要:所以,我們是不是應該寫一個消息訂閱器呢這樣的話,一觸發方法,我們就發一個通知出來,然后,訂閱這個消息的,就會怎樣。。。截止到現在,在我們只考慮最簡單情況下。。關于的新文章行代碼,理解和分析的響應式架構 本文能幫你做什么?。。好奇vue雙向綁定的同學,可以部分緩解好奇心還可以幫你了解如何實現$watch 前情回顧 我之前寫了一篇沒什么干貨的文章。。并且刨了一個大坑。。今天。。打算來填一天...
摘要:分享前啰嗦我之前介紹過如何實現和。我們采用用最精簡的代碼,還原響應式架構實現以前寫的那篇源碼分析之如何實現和可以作為本次分享的參考。到現在為止,我們再看那張圖是不是就清楚很多了總結我非常喜歡,以上代碼為了好展示,都采用最簡單的方式呈現。 分享前啰嗦 我之前介紹過vue1.0如何實現observer和watcher。本想繼續寫下去,可是vue2.0橫空出世..所以 直接看vue2.0吧...
摘要:巴拉巴拉省略大法,去除無關代碼巴拉巴拉省略大法,去除無關代碼核心就這一句話。文章鏈接源碼分析系列源碼分析系列之環境搭建源碼分析系列之入口文件分析源碼分析系列之響應式數據一 前言 接著上一篇的初始化部分,我們細看initData中做了什么。 正文 initData function initData (vm: Component) { let data = vm.$options.d...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理依賴收集源碼版之引用數據類型上 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于...
摘要:所以最近攻讀了其源碼的一部分,先把雙向數據綁定這一塊的內容給整理一下,也算是一種學習的反芻。設計思想觀察者模式的雙向數據綁定的設計思想為觀察者模式,為了方便,下文中將被觀察的對象稱為觀察者,將觀察者對象觸發更新的稱為訂閱者。 雖然工作中一直使用Vue作為基礎庫,但是對于其實現機理僅限于道聽途說,這樣對長期的技術發展很不利。所以最近攻讀了其源碼的一部分,先把雙向數據綁定這一塊的內容給整理...
閱讀 2579·2021-08-20 09:38
閱讀 1360·2019-08-30 15:43
閱讀 597·2019-08-29 17:13
閱讀 1607·2019-08-29 14:01
閱讀 1319·2019-08-29 13:29
閱讀 2327·2019-08-23 18:29
閱讀 2051·2019-08-23 17:51
閱讀 1920·2019-08-23 17:16