1. 程式人生 > >Vue.js 內部執行機制 (二) ---- 響應式系統的依賴收集追蹤原理

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物件中 

  1. 用 addSub 方法可以在目前的 Dep 物件中增加一個 Watcher 的訂閱操作;
  2. 用 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.使用兩套程式碼,根據使用者的終端不同切載入不同的程式碼來適配 兩種