摘要:至此監聽器和訂閱者功能基本完成,后面再加上指令解析器的功能系列文章的目錄雙向綁定的實現原理系列一雙向綁定的實現原理系列二設計模式雙向綁定的實現原理系列三監聽器和訂閱者雙向綁定的實現原理系列四補充指令解析器
監聽器Observer和訂閱者Watcher 實現簡單版Vue的過程,主要實現{{}}、v-model和事件指令的功能 主要分為三個部分
github源碼
1.數據監聽器Observer,能夠對數據對象的所有屬性進行監聽; 實現數據的雙向綁定,首先要對數據進行劫持監聽,所以我們需要設置一個監聽器Observer,用來監聽所有屬性 2.Watcher將數據監聽器和指令解析器連接起來,數據的屬性變動時,執行指令綁定的相應回調函數, 1.如果屬性發上變化了,就需要告訴訂閱者Watcher看是否需要更新。 3.指令解析器Compile, 對每個節點元素進行掃描和解析,將相關指令對應初始化成一個訂閱者Watcher 因為訂閱者是有很多個,所以我們需要有一個消息訂閱器Dep來專門收集這些訂閱者,然后在監聽器Observer和訂閱者Watcher之間進行統一管理的。=
監聽器Observer
Observer是一個數據監聽器,核心是前面一直談論的Object.defineProperty(),
對所有屬性監聽,利用遞歸來遍歷所有的屬性值,對其進行Object.defineProperty()操作:
function definReactive(data,key,val){ observers(val);//遞歸所有子屬性 Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ console.log("屬性"+key+"執行get"); return val; }, set:function(newVal){ val = newVal; console.log("屬性:"+key+"以及被監聽,現在值為:"+newVal.toString()); } }) } function observers(data){ if(!data || typeof data!="object"){ return; } Object.keys(data).forEach(function(key){ definReactive(data,key,data[key]); }) } var library = { book1:{name:""}, book2:"" } observers(library); library.book1.name = "vue書籍"; library.book2 = "沒有書"; //屬性book1執行get //屬性:name以及被監聽,現在值為:vue書籍 //屬性:book2以及被監聽,現在值為:沒有書
接下來創建一個收集所有訂閱者的訂閱器Dep,閱器Dep主要負責收集訂閱者,然后再屬性變化的時候執行對應訂閱者的更新函數,
再改寫一下訂閱器Observer,創建一個observer.js:
function Observe(data){ this.data = data; this.walk(data); } Observe.prototype = { walk:function(data){ var self = this; Object.keys(data).forEach(function(key) { self.defineReactive(data, key, data[key]); }); }, defineReactive:function(data,key,val){ observers(val);//遞歸所有子屬性 var dep = new Dep(); Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ if(是否需要添加訂閱者){ dep.addSub(Watcher);//在這里添加一個訂閱者 } console.log("屬性"+key+"執行get"); return val; }, set:function(newVal){ if(val === newVal){ return; } val = newVal; dep.notify();//如果數據變化,通知所有訂閱者 console.log("屬性:"+key+"以及被監聽,現在值為:"+newVal.toString()); } }) } } function observers(data){ if(!data || typeof data!="object"){ return; } return new Observe(data); } /**Dep:創建一個可以容納訂閱者的消息訂閱器 * **/ function Dep(){ this.subs = []; } Dep.prototype = { addSub:function(sub){//添加訂閱者 this.subs.push(sub); }, notify:function(){//通知訂閱者 this.subs.forEach(function(sub){ sub.update(); }) } } 可以看出,訂閱器Dep,添加一個訂閱者是在Object.defineProperty()的get里面,這是為了讓Watcher初始化進行觸發, 因此要判斷是不是需要添加訂閱者,后面解釋。在set里面,如果數據變化,就會通知所有的訂閱者,訂閱者就會去執行對應的更新的函數 以上,一個完整的訂閱器完成。
訂閱者Watcher
Watcher在初始化的時候要將自己添加進訂閱者Dep中,如何做到:已經知道監聽器Observer是在get函數執行了添加訂閱者Wather的操作的,
所以我們只要在訂閱者Watcher初始化的時候觸發對應的get函數,去執行添加訂閱者操作即可,
那要如何觸發get的函數:
只要獲取對應的屬性值就可以觸發了,核心原因就是因為我們使用了Object.defineProperty()進行數據監聽。
注意:
我們只要在訂閱者Watcher初始化的時候才需要添加訂閱者,所以需要做一個判斷操作,
因此可以在訂閱器上做一下手腳:在Dep.target上緩存下訂閱者,添加成功后再將其去掉就可以了。
創建一個watcher.js
function Watcher(vm,exp,cb){ this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get();//將自己添加到訂閱器的操作 } Watcher.prototype = { update:function () { this.run(); }, run:function () { var value = this.vm.data[this.exp]; var oldVal = this.value; if(value != oldVal){ this.value = value; this.cb.call(this.vm,value,oldVal); } }, get:function () { Dep.target = this;//緩存自己 var value = this.vm.data[this.exp];//強制執行監聽器observer里的Object.defineProperty()里的get函數 Dep.target = null;//釋放自己 return value; } }
再調整下observer.js的defineReactive函數里的get操作:
defineReactive:function(data,key,val){ observers(val);//遞歸所有子屬性 var dep = new Dep(); Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ if(Dep.target){ dep.addSub(Dep.target);//在這里添加一個訂閱者 } console.log("屬性"+key+"執行get"); return val; }, set:function(newVal){ if(val === newVal){ return; } val = newVal; dep.notify();//如果數據變化,通知所有訂閱者 console.log("屬性:"+key+"以及被監聽,現在值為:"+newVal.toString()); } }) } //Dep加個target屬性 function Dep(){ this.subs = []; this.target = null; }
簡單版的Watcher設計好了,
只要將Observer和Watcher關聯起來,就可以實現一個簡單的雙向綁定數據了。
這里沒有還沒有設計解析器Compile,所以對于模板數據我們都進行寫死處理:
模板有個節點,id為name,雙向數據綁定的變量name,這里大框號暫時沒有用:
{{name}}
selVue.js 關聯Observer和Watcher
function SelfVue(data,el,exp){ this.data = data; observers(data); el.innerHTML = this.data[exp];//初始化模板數據的值 new Watcher(this,exp,function(value){ el.innerHTML = value; }); return this; }
頁面上實現雙向數據綁定:
{{name}}
打開頁面,可以看到頁面剛開始顯示了是“第一次顯示數據”,過了2s后就變成“重新賦值了”了。到這里,完成了一部分
注意:賦值的時候是 self_Vue.data.name = "重新賦值了",但是希望是 self_Vue.name = "重新賦值了", 需要在new SelVue的時候做個代理,讓訪問self_Vue的屬性代理為訪問self_Vue.data的屬性, 實現原理還是使用Object.defineProperty()對屬性再包一層,
修改selVue.js:
function SelfVue(data,el,exp){ var self = this; this.data = data; Object.keys(data).forEach(function (key) { self.proxyKeys(key);//綁定代理屬性 }); observers(data); el.innerHTML = this.data[exp];//初始化模板數據的值 new Watcher(this,exp,function(value){ el.innerHTML = value; }); return this; } SelfVue.prototype = { proxyKeys:function(key){ var self = this; Object.defineProperty(this,key,{ enumerable:false, configurable:true, get:function proxyGetter(){ return self.data[key]; }, set:function proxySetter(newVal){ self.data[key] = newVal; } }) } } //這下我們就可以直接通過self_Vue.name = "重新賦值了"的形式來進行改變模板數據。
至此監聽器Observer和訂閱者Watcher功能基本完成,后面再加上指令解析器compile的功能!
系列文章的目錄:Vue雙向綁定的實現原理系列(一):Object.defineproperty
Vue雙向綁定的實現原理系列(二):設計模式
Vue雙向綁定的實現原理系列(三):監聽器Observer和訂閱者Watcher
Vue雙向綁定的實現原理系列(四):補充指令解析器compile
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107256.html
摘要:至此監聽器和訂閱者功能基本完成,后面再加上指令解析器的功能系列文章的目錄雙向綁定的實現原理系列一雙向綁定的實現原理系列二設計模式雙向綁定的實現原理系列三監聽器和訂閱者雙向綁定的實現原理系列四補充指令解析器 監聽器Observer和訂閱者Watcher 實現簡單版Vue的過程,主要實現{{}}、v-model和事件指令的功能 主要分為三個部分 github源碼 1.數據監聽器Obser...
摘要:儲存訂閱器因為屬性被監聽,這一步會執行監聽器里的方法這一步我們把也給弄了出來,到這一步我們已經實現了一個簡單的雙向綁定了,我們可以嘗試把兩者結合起來看下效果。總結本文主要是對雙向綁定原理的學習與實現。 當今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個陣營基本上無法立足于前端,甚至是兩個或者三個陣營都要選擇,大勢所趨。 所以我們要時刻保持好奇心,擁抱變化,...
摘要:兼容性更詳細的可以看一下實現思路系列的雙向綁定,關鍵步驟實現數據監聽器,用重寫數據的,值更新就在中通知訂閱者更新數據。 showImg(https://segmentfault.com/img/remote/1460000015375220?w=640&h=426); 前言 現在的前端面試不管你用的什么框架,總會問你這個框架的雙向綁定機制,有的甚至要求你現場實現一個雙向綁定出來,那對于...
摘要:補充指令解析器源碼補充下節點類型的知識元素節點屬性節點文本節點節點實體引用名稱節點實體名稱節點處理指令節點注釋節點文檔節點文檔類型節點文檔片段節點聲明節點指令解析器解析節點,直接固定某個節點進行替換數據的解析模板指令,替換模板數據初始化試圖 補充指令解析器compile github源碼 補充下HTML節點類型的知識: 元素節點 Node.ELEMEN...
摘要:補充指令解析器源碼補充下節點類型的知識元素節點屬性節點文本節點節點實體引用名稱節點實體名稱節點處理指令節點注釋節點文檔節點文檔類型節點文檔片段節點聲明節點指令解析器解析節點,直接固定某個節點進行替換數據的解析模板指令,替換模板數據初始化試圖 補充指令解析器compile github源碼 補充下HTML節點類型的知識: 元素節點 Node.ELEMEN...
閱讀 3616·2021-11-24 09:39
閱讀 2546·2021-11-15 11:37
閱讀 2211·2021-11-11 16:55
閱讀 5155·2021-10-14 09:43
閱讀 3703·2021-10-08 10:05
閱讀 3006·2021-09-13 10:26
閱讀 2327·2021-09-08 09:35
閱讀 3535·2019-08-30 15:55