【Vue原理】依賴收集 - 源碼版之基本數據類型

專註 Vue 源碼分享,為了方便大家理解,分為了白話版和 源碼版,白話版可以輕鬆理解工作原理和設計思想,源碼版可以更清楚內部操作和 Vue的美,喜歡我就關注我的公眾號,公眾號的文章,排版更好看

如果你覺得排版難看,請點擊下面鏈接 或者 關注公眾號

【Vue原理】依賴收集 - 源碼版之基本數據類型?

mp.weixin.qq.com圖標

如果對依賴收集完全沒有概念的同學,可以先看我這篇 【Vue原理】響應式原理 - 白話版

依賴收集,主要是為了解決一個問題,什麼問題呢?

首先,我們都知道,Vue 的數據是響應式更新的,一旦數據改變了,那麼相應使用到 數據的地方也會跟著改變。

那麼問題來了,數據改變的時候,Vue 怎麼知道,去讓那些使用到數據的地方也改變呢?

這就是依賴收集要解決的問題!

他是怎麼解決的?簡單說就是把依賴了數據的地方,給集中收集起來以便變化後通知!我們今天來看源碼的流程

首先,響應式更新,分為兩步,依賴收集和依賴更新

今天講的是依賴收集,如何去收集 使用了數據的地方

依賴收集,又分為兩個流程

1、數據初始化流程

2、依賴收集流程

當前篇,先以基本數據類型為例講解,因為 基本數據和 引用數據 在處理上有很大的不同,引用類型需要理解的東西更多更複雜,所以需要循序漸進,分兩篇描述

數據初始化流程

首先,在實例初始化的時候,需要對數據進行響應式處理,也就是給每個屬性都使用 Object.defineProperty 處理

處理的流程是怎麼樣的呢?

1、實例初始化中,調用 initState 處理部分選項數據,initData 用於處理選項 data

Vue.prototype._init=function(){
...
initState(this)
...

}

function initState(vm) {

var opts = vm.$options;

... propscomputedwatch 等選項處理

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 的源碼

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
}
});
}

哈哈哈,這裡就有意思了,這段代碼就是 依賴收集的核心,主要是三個點

  1. Dep.target
  2. Dep
  3. dep.addSub

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 | 前端開發 | 前端框架 |