寫文章不容易,點個讚唄兄弟
專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧
研究基於 Vue版本 【2.5.17】
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧數組
若是對依賴收集徹底沒有概念的同窗,能夠先看我這篇函數
依賴收集,主要是爲了解決一個問題,什麼問題呢?this
首先,咱們都知道,Vue 的數據是響應式更新的,一旦數據改變了,那麼相應使用到 數據的地方也會跟着改變。spa
那麼問題來了,數據改變的時候,Vue 怎麼知道,去讓那些使用到數據的地方也改變呢?prototype
這就是依賴收集要解決的問題!code
他是怎麼解決的?簡單說就是把依賴了數據的地方,給集中收集起來以便變化後通知!咱們今天來看源碼的流程server
首先,響應式更新,分爲兩步,依賴收集和依賴更新對象
今天講的是依賴收集,如何去收集 使用了數據的地方
依賴收集,又分爲兩個流程
一、數據初始化流程
二、依賴收集流程
當前篇,先以基本數據類型爲例講解,由於 基本數據和 引用數據 在處理上有很大的不一樣,引用類型須要理解的東西更多更復雜,因此須要按部就班,分兩篇描述
首先,在實例初始化的時候,須要對數據進行響應式處理,也就是給每一個屬性都使用 Object.defineProperty 處理
處理的流程是怎麼樣的呢?
一、實例初始化中,調用 initState 處理部分選項數據,initData 用於處理選項 data
Vue.prototype._init=function(){ ... initState(this) ... } function initState(vm) { var opts = vm.$options; ... props,computed,watch 等選項處理 if (opts.data) { initData(vm); } };
二、initData 遍歷 data,definedReactive 處理每一個屬性
function initData(vm) { var data = vm.$options.data; data = typeof data === 'function' ? data.call(vm, vm) : data || {}; // ... 遍歷 data 數據對象的key,重名檢測,合規檢測等代碼 new Observer(data); } function Observer(value) { var keys = Object.keys(value); // ...被省略的代碼 for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } };
三、definedReactive 給對象的屬性 經過 Object.defineProperty 設置響應式
function defineReactive(obj, key) { // dep 用於中收集全部 依賴個人 東西 var dep = new Dep(); var val = obj[key] Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { ...依賴收集,詳細源碼下個流程放出 }, set() { ....依賴更新,源碼下篇文章放出 } }); }
寫一個小例子來解析
new Vue({ el: document.getElementsByTagName("div")[0], data(){ return { name:11 } } })
頁面模板
頁面引用了數據 name,name 須要保存 頁面的watcher,以便於 name 變化時,通知 頁面watcher 更新
一、頁面渲染函數
with(this){ return _c('div',{},[name]) }
二、讀取 name
渲染函數執行,上下文對象綁定爲 實例,因而name讀取到實例上的 name
三、保存 watcher
name 被讀取,天然走到 Object.defineProperty.get 方法上,從這裏開始收集 watcher
先來觀察下 defineReactive 中省略的 get 的源碼
function defineReactive(obj, key) { var dep = new Dep(); var val = obj[key] Object.defineProperty(obj, key, { get() { if (Dep.target) { // 收集依賴 dep.addSub(Dep.target) } return val } }); }
哈哈哈,這裏就有意思了,這段代碼就是 依賴收集的核心,主要是三個點
Dep.target
Dep
dep.addSub
一、Dep.target
Dep.target 指向的是各類 watcher,watch的watcher,頁面的watcher 等等
Dep.target 是變化的,根據當前解析流程,不停地指向不一樣的 watcher (指向,其實就是直接賦值 ,以下)
Dep.target = 具體watcher
「固然沒有這麼簡單,就是表示一個意思而已」
簡單想,指向哪一個watcher,那麼就是那個 watcher 正在使用數據,數據就要收集這個watcher
你能夠先不用管 Dep.target 究竟是怎麼指向,你只用記住 在 watcher
好比當前頁面開始渲染時,Dep.target 會提早指向當前頁面的 watcher。
因而頁面渲染函數執行,並引用了數據 name 後,name 直接收集 Dep.target,就會收集到當前頁面的 watcher
watcher 有負責實例更新的功能,因此會被收集起來,數據變化時通知 watcher,就能夠調用 watcher 去更新了
watcher 在 依賴收集中只起到被收集的做用,因此不會在這裏詳細解釋
二、Dep
Dep 是一個構造函數,用於建立實例,並帶有不少方法
實例會包含一個屬性 subs 數組,用於存儲不一樣數據 【收集的依賴】
看下dep的構造函數
var Dep = function Dep() { // 保存watcher 的數組 this.subs = []; };
三、dep.addSub
原型上的方法,做用是往 dep.subs 存儲器中 中直接添加 watcher
Dep.prototype.addSub = function(sub) { this.subs.push(sub); };
因此,【dep.addSub(Dep.target) 】就會直接添加當前 watcher
因而,收集流程大概是這樣
一、頁面的渲染函數執行, name 被讀取
二、觸發 name的 Object.defineProperty.get 方法
三、因而,頁面的 watcher 就會被收集到 name 專屬的閉包dep 的 subs 中
爲何須要依賴收集,以前也已經說過,是爲了變化時通知 那些使用過數據的地方
就比如,你去商店買東西,東西尚未發售,因而你把你的電話給老闆,老闆把你的記在電話本上。當東西發售時,就會打你的電話通知你,讓你來領取(完成更新)。
其中涉及的幾個步驟,按上面的例子來轉化一下
一、你買東西,就是你要使用數據 name
二、你把電話給老闆,電話就是你的 watcher,用於通知
三、老闆記下電話在電話本,就是把 watcher 保存在 subs 中。
四、剩下的步驟屬於依賴更新