專註 Vue 源碼分享,為了方便大家理解,分為了白話版和 源碼版,白話版可以輕鬆理解工作原理和設計思想,源碼版可以更清楚內部操作和 Vue的美,喜歡我就關注我的公眾號,公眾號的文章,排版更好看
如果你覺得排版難看,請點擊下面鏈接 或者 關注公眾號
如果對依賴收集完全沒有概念的同學,可以先看我這篇 【Vue原理】響應式原理 - 白話版
依賴收集,主要是為了解決一個問題,什麼問題呢?
首先,我們都知道,Vue 的數據是響應式更新的,一旦數據改變了,那麼相應使用到 數據的地方也會跟著改變。
那麼問題來了,數據改變的時候,Vue 怎麼知道,去讓那些使用到數據的地方也改變呢?
這就是依賴收集要解決的問題!
他是怎麼解決的?簡單說就是把依賴了數據的地方,給集中收集起來以便變化後通知!我們今天來看源碼的流程
首先,響應式更新,分為兩步,依賴收集和依賴更新
今天講的是依賴收集,如何去收集 使用了數據的地方
依賴收集,又分為兩個流程
1、數據初始化流程
2、依賴收集流程
當前篇,先以基本數據類型為例講解,因為 基本數據和 引用數據 在處理上有很大的不同,引用類型需要理解的東西更多更複雜,所以需要循序漸進,分兩篇描述
首先,在實例初始化的時候,需要對數據進行響應式處理,也就是給每個屬性都使用 Object.defineProperty 處理
處理的流程是怎麼樣的呢?
1、實例初始化中,調用 initState 處理部分選項數據,initData 用於處理選項 data
Vue.prototype._init=function(){ ... initState(this) ...
}
function initState(vm) {
var opts = vm.$options;
... props,computed,watch 等選項處理
if (opts.data) { initData(vm); } };
2、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]); } };
3、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 } } })
頁面模板
<div>{{name}}</div>
頁面引用了數據 name,name 需要保存 頁面的watcher,以便於 name 變化時,通知 頁面watcher 更新
1、頁面渲染函數
with(this){ return _c(div,{},[name]) }
2、讀取 name
渲染函數執行,上下文對象綁定為 實例,於是name讀取到實例上的 name
3、保存 watcher
name 被讀取,自然走到 Object.defineProperty.get 方法上,從這裡開始收集 watcher
先來觀察下 defineReactive 中省略的 get 的源碼
var dep = new Dep(); var val = obj[key]
Object.defineProperty(obj, key, {
get() { if (Dep.target) { // 收集依賴 dep.addSub(Dep.target) } return val } }); }
哈哈哈,這裡就有意思了,這段代碼就是 依賴收集的核心,主要是三個點
1、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 在 依賴收集中只起到被收集的作用,所以不會在這裡詳細解釋
2、Dep
Dep 是一個構造函數,用於創建實例,並帶有很多方法
實例會包含一個屬性 subs 數組,用於存儲不同數據 【收集的依賴】
看下dep的構造函數
var Dep = function Dep() {
// 保存watcher 的數組 this.subs = []; };
3、dep.addSub
原型上的方法,作用是往 dep.subs 存儲器中 中直接添加 watcher
Dep.prototype.addSub = function(sub) { this.subs.push(sub); };
所以,【dep.addSub(Dep.target) 】就會直接添加當前 watcher
於是,收集流程大概是這樣
1、頁面的渲染函數執行, name 被讀取
2、觸發 name的 Object.defineProperty.get 方法
3、於是,頁面的 watcher 就會被收集到 name 專屬的閉包dep 的 subs 中
為什麼需要依賴收集,之前也已經說過,是為了變化時通知 那些使用過數據的地方
就好比,你去商店買東西,東西還沒有發售,於是你把你的電話給老闆,老闆把你的記在電話本上。當東西發售時,就會打你的電話通知你,讓你來領取(完成更新)。
其中涉及的幾個步驟,按上面的例子來轉化一下
1、你買東西,就是你要使用數據 name
2、你把電話給老闆,電話就是你的 watcher,用於通知
3、老闆記下電話在電話本,就是把 watcher 保存在 subs 中。
4、剩下的步驟屬於依賴更新
推薦閱讀:
TAG:Vue.js | 前端開發 | 前端框架 |