一起學習vue源碼 - Object的變化偵測


 

作者:小土豆

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

掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

微信公眾號:不知名寶藏程序媛(關注"不知名寶藏程序媛"免費領取前端電子書籍。文章公眾號首發,關注公眾號第一時間獲取最新文章。)

碼字不易,點贊鼓勵喲~

一.前言

  一起學習vue源碼的第一篇,本來想起名為雙向數據綁定原理,但是想來還是引用書中[深入淺出vue.js]比較專業的描述作為題目。

  (主要是因為雙向數據綁定中Object和Array的實現原理是不一樣的,所以還是拆分的細一些比較好)

  總歸來說,雙向數據綁定就是通過變化偵測這種方式去實現的,這篇文章主要總結的是Object的變化偵測。

 

  我們在面試的時候,如果面試者的技術棧包含vue框架,那么面試官會有很大的幾率甩出“你了解vue中雙向數據綁定的原理嗎”這個問題。

  我也聽過一些回答,大家一般都能說出一個詞叫“發布-訂閱”。

  那在深入去問的時候,或者說你能不能給我實現一個簡單的雙向數據綁定,基本就回答不上來了。

  

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

  前面說過雙向數據綁定就是通過變化偵測這種方式去實現的。

  那這里的發布-訂閱我理解是軟件的設計思想,它比變化偵測更深一層,已經到了代碼的設計模式這一層了。

  所以我們可以說雙向數據綁定就是通過變化偵測這種方式去實現的,也可以說雙向數據綁定是通過發布-訂閱這種模式去實現的。

  我個人覺得兩者說法都沒有問題,只是描述方式不一樣。

 

  那不管是叫變化偵測還是發布-訂閱,有一些實際生活中的例子可以便於我們理解它們。

  (后面的很多描述都會混用這兩個名詞,不用糾結叫法,了解說的是同一個東西即可)

  比如我們經常玩的微博:

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

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

    在過了一段時間,博主MM爆出一個不好的新聞,用戶kk便將博主MM的微博取關了。

 

  在這個實際場景中,我們可以稱博主MM是一個發布者

  用戶kk是一個訂閱者

  微博客戶端就是一個管理者的角色,它時刻偵測這博主MM的動態,在博主MM更新動態是主動將動態推送給訂閱者。

  

  前面說了這么多想來大家應該能理解發布訂閱/變化偵測大致的設計思想和需要關注的幾個點了:

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

    2.如何收集保存訂閱者。

    3.訂閱者如何實現。

  

  接着我們就我們總結的點逐個擊破。

 

二.如何偵測數據的變化

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

  備注:對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.depend和Dep.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變量中。

 

  到這里,由訂閱器Dep的depend方法順藤摸瓜出來的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方法中進行調用。

   

 

 

 

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.value和newValue做一個比較,即對舊值和新值作比較。

  只有在新值和舊值不相等的情況下,才會觸發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中觸發數據屬性get方法的執行已經補充完畢,我們在看看訂閱器Dep的depend方法。

depend() {
        // 這里可以先不用關注depObject是什么 // 就先暫時理解它是一個訂閱者/依賴對象 this.addSub(depObject); }

 

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

  我們回頭再看看這個depend觸發的流程:

    

   

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

 

  所以按照這個流程,在觸發數據屬性上定義的get方法之前,就必須將Watcher實例准備好。

  我們知道在初始化Watcher時,Watcher內部的this的指向就是Watcher實例。

  所以vue設計的時候,在Watcher的get方法中把Watcher實例保存到了Dep的target屬性上。

  這樣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中的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)的判斷是非常必須的步驟。

 

六.完整代碼

  現在我們將Observer、Dep、Watcher的完整代碼貼出來。

  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實現

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

 

  接着,手動為data的text屬性創建一個訂閱者,代碼依然寫在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來實現:監聽button的click事件,觸發時將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源碼 - Object的變化偵測" 總結完成。

 

 

結束語:

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

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

 如果我的內容能給你帶來幫助,可以持續關注我,或者在評論區指出不足之處。

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

  

 

作者:小土豆

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

掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

微信公眾號:不知名寶藏程序媛(關注"不知名寶藏程序媛"免費領取前端電子書籍。文章公眾號首發,關注公眾號第一時間獲取最新文章。)

碼字不易,點贊鼓勵喲~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM