1W字長文+多圖,帶你瞭解vue的雙向數據綁定源碼實現

做者:小土豆biubiubiujavascript

博客園:www.cnblogs.com/HouJiao/html

掘金:juejin.im/user/58c61b…前端

簡書:www.jianshu.com/u/cb1c3884e…vue

微信公衆號:土豆媽的碎碎念(掃碼關注,一塊兒吸貓,一塊兒聽故事,一塊兒學習前端技術)java

碼字不易,點贊鼓勵喲~面試

前言

一塊兒學習vue源碼的第一篇來說講vue雙向數據綁定的源碼實現。設計模式

vue中的雙向數據綁定主要是經過變化偵測這種方式去實現的,這篇文章主要總結的是Object的變化偵測數組

vue雙向數據綁定中Object和Array的實現原理是不同的瀏覽器

咱們在面試的時候,若是面試者的技術棧包含vue框架,那麼面試官會有很大的概率甩出「你瞭解vue中雙向數據綁定的原理嗎」這個問題。我也聽過一些回答,你們通常都能說出一個詞叫發佈-訂閱。那在深刻去問的時候,或者說你能不能給我實現一個簡單的雙向數據綁定,基本就回答不上來了。微信

說到這裏我已經拋出三個名詞了:雙向數據綁定變化偵測發佈-訂閱

前面說過雙向數據綁定就是經過變化偵測這種方式去實現的。那這裏的發佈-訂閱我理解是軟件的設計思想,它比變化偵測更深一層,已經到了代碼的設計模式這一層了。

因此咱們能夠說雙向數據綁定就是經過變化偵測這種方式去實現的,也能夠說雙向數據綁定是經過發佈-訂閱這種模式去實現的。我我的以爲二者說法都沒有問題,只是描述方式不同。

那不論是叫變化偵測仍是發佈-訂閱,有一些實際生活中的例子能夠便於咱們理解它們。

後面的不少描述都會混用這兩個名詞,不用糾結叫法,瞭解說的是同一個東西便可

好比咱們常常玩的微博:

有一個用戶kk很喜歡某個博主MM,而後就在微博上關注了博主MM。

以後每一次博主MM在微博上發表一些吃吃喝喝的動態,微博客戶端都會主動將動態推送給用戶kk。 

在過了一段時間,博主MM爆出一個很差的新聞,用戶kk便將博主MM的微博取關了。
複製代碼

在這個實際場景中,咱們能夠稱博主MM是一個發佈者用戶kk是一個訂閱者微博客戶端就是一個管理者的角色,它時刻偵測這博主MM的動態,在博主MM更新動態時主動將動態推送給訂閱者。

基於咱們前面這些,想來你們應該能理解發佈訂閱/變化偵測大體的設計思想和須要關注的幾個點了:

1.如何偵測數據的變化(或者說如何偵測到發佈者的發佈的內容)

2.如何收集保存訂閱者(也就是微博客戶端這個管理者的角色如何實現)。  

3.訂閱者如何實現。
複製代碼

接着就咱們總結的這幾個點逐個去解讀vue的源碼實現。

如何偵測數據的變化

看過javascript高級程序設計的應該都知道Object類提供了一個方法defineProperty,在該方法中定義getset就能夠實現數據的偵測。

對Object.defineProperty不瞭解的能夠移步這裏

下面就Object的defineProperty方法作一個示例演示。

var obj = {};

var name;
Object.defineProperty(obj, 'name', {
      enumerable : true,
      configurable : true,
      get: function(){
          console.log("get方法被調用");
          return name;
      },
      set: function(newName){
          console.log("set方法被調用");
          name = newName;
      }
})


// 修改name屬性時會觸發set方法
obj.name = 'newTodou';

// 訪問name屬性時會觸發get方法
var objName = obj.name;
複製代碼

咱們將這段代碼引入一個html中,執行後控制檯的打印結果以下:

能夠看到,當咱們修改obj.name屬性值時,調用了name屬性的set方法,打印了"set方法被調用";當咱們訪問obj.name屬性值時,調用name屬性的get方法,打印了"get方法被調用"

那麼這就是咱們說的如何偵測數據的變化這個問題的答案,是否是很簡單呢。

訪問數據屬性值時會觸發定義在屬性上的get方法;修改數據屬性值時觸發定義在屬性上的set方法。

這句話很關鍵,但願能夠牢記,後面不少內容都跟這個相關。
複製代碼

實際上到這裏咱們已經能夠實現一個簡單的雙向數據綁定:input輸入框內容改變,實現輸入框下方span文本內容改變

咱們先梳理一下這整個的實現思路:監聽輸入框的內容,將輸入框的內容同步到span的innerText屬性。

監聽輸入框內容的變化能夠經過keyup事件,在事件內部獲取到input框中的內容,即獲取到變化的數據,咱們把這個數據保存到一個obj對象的name屬性中。

將輸入框的內容同步到span的innerText屬性這個操做至關於將變化的數據同步更新到視圖中,更新的邏輯很簡單:spanEle.innerText = obj.name

咱們須要考慮的是在哪裏觸發這個更新操做。

在監聽輸入框內容變化的邏輯中咱們說過會將變化的數據保存到obj.name中。那這個操做實際上就是爲對象的屬性賦值,會觸發定義在屬性上的set方法。那麼將輸入框的內容同步到span的innerText屬性這個操做,很天然的就落到了name屬性的set方法中。

到這裏,相信你們已經很輕鬆能寫出代碼了。

<input type="text" id="name"/>
<br/>
<span id="text"></span>
<script type="text/javascript"> var nameEle = document.getElementById("name"); var textEle = document.getElementById('text'); var obj = {}; Object.defineProperty(obj, 'name', { enumerable: true, configurable: true, get: function(){ return textEle.value; }, set: function(newName){ textEle.innerText = newName; } }) nameEle.onkeyup = function () { obj.name = event.target.value; } </script>
複製代碼

整個邏輯代碼都比較簡單,可是基本已經能夠應付面試官的問題:你能不能給我實現一個簡單的雙向數據綁定這個問題了。

接着還沒完,咱們知道一個對象裏面通常都會有多個屬性,vue data中通常也會存在多個或者多層的屬性和數據,好比:

data: {
    id: 12091,
    context: {
        index:1,
        result:0
        
    }
}
複製代碼

因此咱們得讓對象中的全部屬性都變得可偵測:遞歸遍歷對象的全部屬性,爲每一個屬性都定義get和set方法。

vue源碼是封裝了一個Observer類來實現這個功能。

/* * obj數據實際上就是vue中的data數據 */
function Observer(obj){
    this.obj = obj;
    this.walk(obj);
   
}
Observer.prototype.walk = function(obj) {
    // 獲取obj對象中全部的屬性
    var keysArr = Object.keys(obj);
    keysArr.forEach(element =>{
        defineReactive(obj, element, obj[element]);
    })
}
// 參照源碼,將該方法爲獨立一個方法
function defineReactive(obj, key, val) {
    // 若是obj是包含多層數據屬性的對象,就須要遞歸每個子屬性
    if(typeof val === 'object'){
        new Observer(val);
    }

    Object.defineProperty(obj, key,{
        enumerable: true,
        configurable: true,
        get: function(){
            return val;
        },
        set: function(newVal) {
            val = newVal;
        }
    })        
}
複製代碼

到這裏,數據偵測這一步就完成了。

如何收集保存訂閱者

收集保存訂閱者說的簡單點就是一個數據存儲的問題,因此也不用太糾結,就將訂閱者保持到數組中。

前面咱們說過微博的那個例子:

用戶kk關注博主MM,對應的就是往數組中添加一個訂閱者/元素。

用戶kk取關博主MM,能夠理解爲從數組中移除一個訂閱者/元素。

博主MM發佈動態,微博客戶端主動動態給用戶kk,這能夠理解爲通知數據更新操做。
複製代碼

那上面描述的一整個內容就是收集保存訂閱者須要關注的東西,深刻淺出vue.js這本書中把它叫作如何收集依賴

那麼如今就咱們說的內容,實現一個類Dep,後面把它稱爲訂閱器,用於管理訂閱者/管理依賴

function Dep(){
    this.subs = [];
}

Dep.prototype.addSub = function(sub){
    this.subs.push(sub);
}
// 添加依賴
Dep.prototype.depend = function() {
    // 這裏能夠先不用關注depObject是什麼
    // 就先暫時理解它是一個訂閱者/依賴對象
    this.addSub(depObject);
}

 // 移除依賴
 Dep.prototype.removeSub = function(sub) {
    // 源碼中是經過抽出來一個remove方法來實現移除的
    if(this.subs.length > 0){
        var index = this.subs.indexOf(sub);
        if(index > -1){
            // 注意splice的用法
            this.subs.splice(index, 1);
        }
    }
}

// 通知數據更新
Dep.prototype.notify = function() {
    for(var i = 0; i < this.subs.length; i++ ){
        // 這裏至關於依次調用subs中每一個元素的update方法
        // update方法內部實現能夠先不用關注,瞭解其目的就是爲了更新數據
        this.subs[i].update()
    }
}
複製代碼

依賴收集和管理實現了以後,咱們須要考慮兩個問題:何時添加依賴何時通知更新數據

在微博的例子中,用戶kk關注博主MM,對應的就是往數組中添加一個訂閱者/元素。那對應到代碼中,能夠視做訪問了對象的屬性,那咱們就能夠在訪問對象屬性的時候添加依賴。

博主MM發佈動態,微博客戶端主動動態給用戶kk,這能夠理解爲通知數據更新操做。在對應到代碼中,能夠視做修改了對象的屬性,那咱們就能夠在修改對象屬性的時候通知數據更新。

這段話可能不是很好理解(有點強賣強賣的趕腳),因此咱們能夠去聯想平時咱們在vue中的操做:使用雙花括號{{text}}在模板的div標籤內插入數據

這個操做實際上就至關因而模板中的div標籤讀取而且依賴了vue中的data.text數據,那咱們就能夠將這個div做爲一個依賴對象收集起來。以後當text數據發生變化後,咱們就須要通知這個div標籤更新它內部的數據。

說了這麼多,咱們剛剛的提的何時添加依賴,何時通知更新數據這個問題就已經有答案了:在get中添加依賴,在set中通知數據更新

關於添加依賴通知數據更新這兩個操做均是Dep這個類的功能,接口分別爲:Dep.dependDep.notify。那如今咱們就將Observer這個類進行完善:get中添加依賴,在set中通知數據更新。

/* * obj數據實際上就是vue中的data數據 */
function Observer(obj){
    this.obj = obj;
    if(Array.isArray(this.obj)){
        //若是是數組,則會調用數組的偵測方法
    }else{
        this.walk(obj);
    }
}
Observer.prototype.walk = function(obj) {
    // 獲取obj對象中全部的屬性
    var keysArr = Object.keys(obj);
    keysArr.forEach(element =>{
        defineReactive(obj, element, obj[element]);
    })
}
// 參照源碼,將該方法爲獨立一個方法
function defineReactive(obj, key, val) {
    // 若是obj是包含多層數據屬性的對象,就須要遞歸每個子屬性
    if(typeof val === 'object'){
        new Observer(val);
    }
    var dep = new Dep();    
    Object.defineProperty(obj, key,{
        enumerable: true,
        configurable: true,
        get: function(){
            // 在get中添加依賴
            dep.depend();
            return val;
        },
        set: function(newVal) {
            val = newVal;
            // 在set中通知數據更新
            dep.notify();

        }
    })        
}
複製代碼

如何實現訂閱者

仍是前面微博的例子,其中用戶KK被視爲一個訂閱者,vue源碼中將定義爲Watcher。那訂閱者須要作什麼事情呢?

先回顧一下咱們實現的訂閱器Dep。第一個功能就是添加訂閱者

depend() {
        // 這裏能夠先不用關注depObject是什麼
        // 就先暫時理解它是一個訂閱者/依賴對象
        this.addSub(depObject);
}
複製代碼

能夠看到這段代碼中當時的註釋是能夠先不用關注depObject是什麼,暫時理解它是一個訂閱者/依賴對象

那如今咱們就知道depObject實際上就是一個Watcher實例。那如何觸發depend方法添加訂閱者呢?

在前面編寫偵測數據變化代碼時,觸發depend方法添加依賴的邏輯在屬性的get方法中。

那vue源碼的設計是在Watcher初始化的時候觸發數據屬性的get方法,便可以將訂閱者添加到訂閱器中。

下面將代碼貼出來。

/* * vm: vue實例對象 * exp: 屬性名 */
function Watcher(vm, exp){
    this.vm = vm;
    this.exp = exp;

    // 初始化的時候觸發數據屬性的get方法,便可以將訂閱者添加到訂閱器中
    this.value = this.get();
}

// 觸發數據屬性的get方法: 訪問數據屬性便可實現
Watcher.prototype.get = function() {
    // 訪問數據屬性邏輯
    var value =  this.vm.data[this.exp];
    return value;
}
複製代碼

這裏對get方法的邏輯簡單的解讀一下:

數據屬性的訪問確定是須要傳遞數據和對應的屬性名才能實現。
而後咱們想一下vue中的data屬性是可使用vue的實例對象加"."操做符進行訪問的。
因此vue在這裏設計的時候沒有直接將數據傳入,而是傳遞一個vue實例,使用vue實例.data['屬性名']對屬性進行訪問,從而去觸發屬性的get方法。
複製代碼

注意:vue還將訪問到的數據屬性值保存到了Watcher中value變量中。

到這裏,由訂閱器Depdepend方法順藤摸瓜出來的Watcher的第一個功能就完成了,即:Watcher初始化的時候觸發數據屬性的get方法,將訂閱者添加到訂閱器中。

咱們在接着摸瓜,看一下訂閱器Dep的第二個功能:通知數據更新。

// 通知數據更新
notify() {
        for(let i = 0; i < this.subs.length; i++ ){
            // 這裏至關於依次調用subs中每一個元素的update方法
            // update方法內部實現能夠先不用關注,瞭解其目的就是爲了更新數據
            this.subs[i].update()
        }
}
複製代碼

這段代碼最重要的一行:this.subs[i].update(),這行代碼實際上觸發的是訂閱者Watcher實例的update方法。(由於subs中的每個元素就是一個訂閱者實例)因此咱們的Watcher的第二個功能就是須要實現一個真正包含更新數據邏輯的update函數。

那什麼叫真正更新數據的邏輯呢?

仍是vue的雙花括號示例:使用雙花括號{{text}}在模板的div標籤內插入數據。

當text數據發生變化後,真正更新數據的邏輯就是: div.innerText = newText。那Watcher中的update方法咱們應該大體瞭解了。

在說回vue的設計,它將真正更新數據的邏輯封裝成一個函數,Watcher實例初始化的時候傳遞給Watcher的構造函數,而後在update方法中進行調用。

代碼實現以下。

/* * vm: vue實例對象 * exp: 對象的屬性 * cb: 真正包含數據更新邏輯的函數 */
function Watcher(vm, exp, cb){
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    // 初始化的時候觸發數據屬性的get方法,便可以將訂閱者添加到訂閱器中
    this.value = this.get();
}

// 觸發數據屬性的get方法: 訪問數據屬性便可實現
Watcher.prototype.get = function() {
    // 訪問數據屬性邏輯
    var value =  this.vm.data[this.exp];
    return value;
}
Watcher.prototype.update = function() {
    // 當update被觸發時,此時獲取到的數據屬性值是已經被修改事後的新值
    var newValue = this.vm.data[this.exp];

    // 觸發傳遞給Watcher的更新數據的函數
    this.cb.call(this.vm, newValue);
    
}
複製代碼

那簡單的update代碼就實現了,不過vue在這裏有作小小的優化。

咱們在get方法中訪問了數據的屬性,並將數據爲修改前的初值保存到了this.value中。因此update方法的優化就是在執行update後續代碼以前,先對this.valuenewValue作一個比較,即對舊值新值做比較。只有在新值舊值不相等的狀況下,纔會觸發cb函數。

Watcher.prototype.update = function() {
    // 當update被觸發時,此時獲取到的數據屬性值是已經被修改事後的新值
    var newValue = this.vm.data[this.exp];
    var oldValue = this.value;

    if(oldValue !== newValue){
        // 觸發傳遞給Watcher的更新數據的函數
        this.cb.call(this.vm, newValue);
    }
}
複製代碼

巧妙保存訂閱者(Watcher)實例

到這裏呢,咱們遺漏了一個邏輯,先看看前面實現的訂閱器Depdepend方法。

depend() {
        // 這裏能夠先不用關注depObject是什麼
        // 就先暫時理解它是一個訂閱者/依賴對象
        this.addSub(depObject);
}
複製代碼

關於這個depObject咱們說過它是一個訂閱者,即Watcher的一個實例,那如何獲取Watcher這個實例呢?

咱們回頭再看看這個depend方法的觸發流程:

即建立Watcher實例,調用Watcher實例的get方法,從而觸發數據屬性上定義的get方法,最終觸發dep.depend方法。

因此按照這個流程,在觸發數據屬性上定義的get方法以前,就必須將Watcher實例準備好。咱們知道在初始化Watcher時,Watcher內部的this的指向就是Watcher實例。因此vue設計的時候,在Watcherget方法中把Watcher實例保存到了Deptarget屬性上。這樣Watcher實例化完成後,全局訪問Dep.target就能獲取到Watcher實例。

因此如今將Watcher類的get方法進行補充。

// 觸發數據屬性的get方法: 訪問數據屬性便可實現
Watcher.prototype.get = function() {
    // 把Watcher實例保存到了Dep的target屬性上
    Dep.target = this;
    // 訪問數據屬性邏輯
    var value =  this.vm.data[this.exp];
    // 將實例清空釋放
    Dep.target = null;
    return value;
}
複製代碼

對於get方法中清空釋放Dep.target的代碼,是有必定緣由的,請先繼續往下看。

接着咱們須要將Dep中的depend方法進行補全。

// 添加依賴
Dep.prototype.depend = function() {
    // addSub添加的是一個訂閱者/依賴對象
    // Watcher實例就是訂閱者,在Watcher實例初始化的時候,已經將本身保存到了Dep.target中
    if(Dep.target){
        this.addSub(Dep.target);
    } 
}
複製代碼

如今我在說一下清空釋放Dep.target的代碼。

假如咱們沒有Dep.target = null這行代碼,depend方法中也沒有if(Dep.target)的判斷。那第一個訂閱者添加完成後是正常的,當數據發生變化後,代碼執行邏輯:

觸發數據屬性上定義的set方法

執行dep.notify  

執行Watcher實例的update方法

....
複製代碼

後面的就不說了,咱們看一下這個過程當中執行Watcher實例的update方法這一步。

Watcher.prototype.update = function() {
    // 當update被觸發時,此時獲取到的數據屬性值是已經被修改事後的新值
    var newValue = this.vm.data[this.exp];
    var oldValue = this.value;

    if(oldValue !== newValue){
        // 觸發傳遞給Watcher的更新數據的函數
        this.cb.call(this.vm, newValue);
    }
}
複製代碼

能夠看到,update方法中由於在執行真正更新數據的函數cb以前須要獲取到新值。因此再次訪問了數據屬性,那可想而知,訪問數據屬性就會調用屬性的get方法。

又由於dep.depend的執行沒有任何條件判斷,致使當前Watcher被植入訂閱器兩次。這顯然是不正常的。所以,Dep.target = nullif(Dep.target)的判斷是很是必須的步驟。

完整代碼

如今咱們將ObserverDepWatcher的完整代碼貼出來。

Observer實現

/* * obj數據實際上就是vue中的data數據 */
function Observer(obj){
    this.obj = obj;
    if(Array.isArray(this.obj)){
        //若是是數組,則會調用數組的偵測方法
    }else{
        this.walk(obj);
    }
}
Observer.prototype.walk = function(obj) {
    // 獲取obj對象中全部的屬性
    var keysArr = Object.keys(obj);
    keysArr.forEach(element =>{
        defineReactive(obj, element, obj[element]);
    })
}
// 參照源碼,將該方法爲獨立一個方法
function defineReactive(obj, key, val) {
    // 若是obj是包含多層數據屬性的對象,就須要遞歸每個子屬性
    if(typeof val === 'object'){
        new Observer(val);
    }
    var dep = new Dep();    
    Object.defineProperty(obj, key,{
        enumerable: true,
        configurable: true,
        get: function(){
            // 在get中添加依賴
            dep.depend();
            return val;
        },
        set: function(newVal) {
            val = newVal;
            // 在set中通知數據更新
            dep.notify();

        }
    })        
}
複製代碼

Dep實現

function Dep(){
    this.subs = [];
}

Dep.prototype.addSub = function(sub){
    this.subs.push(sub);
}
// 添加依賴
Dep.prototype.depend = function() {
    // addSub添加的是一個訂閱者/依賴對象
    // Watcher實例就是訂閱者,在Watcher實例初始化的時候,已經將本身保存到了Dep.target中
    if(Dep.target){
        this.addSub(Dep.target);
    } 
}

 // 移除依賴
 Dep.prototype.removeSub = function(sub) {
    // 源碼中是經過抽出來一個remove方法來實現移除的
    if(this.subs.length > 0){
        var index = this.subs.indexOf(sub);
        if(index > -1){
            // 注意splice的用法
            this.subs.splice(index, 1);
        }
    }
}

// 通知數據更新
Dep.prototype.notify = function() {
    for(var i = 0; i < this.subs.length; i++ ){
        // 這裏至關於依次調用subs中每一個元素的update方法
        // update方法內部實現能夠先不用關注,瞭解其目的就是爲了更新數據
        this.subs[i].update()
    }
}
複製代碼

Watcher實現

/* * vm: vue實例對象 * exp: 對象的屬性 * cb: 真正包含數據更新邏輯的函數 */
function Watcher(vm, exp, cb){
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    // 初始化的時候觸發數據屬性的get方法,便可以將訂閱者添加到訂閱器中
    this.value = this.get();
}

// 觸發數據屬性的get方法: 訪問數據屬性便可實現
Watcher.prototype.get = function() {
    // 把Watcher實例保存到了Dep的target屬性上
    Dep.target = this;
    // 訪問數據屬性邏輯
    var value =  this.vm.data[this.exp];
    // 將實例清空釋放
    Dep.target = null;
    return value;
}
Watcher.prototype.update = function() {
    // 當update被觸發時,此時獲取到的數據屬性值是已經被修改事後的新值
    var newValue = this.vm.data[this.exp];
    var oldValue = this.value;

    if(oldValue !== newValue){
        // 觸發傳遞給Watcher的更新數據的函數
        this.cb.call(this.vm, newValue);
    }
}
複製代碼

實踐

關鍵核心的代碼已經梳理完成,接下來就是使用了。

由於這個過程當中沒有模板編譯的實現,所以有些代碼須要寫死。

回想vue中雙向數據綁定的用法,咱們先寫一段簡單的代碼。

<html>
    <head>
        <meta charset="utf-8" />
        <title>一塊兒學習Vue源碼-Object的變化偵測</title>
    </head>
    <body>
        <h1>一塊兒學習Vue源碼-Object的變化偵測</h1>
        <div id="box">
            {{text}}
        </div>
    </body>
    <script type="text/javascript" src="./Dep.js"></script> 
    <script type="text/javascript" src="./Observer.js"></script>    
    <script type="text/javascript" src="./Watcher.js"></script>

    <script type='text/javascript'> /* * data: 數據 * el: 元素 * exp:對象的屬性 * (傳遞這個exp固定參數也是由於沒有模板編譯相關的代碼,因此就暫時寫死一個屬性) */ function Vue(data, el, exp){ this.data = data; this.el = el; // 由於沒有模板相關的代碼,因此{{text}}的值使用這種方式進行解析 this.innerHTML = this.data[exp]; } var data = { text: 'hello Vue' }; var el = document.getElementById('box'); var vm = new Vue(data, el); </script>
</html>
複製代碼

這段代碼運行後,瀏覽器中已經能夠顯示{{text}}的值了。

正常顯示並非由於咱們對模板和花括號進行編譯,而是使用el.innerHTML = data.text;這種寫死的方式實現的。

接着,第一步就是將數據變得可觀測,即調用Observer傳入data數據,咱們將代碼寫到Vue構造函數中。

/* * data: 數據 * el: 元素 * exp:對象的屬性 */
function Vue(data, el, exp){
     this.data = data;
     this.el = el;
     this.exp = exp;

     // 由於沒有模板相關的代碼,因此{{text}}的值使用這種方式進行解析
     this.el.innerHTML = this.data[exp];

     //初始化vue實例須要將data數據變得可觀測
     new Observer(data);
}

複製代碼

接着,手動爲datatext屬性建立一個訂閱者,代碼依然寫在vue構造函數中。

手動建立訂閱者也是由於沒有模板編譯代碼,不然建立訂閱者正常的邏輯是遍歷模板動態建立訂閱者。

/* * data: 數據 * el: 元素 * exp:對象的屬性 */
function Vue(data, el, exp){
     this.data = data;
     this.el = el;
     this.exp = exp;

     // 由於沒有模板相關的代碼,因此{{text}}的值使用這種方式進行解析
     this.el.innerHTML = this.data[exp];

     //初始化vue實例須要將data數據變得可觀測
     new Observer(data);

     this.cb = function(newVal){
          this.el.innerHTML = newVal;
     }
     // 建立一個訂閱者
     new Watcher(this, exp, this.cb);
}
複製代碼

建立訂閱者的時候有一個cb參數,cb就是咱們前面一直說的那個真正包含更新數據邏輯的函數

這些操做完成後,最後一步就是修改data.text的數據,若是修改完成後,div的內容發生變化,就證實咱們這份代碼已經成功運行了。那修改data.text數據的邏輯我借用一個button來實現:監聽buttonclick事件,觸發時將data.text的值改成"hello new vue"

<html>
    <head>
        <meta charset="utf-8" />
        <title>一塊兒學習Vue源碼-Object的變化偵測</title>
    </head>
    <body>
        <h1>一塊兒學習Vue源碼-Object的變化偵測</h1>
        <div id="box">
            {{text}}
        </div>
        <br/>
        <button onclick="btnClick()">點擊我改變div的內容</button>
    </body>
    <script type="text/javascript" src="./Dep.js"></script> 
    <script type="text/javascript" src="./Observer.js"></script>    
    <script type="text/javascript" src="./Watcher.js"></script>

    <script> /* * data: 數據 * el: 元素id * exp:對象的屬性 * (傳遞這個exp固定參數也是由於沒有模板編譯相關的代碼,因此就暫時寫死一個屬性) * cb: 真正包含數據更新邏輯的函數 */ function Vue(data, el, exp){ this.data = data; this.el = el; this.exp = exp; // 由於沒有模板相關的代碼,因此{{text}}的值使用這種方式進行解析 this.el.innerHTML = this.data[exp]; this.cb = function(newVal){ this.el.innerHTML = newVal; } //初始化vue實例須要將data數據變得可觀測 new Observer(data); //建立一個訂閱者 new Watcher(this, exp, this.cb); } var data = { text: 'hello Vue' }; var el = document.getElementById('box'); var exp = 'text'; var vm = new Vue(data, el, exp); function btnClick(){ vm.data.text = "hello new vue"; } </script>
</html>
複製代碼

上面這份代碼已是完整的代碼了,咱們一塊兒在瀏覽中操做一波看下結果。

能夠看到,咱們的代碼已經成功運行。

那到這裏,vue的雙向數據綁定源碼實現梳理完畢。

結束語:

    個人vue源碼的學習途徑主要會參考我本身剛入手的《深刻淺出vue.js》這本書,同時會參考網上一些內容。

    我會盡可能將從源碼中解讀出的內容,以一種更通俗易懂的方式總結出來。

    若是個人內容能給你帶來幫助,能夠持續關注我,或者在評論區指出不足之處。

    同時由於是源碼學習,因此這個過程當中我也充當一個源碼搬運工的角色,不創造代碼只搬運並解讀源碼。
複製代碼

做者:小土豆biubiubiu

博客園:www.cnblogs.com/HouJiao/

掘金:juejin.im/user/58c61b…

簡書:www.jianshu.com/u/cb1c3884e…

微信公衆號:土豆媽的碎碎念(掃碼關注,一塊兒吸貓,一塊兒聽故事,一塊兒學習前端技術)

碼字不易,點贊鼓勵喲~
相關文章
相關標籤/搜索