寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】數組
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧閉包
上一篇,咱們已經分析過了 基礎數據類型的 依賴收集學習
這一篇內容是針對 引用數據類型的數據的 依賴收集分析,由於引用類型數據要複雜些,必須分開寫prototype
文章很長,高能預警,作好準備耐下心好,確定仍是有點收穫的code
可是兩個類型的數據的處理,又有不少重複的地方,因此打算只寫一些差別性的地方就行了,不然顯得廢話不少server
兩個步驟,都有不一樣的地方對象
一、數據初始化blog
二、依賴收集
若是數據類型是引用類型,須要對數據進行額外的處理。
處理又分了 對象 和 數組 兩種,會分開來說
1對象
一、遍歷對象的每一個屬性,一樣設置響應式,假設屬性都是基本類型,處理流程跟上一篇同樣
二、每一個數據對象會增長一個 ob 屬性
好比設置一個 child 的數據對象
下圖,你能夠看到 child 對象處理以後添加了一個 ob 屬性
ob_ 屬性有什麼用啊?
你能夠觀察到,ob 有一個 dep 屬性,這個 dep 是否是有點屬性,是的,在上一篇基礎數據類型中講過
那麼這個 ob 屬性有什麼用啊?
你能夠觀察到,ob 有一個 dep 屬性,這個 dep 是否是有點屬性,是的,在上一篇基礎數據類型中講過
dep 正是存儲依賴的地方
好比 頁面引用了 數據child,watch 引用了數據child,那麼child 就會把這個兩個保存在 dep.subs 中
dep.subs = [ 頁面-watcher,watch-watcher ]
可是,在上一篇基礎類型種, dep 是做爲閉包存在的啊,並非保存在什麼【ob.dep】 中啊
沒錯,這就是 引用類型 和 基礎類型的區別了
基礎數據類型,只使用 【閉包dep】 來存儲依賴
引用數據類型,使用 【閉包dep】 和 【 ob.dep】 兩種來存儲依賴
什麼?你說閉包dep 在哪裏?好吧,在 defineReactive 的源碼中,你去看看這個方法的源碼,下面有
那麼,爲何,引用類型須要 使用__ob__.dep 存儲依賴呢?
首先,明確一點,存儲依賴,是爲了數據變化時通知依賴,因此 ob.dep 也是爲了變化後的通知
閉包 dep 只存在 defineReactive 中,其餘地方沒法使用到,因此須要保存另一個在其餘地方使用
在其餘什麼地方會使用呢?
在Vue掛載原型上的方法 set 和 del 中,源碼以下
function set(target, key, val) { var ob = (target).__ob__; // 通知依賴更新 ob.dep.notify(); } Vue.prototype.$set = set;
function del(target, key) { var ob = (target).__ob__; delete target[key]; if (!ob) return // 通知依賴更新 ob.dep.notify(); } Vue.prototype.$delete = del;
這兩個方法,你們應該都用過,爲了給對象動態 添加屬性和 刪除屬性
可是若是直接添加屬性或者刪除屬性,Vue 是監聽不到的,好比下面這樣
child.xxxx=1 delete child.xxxx
因此必需要經過 Vue 包裝過的方法 set 和 del 來操做
在 set 和 del 執行完,是須要通知依賴更新的,可是我怎麼通知?
此時,【ob.dep】 就發揮做用了!就由於依賴多收集了一份在 ob.dep 中
使用就是上面一句話,通知更新
ob.dep.notify();
二、數組
一、須要遍歷數組,可能數組是對象數組,以下面
[{name:1},{name:888}]
遍歷時,若是遇到子項是對象的,會跟上面解析對象同樣操做
二、給數組保存一個 ob 屬性
好比設置一個 arr 數組
看到 arr數組 加多了一個 ob 屬性
其實這個 ob 屬性 和 上一段講對象 的做用是差很少的,這裏也只是說 ob.dep
數組中的 ob.dep 存儲的也是依賴,給誰用呢?
給 Vue 封裝的數組方法使用,要知道要想數組變化也被監聽到,是必須使用Vue封裝的數組方法的,不然沒法實時更新
這裏舉重寫方法之一 push,其餘的還有 splice 等,Vue 官方文檔已經有過說明
var original = Array.prototype.push; Array.prototype.push = function() { var args = [], len = arguments.length; // 複製 傳給 push 等方法的參數 while (len--) args[len] = arguments[len]; // 執行 原方法 var result = original.apply(this, args); var ob = this.__ob__; // notify change ob.dep.notify(); return resul }
看到在執行完 數組方法以後,一樣須要通知依賴更新,也就是通知 ob.dep 中收集的依賴去更新
如今,咱們知道了,響應式數據對 引用類型作了什麼額外的處理,主要是加了一個 ob 屬性
咱們已經知道了 ob 有什麼用,如今看看源碼是怎麼添加 ob 的
// 初始化Vue組件的數據 function initData(vm) { var data = vm.$options.data; data = vm._data = typeof data === 'function' ? data.call(vm, vm) : data || {}; ....遍歷 data 數據對象的key ,重名檢測,合規檢測 observe(data, true); } function observe(value) { if (Array.isArray(value) || typeof value == "object") { ob = new Observer(value); } return ob }
function Observer(value) { // 給對象生成依賴保存器 this.dep = new Dep(); // 給 每個對象 添加一個 __ob__ 屬性,值爲 Observer 實例 value.__ob__ = this if (Array.isArray(value)) { // 遍歷數組,每一項都須要經過 observe 處理,若是是對象就添加 __ob__ for (var i = 0, l =value.length; i < l; i++) { observe(value[i]); } } else { var keys = Object.keys(value); // 給對象的每個屬性設置響應式 for (var i = 0; i < keys.length; i++) { defineReactive(value, keys[i]); } } };
源碼的流程跟上一篇差很少,只是處理引用數據類型會增長多幾行源碼的額外處理
咱們以前只說了一種對象數據類型,好比下面這樣
若是會嵌套多層對象呢?好比這樣,會怎麼處理
沒錯,Vue 會遞歸處理,當遍歷屬性,使用 defineReactive 處理時,遞歸調用 observe 處理(源碼標紅加粗)
若是值是對象,那麼一樣給 值加多一個 ob
若是不是,那麼正常往下走,設置響應式
源碼以下
function defineReactive(obj, key, value) { // dep 用於中收集全部 依賴個人 東西 var dep = new Dep(); var val = obj[key] // 返回的 childOb 是一個 Observer 實例 // 若是值是一個對象,須要遞歸遍歷對象 var childOb = observe(val); Object.defineProperty(obj, key, { get() {...依賴收集跟初始化無關,下面會講}, set() { .... } }); }
畫一個流程圖,僅供參考
哈哈哈,上面寫得好長啊,是有點,可是沒辦法,想說詳細點啊,好吧,還有一段,可是比較短一些哈哈哈,反正看完的人,我jio 得很厲害了,答應我,若是你仔細看完了,評論一下好嗎,讓我知道有人仔細看了
收集流程,就是重點關注 Object.defineProperty 設置的 get 方法了
跟 基礎類型數據 對比,引用類型的 收集方法也只是多了幾行處理,差別在兩行代碼
childOb.dep.depend,被我 簡單化爲 childOb.dep.addSub(Dep.target) dependArray(value) 能夠先看下源碼,以下
function defineReactive(obj, key, value) { var dep = new Dep(); var val = obj[key] var childOb = observe(val); Object.defineProperty(obj, key, { get() { var value = val if (Dep.target) { // 收集依賴進 dep.subs dep.addSub(Dep.target); // 若是值是一個對象,Observer 實例的 dep 也收集一遍依賴 if (childOb) { childOb.dep.addSub(Dep.target) if (Array.isArray(value)) { dependArray(value); } } } return value } }); }
上面的源碼,混雜了 對象和 數組的處理,咱們分開說
一、對象
在數據初始化的流程中,咱們已經知道值是對象的話,會存儲多一份依賴在 ob.dep 中
就只有一句話
childOb.dep.depend();
數組還有另一個處理,就是
dependArray(value);
看下源碼,以下
function dependArray(value) { for (var i = 0, l = value.length; i < l; i++) { var e = value[i]; // 只有子項是對象的時候,收集依賴進 dep.subs e && e.__ob__ && e.__ob__.dep.addSub(Dep.target); // 若是子項仍是 數組,那就繼續遞歸遍歷 if (Array.isArray(e)) { dependArray(e); } } }
顯然,是爲了防止數組裏面有對象,從而須要給 數組子項對象也保存一份
你確定會問,爲何子項對象也要保存一份依賴?
一、頁面依賴了數組,數組子項變化了,是否是頁面也須要更新?可是子項內部變化怎麼通知頁面更新?因此須要給子項對象也保存一份依賴?
二、數組子項數組變化,就是對象增刪屬性,必須用到Vue封裝方法 set 和 del,set 和 del 會通知依賴更新,因此子項對象也要保存
看個栗子
頁面模板
看到數組的數據,就存在兩個 ob
到這裏,就能夠很清楚,引用類型和 基礎類型的處理差別了
一、引用類型會多添加一個 __ob__屬性,其中包含 dep,用於存儲 收集到的依賴
二、對象使用 ob.dep,做用在 Vue 自定義的方法 set 和 del 中
三、數組使用 ob.dep,做用在 Vue 重寫的數組方法 push 等中
終於寫完了,真的好長,可是我以爲值得了