摘要:下圖展示了實(shí)現(xiàn)雙向綁定的流程實(shí)現(xiàn)一個(gè)簡(jiǎn)單的雙向綁定雙向綁定最最最初級(jí)進(jìn)階版操作是非常耗時(shí)和好性能,所以在優(yōu)化過(guò)程中先從操作入手。
接觸Vue有一段時(shí)間了,但是對(duì)于其雙向綁定的實(shí)現(xiàn)一直是似懂非懂,今天看到一篇寫(xiě)的比較好的文章 傳送門(mén)1 根據(jù)原作者的指導(dǎo)自己也去實(shí)現(xiàn)了一遍簡(jiǎn)單的 demo (本文的demo均基于Object.defineProperty 實(shí)現(xiàn)數(shù)據(jù)劫持,利用了對(duì)Vue.js實(shí)現(xiàn)雙向綁定的思想)
[注]本文所有圖片均來(lái)自于:傳送門(mén)2
前言 幾種主流的雙向綁定1.發(fā)布-訂閱模式
2.臟值檢測(cè)
通過(guò)對(duì)比數(shù)據(jù)是否有變更,來(lái)決定是否更新視圖。最簡(jiǎn)單的可以通過(guò)定時(shí)輪詢(xún)?nèi)z測(cè)數(shù)據(jù)的變動(dòng)。當(dāng)然Google不會(huì)這么low, Angular 只有在指定事件觸發(fā)時(shí)進(jìn)入臟值檢測(cè):
DOM事件,比如用戶(hù)輸入文本點(diǎn)擊按鈕等(ng-click)
XHR響應(yīng)事件
瀏覽器 Location 變更
Timer事件
執(zhí)行 $digidt() 或 $apply()
3.數(shù)據(jù)劫持
Vue.js 采用的是 數(shù)據(jù)劫持+發(fā)布/訂閱模式 的方式,通過(guò) Object.defineProperty() 來(lái)劫持各個(gè)屬性的 setter/getter, 在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者(Wacther), 觸發(fā)相應(yīng)的監(jiān)聽(tīng)回調(diào)。下圖展示了Vue實(shí)現(xiàn)雙向綁定的流程
進(jìn)階版demo雙向綁定最最最初級(jí)demo
DOM操作是非常耗時(shí)和好性能,所以在優(yōu)化過(guò)程中先從DOM操作入手。因?yàn)楸闅v解析過(guò)程中有多次DOM操作,為了提高性能和效率,需要一種方法來(lái)避免對(duì)DOM元素的直接封裝操作。在Vue使用 DocumentFragment 作為替代容器。
DocumentFragment 接口表示一個(gè)沒(méi)有父級(jí)文件的最小文檔對(duì)象。它被當(dāng)做一個(gè)輕量版本的Document 使用。所以使用 DocumentFragment 代替DOM直接處理,可以提高性能和速度。
//將傳入 node 的子節(jié)點(diǎn)進(jìn)行劫持,經(jīng)過(guò)處理后重新掛載回目標(biāo)節(jié)點(diǎn) function convertNode(node,vm){ var fragment = document.createDocumentFragment(), child while(child = node.firstChild){ //將原生節(jié)點(diǎn)拷貝到 fragment,并刪除之前的child節(jié)點(diǎn) fragment.appendChild(child) } return fragment } var dom = convertNode(document.getElementById("app")) document.getElmentById("app").appendChild(dom)實(shí)現(xiàn)Complie解析模板指令
Complie 主要做的事情就是解析模板指令,將模板中的變量替換為數(shù)據(jù)。所以要遍歷整個(gè)DOM樹(shù),進(jìn)行掃描解析編譯,調(diào)用對(duì)應(yīng)的指令渲染函數(shù)進(jìn)行渲染,并調(diào)用對(duì)應(yīng)的指令更新函數(shù)進(jìn)行綁定
{{text}}
function convertNode(node,vm){ //... while(child = node.firstChild){ Compile(child,vm) fragment.appendChild(child) } return fragment } function Compile(node,vm){ var reg = /{{(.*)}}/ if(node.nodeType===1){ var attr = node.attributes //對(duì)所有屬性進(jìn)行解析 for(var i=0;iViewModel 層向 View 層的數(shù)據(jù)綁定 接下來(lái)實(shí)現(xiàn)一個(gè) Xin 構(gòu)造器,通過(guò) Compile 來(lái)解析模板指令,通過(guò) Observer 監(jiān)聽(tīng)屬性數(shù)據(jù)的變化實(shí)現(xiàn) Model 層向 View 層的數(shù)據(jù)綁定
function Xin(options){ this.data = options.data Observer(this.data,this) var id = options.el var dom = convertNode(document.getElementById(id),this) document.getElementById(id).appendChild(dom) }新建一個(gè) vm 實(shí)例來(lái)測(cè)試一下 Model --> View 的綁定情況
var vm = new Xin({ el:"app", data:{ text:"Hello MVVM" } })View 層向 viewModel 層的數(shù)據(jù)綁定實(shí)際上,在 Observer 中我們已經(jīng)通過(guò) 數(shù)據(jù)劫持 實(shí)現(xiàn)了監(jiān)聽(tīng)每個(gè)數(shù)據(jù)的變化,在控制臺(tái)打印 console.log(val) 就可以實(shí)時(shí)看到數(shù)據(jù)的變化。所以接下來(lái)實(shí)現(xiàn)的關(guān)鍵就是怎么用監(jiān)聽(tīng)到的數(shù)據(jù)去更新視圖。
在這里,我們?nèi)?shí)現(xiàn)一個(gè) Wacther 可以將它理解為觀(guān)察者 ,他的作用是能夠接收從 Observer 發(fā)過(guò)來(lái)的屬性變動(dòng)通知, 然后根據(jù)屬性的變動(dòng)更新視圖 update。在監(jiān)聽(tīng)過(guò)程中,為所有的 data 屬性生成一個(gè)主題對(duì)象 Dep,Dep中包含需要維護(hù)的觀(guān)察者列表。每當(dāng)主題對(duì)象狀態(tài)發(fā)生變化時(shí),其相關(guān)依賴(lài)都會(huì)得到通知,并且被自動(dòng)更新(數(shù)據(jù)變動(dòng)會(huì)觸發(fā)notify,再調(diào)用訂閱者的update() 方法)
function Dep(){ this.subs=[] //訂閱者隊(duì)列 } Dep.prototype={ addSub:function(sub){ this.subs.push(sub) }, notify:function(){ this.subs.forEach(function(sub){ sub.update() }) } } funcion Watcher(vm,node,bindName){ //將全局Dep.target設(shè)置為當(dāng)前頁(yè)面元素node Dep.target = this //完成watcher的初始化 this.name = bindName this.node = node this.vm = vm this.update() //初次綁定時(shí)進(jìn)行更新 Dep.target = null //保證Dep.target唯一 } Watcher.prototype = { get:function(){ this.value = this.vm.data[this.name] }, update:function(){ this.get() this.node.nodeValue = this.value } } function Observer(obj,vm){ //... Object.defineProperty(obj,prop,{ get:function(){...}, set:function(newVal){ if(val == newVal) return val = newVal //data屬性被修改,由dep觸發(fā)view層更新 dep.notify() } }) }考慮這樣一個(gè)問(wèn)題,什么時(shí)候會(huì)有雙向綁定? viewModel --> view 可能會(huì)發(fā)生在所有類(lèi)型的DOM節(jié)點(diǎn)上,而 view --> viewModel 只能發(fā)生在 input, select, textarea 等交互控件上。所以將文本節(jié)點(diǎn)包裝成 Watcher , 添加相關(guān)元素的觀(guān)察者列表中,Watcher 負(fù)責(zé)更新頁(yè)面元素
function Compile(node,vm){ //... if(node.nodeType ===3){ //文本節(jié)點(diǎn)類(lèi)型 if(reg.test(node.nodeValue)){ var bindName = RegExp.$1.trim() new Watcher = (vm,node,bindName) //為該頁(yè)面元素node生產(chǎn)watcher } } }更新本文中實(shí)現(xiàn)模板渲染的方法借鑒了 Vue 1.x 中實(shí)現(xiàn)模板渲染的方法。
資料參考
Vue 2.x 模板渲染 方法借鑒React 中的 VirtualDOM,基于 VirtualDOM。 Vue 2.x 還支持服務(wù)端渲染SSR1.https://github.com/DMQ/mvvm#_2
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/91898.html
摘要:廢話(huà)不多說(shuō)直接看效果圖代碼很好理解,但是在看代碼之前需要知道雙向綁定的原理其實(shí)就是基于實(shí)現(xiàn)的雙向綁定官方傳送門(mén)這里我們用官方的話(huà)來(lái)說(shuō)方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回這個(gè)對(duì)象。 廢話(huà)不多說(shuō)直接看效果圖 showImg(https://segmentfault.com/img/bVbiYY5?w=282&h=500); 代碼很好理解,但是在看代碼之前...
摘要:廢話(huà)不多說(shuō)直接看效果圖代碼很好理解,但是在看代碼之前需要知道雙向綁定的原理其實(shí)就是基于實(shí)現(xiàn)的雙向綁定官方傳送門(mén)這里我們用官方的話(huà)來(lái)說(shuō)方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回這個(gè)對(duì)象。 廢話(huà)不多說(shuō)直接看效果圖 showImg(https://segmentfault.com/img/bVbiYY5?w=282&h=500); 代碼很好理解,但是在看代碼之前...
摘要:對(duì)于前端,有時(shí)候需要實(shí)現(xiàn)視圖層和數(shù)據(jù)層的雙向綁定例如當(dāng)前流行的各種框架和類(lèi)庫(kù)。為代表前端數(shù)據(jù)劫持。參考資料實(shí)現(xiàn)數(shù)據(jù)雙向綁定的三種方式談?wù)勚械碾p向數(shù)據(jù)綁定非常簡(jiǎn)單的雙向數(shù)據(jù)綁定框架三 對(duì)于前端,有時(shí)候需要實(shí)現(xiàn)視圖層和數(shù)據(jù)層的雙向綁定(two-way-binding), 例如當(dāng)前流行的各種框架和類(lèi)庫(kù):Vue.js、Angular.js、React.js。 然而,他們最原始的實(shí)現(xiàn)方式其實(shí)都相...
閱讀 3629·2021-09-22 15:50
閱讀 3289·2019-08-30 15:54
閱讀 2821·2019-08-30 14:12
閱讀 3122·2019-08-30 11:22
閱讀 2145·2019-08-29 11:16
閱讀 3633·2019-08-26 13:43
閱讀 1286·2019-08-23 18:33
閱讀 978·2019-08-23 18:32