Vue2?Observer實例dep和閉包中dep區(qū)別詳解
start
此前學(xué)習(xí) Vue2 源碼。對 Vue 源碼中兩次出現(xiàn)的new Dep(),不清楚它們的區(qū)別,寫一個文章記錄一下。
Vue2 源碼中有兩處通過new Dep生成dep實例:
1. Observer實例上的dep
export class Observer {
value;
dep;
vmCount;
constructor(value) {
this.value = value;
/* Observer的實例上有一個 dep 屬性 */
this.dep = new Dep();
def(value, "__ob__", this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
}
2. 定義getter,setter中dep
export function defineReactive(obj, key, val, customSetter, shallown) {
/* 這個地方也定義了一個dep */
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable,
configurable,
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
},
set: function reactiveSetter(newVal) {
/* ... */
},
})
}
為了方便后續(xù)介紹,我對兩種dep的名稱做一下縮減。
1. Observer實例上的dep => Observer實例上的dep
2. 定義getter,setter中dep => 閉包中的dep
既然是 new Dep(),肯定是想要實現(xiàn)某些功能。想要弄懂這兩種 dep 的區(qū)別,先看看在源碼中它們?nèi)绾喂ぷ鞯摹?/p>
1. 依賴收集
在打包輸出的dist文件夾中,找到完整的 Vue.js 源碼,全局搜索一下dep.depend() (收集依賴的方法)。
僅三處做了依賴收集
// 第 1 種情況
// 當(dāng)觸發(fā)對象屬性 getter 的時候,`閉包中的dep`會收集依賴。
dep.depend()
// 第 2 種情況
// 當(dāng)觸發(fā)對象屬性 getter 的時候,若屬性值有更深的子層級。`Observer實例上的dep`會收集依賴。
childOb.dep.depend()
// 第 3 種情況
// 當(dāng)觸發(fā)對象屬性 getter的時候,數(shù)據(jù)有更深的子層級且子層級是數(shù)組類型。遞歸遍歷數(shù)組的每一項,若數(shù)組項上存在`Observer的實例`,則對應(yīng)的`Observer實例上的dep`會收集依賴。
function dependArray(value) {
for (var e = void 0, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
情況說明:
- 當(dāng)觸發(fā)對象屬性 getter 的時候,閉包中的dep會收集依賴。
- 當(dāng)觸發(fā)對象屬性 getter 的時候,若屬性值有更深的子層級。Observer實例上的dep會收集依賴。
當(dāng)觸發(fā)對象屬性 getter 的時候,數(shù)據(jù)有更深的子層級且子層級是數(shù)組類型。遞歸遍歷數(shù)組的每一項,若數(shù)組項上存在Observer的實例,則對應(yīng)的Observer實例上的dep會收集依賴。
寫到這里我有點疑惑,為什么數(shù)組的情況,還要額外處理?
- 因為數(shù)組中如果存儲的不是對象或數(shù)組,對應(yīng)數(shù)組項不會有Observer的實例;
- 因為數(shù)組中如果存儲的是對象或數(shù)組,對應(yīng)數(shù)組項身上就會有Observer的實例;
- (核心原因:數(shù)組的每一項并不是都會被設(shè)置 getter,所以這里需要遞歸處理一下數(shù)組)
舉個例子
寫一個 Html 頁面測試
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>lazy_tomato</title>
</head>
<body>
<div id="app">
<div>{{ c }}</div>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
b: '你好呀',
c: [{}],
d: {},
}
},
})
</script>
</body>
</html>
修改一下Vue源碼,加入打?。?/p>
if (Dep.target) {
dep.depend()
/* 這里 */
console.log('111')
if (childOb) {
childOb.dep.depend()
/* 這里 */
console.log('222')
if (Array.isArray(value)) {
dependArray(value)
}
}
}
function dependArray(value) {
for (var e = void 0, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (e && e.__ob__ && e.__ob__.dep) {
/* 這里 */
console.log('333')
}
if (Array.isArray(e)) {
dependArray(e)
}
}
}
實驗結(jié)果:
- 如果頁面沒有使用 data 中的數(shù)據(jù),那么三種情況的 dep.depend() 都不會觸發(fā)。(因為沒有觸發(fā) getter)
- 如果頁面僅僅使用到了 b,b 是簡單類型的數(shù)據(jù),沒有子層級, 1會觸發(fā), 2、3不觸發(fā);
- 如果頁面僅僅使用到了 c,c 是數(shù)組類型的數(shù)據(jù),而且數(shù)組中的項是對象,1、2、3會觸發(fā) 2不觸發(fā);
- 如果頁面僅僅使用到了 d,d 是對象類型的數(shù)據(jù),有子層級,1、2會觸發(fā), 3不觸發(fā);
結(jié)論: 由上面的結(jié)果,可以得到結(jié)論:
- 閉包中的dep 關(guān)注的是對象屬性;
- Observer實例上的dep,關(guān)注的是對象中屬性值是對象或者是數(shù)組的情況;
2. 通知更新
在打包輸出的dist文件夾中,找到完整的 Vue.js 源碼,全局搜索一下dep.notify()(通知更新的方法)。僅四處做了依賴收集。
// 第 1 種情況 // 當(dāng)觸發(fā)對象屬性 setter 的時候,`閉包中的dep`會通知更新。 dep.notify() // 還有三處,都是使用 `ob.dep.notify()` 的方式會通知更新。 // 場景分別為: // 1. 改寫數(shù)組的七個方法 // 2. set 方法 // 3. del 方法
結(jié)論:
- 閉包中的dep 用于由對象本身修改而觸發(fā) setter 函數(shù)導(dǎo)致閉包中的 Dep 通知所有的 Watcher 對象。
- Observer實例上的dep 則是在對象本身增刪屬性或者數(shù)組變化的時候被觸發(fā)的 Dep。
思考
看到上述的內(nèi)容,可以想到兩種 dep 其實是各司其職。
- 閉包中的 dep 用于管理對象本身修改而觸發(fā)的依賴。
- Observer 實例上的 dep 用于對象本身增刪屬性或者數(shù)組變化的時候被觸發(fā)的依賴。
它可以用于彌補 Object.defineProperty() 的缺陷。
- 對象屬性 新增或者刪除;
- 數(shù)組方法不支持響應(yīng)式;
- 通過數(shù)組索引修改數(shù)據(jù)項;
拓展一
如果把 Observer 中初始化 dep 的代碼注釋掉,那么:$set,$del,重寫的七種數(shù)組方法 都將失效。
class Observer {
constructor(value) {
// this.dep = new Dep(); // 正確的寫法
this.dep = { depend() {}, notify() {} }
/* ... */
}
}
拓展二
既然源碼中的 $set,$del,重寫的七種數(shù)組方法 ,通知更新,使用的是ob.dep.notify()。 那我們是否可以自己手動通知嗎?
理論是可行的,但是 Vue 源碼相對來說,做了更多特殊場景的考慮。所以用官方提供的 API 更可靠。
手動通知的案例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>lazy_tomato</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in list" :key="item">{{item}}</li>
</ul>
<h2 @click="foo">點擊我通過數(shù)組索引添加數(shù)據(jù)</h2>
<h2 @click="bar">點擊我手動更新通知依賴更新</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
list: [1, 2, 3],
}
},
methods: {
// 點擊我向數(shù)組中添加數(shù)據(jù)
foo() {
let length = this.list.length
this.list[length] = 'tomato' + length // 通過數(shù)組索引修改數(shù)據(jù),默認(rèn)是無法通知依賴更新的。
console.log(this.list)
},
// 點擊我手動更新通知更新
bar() {
this.list.__ob__.dep.notify()
},
},
})
</script>
</body>
</html>
效果圖:

end
- 本文就 Dep 的初始化和基礎(chǔ)的使用場景做了學(xué)習(xí)。
再總結(jié)一下:
- 閉包中的dep 用于由對象本身修改而觸發(fā) setter 函數(shù)導(dǎo)致閉包中的 Dep 通知所有的 Watcher 對象。
- Observer實例上的dep 則是在對象本身增刪屬性或者數(shù)組變化的時候被觸發(fā)的 Dep。
更多關(guān)于Vue2 Observer與dep閉包的資料請關(guān)注腳本之家其它相關(guān)文章!
- vue中__ob__:?Observer的踩坑記錄
- Vue之Dep和Observer的用法及說明
- vue3?圖片懶加載的兩種方式、IntersectionObserver和useIntersectionObserver實例詳解
- 關(guān)于Vue?"__ob__:Observer"屬性的解決方案詳析
- vue中關(guān)于_ob_:observer的處理方式
- Vue數(shù)組中出現(xiàn)__ob__:Observer無法取值問題的解決方法
- Vue響應(yīng)式原理Observer、Dep、Watcher理解
- vue中{__ob__: observer}對象轉(zhuǎn)化為數(shù)組進(jìn)行遍歷方式
相關(guān)文章
解決Element中el-date-picker組件不回填的情況
這篇文章主要介紹了解決Element中el-date-picker組件不回填的情況,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
vue中關(guān)于confirm確認(rèn)框的用法
這篇文章主要介紹了vue中關(guān)于confirm確認(rèn)框的用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08
Vue通過getAction的finally來最大程度避免影響主數(shù)據(jù)呈現(xiàn)問題
這篇文章主要介紹了Vue通過getAction的finally來最大程度避免影響主數(shù)據(jù)呈現(xiàn),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
TypeScript基本類型 typeof 和keyof案例詳解
這篇文章主要介紹了TypeScript基本類型 typeof 和keyof案例詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-10-10
webpack如何將vue3單頁面應(yīng)用改造成多頁面應(yīng)用
這篇文章主要介紹了webpack如何將vue3單頁面應(yīng)用改造成多頁面應(yīng)用,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05
解決iview多表頭動態(tài)更改列元素發(fā)生的錯誤的方法
這篇文章主要介紹了解決iview多表頭動態(tài)更改列元素發(fā)生的錯誤的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11

