Vue.js 內部執行機制 (二) ---- 響應式系統的依賴收集追蹤原理
為什麼需要依賴收集?
1、在 Vue 中,我們可能更新了不用更新檢視的資料,如果沒有依賴收集,則也會呼叫更新檢視的 cb 函式,顯然這是不合理的
2、Vue 頁面中可能多處引用同一個 Vue 元件物件,更新響應式資料時,則應當更新多處檢視,這些都涉及依賴收集
首先的訂閱者 Dep 類
/** * 依賴收集類 */ class Dep { constructor() { /* 用來存放Watcher物件的陣列 */ this.subs = []; } /* 向subs陣列中插入新的Watcher物件 */ addSub(sub) { this.subs.push(sub); } /* 通知subs陣列中所有Watcher物件更新檢視 */ /* 引數val是我為了模擬更新頁面中資料設定的,本來沒有,刪掉即可 */ notify(val) { this.subs.forEach(sub => sub.update(val)); } }
在訂閱者Dep物件中
- 用 addSub 方法可以在目前的 Dep 物件中增加一個 Watcher 的訂閱操作;
- 用 notify 方法通知目前 Dep 物件的 subs 中的所有 Watcher 物件觸發更新操作
然後的觀察者Watcher
/** * 所有依賴觀察者類 */ class Watcher { /* id是我為了模擬更新頁面資料設定的,刪掉即可 */ constructor(id) { /* 在new一個Watcher物件時將該物件賦值給Dep.target,在get中會用到 */ Dep.target = this; this.id = id; console.log(Dep.target); } /* 檢視更新方法 */ /* val是我為了模擬更新頁面資料設定的,刪掉即可 */ update(val) { console.log(this.id); document.querySelector(this.id).innerHTML = val; console.log("檢視更新啦~"); } } /* 用來指定當前建立的Watcher將其新增至依賴收集物件中 */ Dep.target = null;
注意:響應式資料物件(data物件)中每個屬性都有一個Dep物件用來收集依賴,而每個呼叫該屬性的Vue元件都會建立一個新的Watcher物件,即都是一個新的依賴
最後的依賴收集
/** * 將屬性響應式化函式 * * @param {object} obj 響應式化物件 * @param {} key 響應式物件屬性 * @returns {} val 舊屬性值 * */ function defineReactive(obj, key, val) { /* 每個屬性都有一個dep物件 */ const dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, /* 可列舉 */ configurable: true, /* 可配置修改或刪除 */ get: function reactiveGetter() { /* 依賴採集 */ dep.addSub(Dep.target); return val; }, set: function reactiveSetter(newVal) { if (newVal === val) { return; } val = newVal; /* 更新屬性值時通知所有觀察者更新檢視 */ /* 引數是我為了模擬更新頁面資料設定的,刪掉即可 */ dep.notify(val); } }) }
/**
* Vue構造類
*/
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一個Watcher觀察者物件,這時候Dep.target會指向這個Watcher物件 */
/* 引數是我為了模擬更新頁面資料設定的,刪掉即可 */
new Watcher('.Test');
/* 在這裡模擬render的過程,為了觸發test屬性的get函式 */
console.log('render~', this._data.test);
/* 這裡頁面中存在兩處呼叫該屬性的Vue元件,則建立兩個Watcher */
new Watcher('.Vue');
console.log('render~', this._data.test);
}
}
完整程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue原始碼解析</title>
</head>
<body>
<div class="Test">I am test Vue.</div>
<div class="Vue">I am test Vue.</div>
</body>
<script>
/**
* 依賴收集類
*/
class Dep {
constructor() {
/* 用來存放Watcher物件的陣列 */
this.subs = [];
}
/* 向subs陣列中插入新的Watcher物件 */
addSub(sub) {
this.subs.push(sub);
}
/* 通知subs陣列中所有Watcher物件更新檢視 */
notify(val) {
this.subs.forEach(sub => sub.update(val));
}
}
/**
* 所有依賴觀察者類
*/
class Watcher {
/* id是我為了模擬更新頁面資料設定的,刪掉即可 */
constructor(id) {
/* 在new一個Watcher物件時將該物件賦值給Dep.target,在get中會用到 */
Dep.target = this;
this.id = id;
console.log(Dep.target);
}
/* 檢視更新方法 */
update(val) {
console.log(this.id);
document.querySelector(this.id).innerHTML = val;
console.log("檢視更新啦~");
}
}
/**
* 將屬性響應式化函式
*
* @param {object} obj 響應式化物件
* @param {} key 響應式物件屬性
* @returns {} val 舊屬性值
*
*/
function defineReactive(obj, key, val) {
/* 每個屬性都有一個dep物件 */
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
/* 可列舉 */
configurable: true,
/* 可配置修改或刪除 */
get: function reactiveGetter() {
/* 依賴採集 */
dep.addSub(Dep.target);
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) {
return;
}
val = newVal;
/* 更新屬性值時通知所有觀察者更新檢視 */
/* 引數是我為了模擬更新頁面資料設定的,刪掉即可 */
dep.notify(val);
}
})
}
/**
* 將物件所有屬性響應式化函式
*
* @param {object} value 響應式化物件
*
*/
function observer(value) {
if (!value || (typeof value !== 'object')) {
return;
}
/* 遍歷value中屬性定製set和get */
Object.keys(value).forEach(key => defineReactive(value, key, value[key]));
}
/**
* Vue構造類
*/
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一個Watcher觀察者物件,這時候Dep.target會指向這個Watcher物件 */
/* 引數是我為了模擬更新頁面資料設定的,刪掉即可 */
new Watcher('.Test');
/* 在這裡模擬render的過程,為了觸發test屬性的get函式 */
console.log('render~', this._data.test);
/* 這裡頁面中存在兩處呼叫該屬性的Vue元件,則建立兩個Watcher */
new Watcher('.Vue');
console.log('render~', this._data.test);
}
}
var test = new Vue({
data: {
test: 'I am test.'
}
});
Dep.target = null;
</script>
</html>
測試結果
總結
在遍歷 data 屬性設定 get 時,就會設定依賴收集方法, Dep物件即是依賴收集物件 ,所有的 Watcher 則是被收集依賴的觀察者
在資料變化時, set 會呼叫 Dep 物件的 notify 方法通知它內部所有的 Watcher 物件進行檢視更新。
參考文章
相關推薦
Vue.js 內部執行機制 (二) ---- 響應式系統的依賴收集追蹤原理
為什麼需要依賴收集? 1、在 Vue 中,我們可能更新了不用更新檢視的資料,如果沒有依賴收集,則也會呼叫更新檢視的 cb 函式,顯然這是不合理的 2、Vue 頁面中可能多處引用同一個 Vue 元件物件,更新響應式資料時,則應當更新多處檢視,這些都涉及依賴收集 首先
Vue.js 內部執行機制(六)---- 批量非同步更新策略及 nextTick 原理
之前我們學到了 Vue 更新資料是如何更新檢視的。 簡單回顧 資料更新(setter)-> 通知依賴收集集合(Dep) -> 呼叫所有觀察者(Watcher) -> 比對節點樹(patch) -> 檢視 在更新檢視這一步,使用非同步更新策略 為
vue.js的執行機制
引入vue.js,new Vue()幹了什麼呢? 1. 初始化 呼叫Vue原型上的_init()進行初始化,會初始化vue的生命週期,props,data,methods,computed,watch等,最重要的是利用Object.definedPropty
vue響應式系統的依賴收集追蹤原理
為什麼要依賴收集? 我先舉一個例子 我們現在有一個Vue物件 1 new Vue({ 2 template: 3 `<div> 4 <span>{{ text1 }}</span&
從template到DOM(Vue.js原始碼角度看內部執行機制)
寫在前面 這篇文章算是對最近寫的一系列Vue.js原始碼的文章(https://github.com/answershuto/learnVue)的總結吧,在閱讀原始碼的過程中也確實受益匪淺,希望自己的這些產出也會對同樣想要學習Vue.js原始碼的小夥伴有所幫助。之前這篇文章同樣在我司(大搜車)的
Vue.js原始碼解析(九)【從template到DOM(Vue.js原始碼角度看內部執行機制)】
從new一個Vue物件開始 let vm = new Vue({ el: '#app', /*some options*/ }); 很多同學好奇,在new一個Vue物件的時候,內部究竟發生了什麼? 究竟Vue.js是如何將data中的資
Vue學習之原始碼分析--從template到DOM(Vue.js原始碼角度看內部執行機制)(九)
從new一個Vue物件開始 let vm = new Vue({ el: '#app', /*some options*/ }); 很多同學好奇,在new一個Vue物件的時候,內部究竟發生了什麼? 究竟Vue.js是如何將data中的資
【JS】JavaScript引擎的內部執行機制
under scrip str tro blog rip 回調函數 ron span 近期在復習JavaScript,看到setTimeout函數時。想起曾經剛學時,在一本書上看過setTimeout()裏的回調函數執行的間隔時間
js基礎總結(二)js的執行機制
執行結果首先全部輸出first,然後全部輸出second。 再來看一道題: 應該是依次彈出4444 這裡考察的都是JS的執行機制。事件click, focus等等,定時器setTimeout和setInterval,ajax都會觸發非同步,屬於非同步任務。js是單執行緒的一個時
vue.js實現數據動態響應(Vue.set的應用)
屬性 點擊 屬性。 沒有 log utf-8 創建 http for 在vue裏面,我們操作最多的就是各種數據,在jquery裏面,我們習慣通過下標定向找到數據,然後重新賦值 比如var a[0]=111;(希望上家公司原諒菜鳥的我寫了不少這樣的代碼??) 下面上代碼
Vue 及框架響應式系統原理
dev 方法 writable 技術分享 構造函數 問題 color 子節點 跨平臺 個人bolg地址 全局概覽 Vue運行內部運行機制 總覽圖: 初始化及掛載 在 new Vue()之後。 Vue 會調用 _init 函數進行初始化,也就是這裏的 init 過程,它會初
Vue.js學習筆記(二)
head 改變 vue ntb con UNC 關註 tle element Vue.js不支持IE8及以下的版本,因為vue使用了IE8無法模擬的ECMAScript5的特性,它支持所有兼容ECMAScript5的瀏覽器。 1 <!DOCTYPE html>
vue.js學習筆記(二)--指令的使用
部落格地址:https://fisher-zh.github.io vue之實現列表的新增點選。 使用指令:v-on v-for v-on v-bind v-model html <!DOCTYPE html> <html lang="en"&
從Event Loop談JS的執行機制
這裡主要是結合Event Loop來談JS程式碼是如何執行的。 讀這部分的前提是已經知道了JS引擎是單執行緒,而且這裡會用到前面說的的幾個概念:(如果不是很理解,可以回頭溫習) JS引擎執行緒 事件觸發執行緒【輪訓】 定時觸發器執行緒 然後再理解一個概念:
Vue.js原始碼——事件機制
寫在前面 因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js原始碼,並做了總結與輸出。 文章的原地址:https://github.com/answershuto/learnVue。 在學習過程中,為Vue加上了中文的註釋https:/
2.解析C語言的內部執行機制
目錄 1.解析C語言的內部機制 2.瞭解ARM-THUMB 子程式呼叫規則 ATPCS 3.分析C語言的反彙編程式碼 1.解析C語言的內部機制 1.把上一節編譯第10節的C語言控制程式碼在Linux系統反彙編檔案,led.dis檔案傳windows系統檢視,然後分析這個程式時
淺談SQL Server內部執行機制
對於已經很熟悉T-SQL的讀者,或者對於較專業的DBA來說,邏輯的增刪改查,或者較複雜的SQL語句,都是非常簡單的,不存在任何挑戰,不值得一提,那麼,SQL的哪些方面是他們的挑戰 或者軟肋呢? 那就是sql優化。然而,要向成為一個好的Sql優化高手,首
前端讀者 | 由setTimeout引發的JS引擎執行機制的研究
本文來自 @xiaoyuze88 連結:http://xiaoyuze88.github.io/ 太久沒碰程式碼了,那天想到關於迴圈呼叫setTimeout實現每隔一秒輸出遞增的數的那個問題,搞了搞,發現很多概念模糊了,在此總結下。 所謂的迴圈呼叫setTimeout實現遞增輸出,就是說用for
深入Vue.js從原始碼開始(二)
從入口開始 我們之前提到過 Vue.js 構建過程,在 web 應用下,我們來分析 Runtime + Compiler 構建出來的 Vue.js,它的入口是 src/platforms/web/entry-runtime-with-compiler.js: 摘選entry-runtime-with-co
【vue】用vue-cli+bootstarp手動寫一個響應式的導航條
一、應用場景 在很多時候,我們的網站都是要求設計成響應式 也就是網站可以適應於 PC 端、平板和手機端 關於響應式的設計網上有很多教程,大致分為兩種: 1.使用一套程式碼,利用媒體查詢來適配不同的螢幕 2.使用兩套程式碼,根據使用者的終端不同切載入不同的程式碼來適配 兩種