這篇文章主要介紹了前端MVVM框架中雙向綁定的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都做網(wǎng)站、網(wǎng)站制作、友好網(wǎng)絡(luò)推廣、成都小程序開發(fā)、友好網(wǎng)絡(luò)營(yíng)銷、友好企業(yè)策劃、友好品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供友好建站搭建服務(wù),24小時(shí)服務(wù)熱線:13518219792,官方網(wǎng)址:www.rwnh.cn
MVVM 框架基本概念
在 MVVM 框架中,View(視圖) 和 Model(數(shù)據(jù)) 是不可以直接通訊的,在它們之間存在著 ViewModel 這個(gè)中間介充當(dāng)著觀察者的角色。當(dāng)用戶操作 View(視圖),ViewModel 感知到變化,然后通知 Model 發(fā)生相應(yīng)改變;反之當(dāng) Model(數(shù)據(jù)) 發(fā)生改變,ViewModel 也能感知到變化,使 View 作出相應(yīng)更新。這個(gè)一來一回的過程就是我們所熟知的雙向綁定。
MVVM 框架的應(yīng)用場(chǎng)景
MVVM 框架的好處顯而易見:當(dāng)前端對(duì)數(shù)據(jù)進(jìn)行操作的時(shí)候,可以通過 Ajax 請(qǐng)求對(duì)數(shù)據(jù)持久化,只需改變 dom 里需要改變的那部分?jǐn)?shù)據(jù)內(nèi)容,而不必刷新整個(gè)頁面。特別是在移動(dòng)端,刷新頁面的代價(jià)太昂貴。雖然有些資源會(huì)被緩存,但是頁面的 dom、css、js 都會(huì)被瀏覽器重新解析一遍,因此移動(dòng)端頁面通常會(huì)被做成 SPA 單頁應(yīng)用。由此在這基礎(chǔ)上誕生了很多 MVVM 框架,比如 React.js、Vue.js、Angular.js 等等。
MVVM 框架的簡(jiǎn)單實(shí)現(xiàn)
模擬 Vue 的雙向綁定流,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的MVVM 框架,從上圖中可以看出虛線方形中就是之前提到的 ViewModel 中間介層,它充當(dāng)著觀察者的角色。另外可以發(fā)現(xiàn)雙向綁定流中的 View 到 Model 其實(shí)是通過 input 的事件監(jiān)聽函數(shù)實(shí)現(xiàn)的,如果換成 React(單向綁定流) 的話,它在這一步交給狀態(tài)管理工具(比如 Redux)來實(shí)現(xiàn)。另外雙向綁定流中的 Model 到 View 其實(shí)各個(gè) MVVM 框架實(shí)現(xiàn)的都是大同小異的,都用到的核心方法是 Object.defineProperty(),通過這個(gè)方法可以進(jìn)行數(shù)據(jù)劫持,當(dāng)數(shù)據(jù)發(fā)生變化時(shí)可以捕捉到相應(yīng)變化,從而進(jìn)行后續(xù)的處理。
Mvvm(入口文件) 的實(shí)現(xiàn)
一般會(huì)這樣調(diào)用 Mvvm 框架
const vm = new Mvvm({ el: '#app', data: { title: 'mvvm title', name: 'mvvm name' }, })
但是這樣子的話,如果要得到 title 屬性就要形如 vm.data.title 這樣取得,為了讓 vm.title 就能獲得 title 屬性,從而在 Mvvm 的 prototype 上加上一個(gè)代理方法,代碼如下:
function Mvvm (options) { this.data = options.data const self = this Object.keys(this.data).forEach(key => self.proxyKeys(key) ) } Mvvm.prototype = { proxyKeys: function(key) { const self = this Object.defineProperty(this, key, { get: function () { // 這里的 get 和 set 實(shí)現(xiàn)了 vm.data.title 和 vm.title 的值同步 return self.data[key] }, set: function (newValue) { self.data[key] = newValue } }) } }
實(shí)現(xiàn)了代理方法后,就步入主流程的實(shí)現(xiàn)
function Mvvm (options) { this.data = options.data // ... observe(this.data) new Compile(options.el, this) }
observer(觀察者) 的實(shí)現(xiàn)
observer 的職責(zé)是監(jiān)聽 Model(JS 對(duì)象) 的變化,最核心的部分就是用到了 Object.defineProperty() 的 get 和 set 方法,當(dāng)要獲取 Model(JS 對(duì)象) 的值時(shí),會(huì)自動(dòng)調(diào)用 get 方法;當(dāng)改動(dòng)了 Model(JS 對(duì)象) 的值時(shí),會(huì)自動(dòng)調(diào)用 set 方法;從而實(shí)現(xiàn)了對(duì)數(shù)據(jù)的劫持,代碼如下所示。
let data = { number: 0 } observe(data) data.number = 1 // 值發(fā)生變化 function observe(data) { if (!data || typeof(data) !== 'object') { return } const self = this Object.keys(data).forEach(key => self.defineReactive(data, key, data[key]) ) } function defineReactive(data, key, value) { observe(value) // 遍歷嵌套對(duì)象 Object.defineProperty(data, key, { get: function() { return value }, set: function(newValue) { if (value !== newValue) { console.log('值發(fā)生變化', 'newValue:' + newValue + ' ' + 'oldValue:' + value) value = newValue } } }) }
運(yùn)行代碼,可以看到控制臺(tái)輸出 值發(fā)生變化 newValue:1 oldValue:0,至此就完成了 observer 的邏輯。
Dep(訂閱者數(shù)組) 和 watcher(訂閱者) 的關(guān)系
觀測(cè)到變化后,我們總要通知給特定的人群,讓他們做出相應(yīng)的處理吧。為了更方便地理解,我們可以把訂閱當(dāng)成是訂閱了一個(gè)微信公眾號(hào),當(dāng)微信公眾號(hào)的內(nèi)容有更新時(shí),那么它會(huì)把內(nèi)容推送(update) 到訂閱了它的人。
那么訂閱了同個(gè)微信公眾號(hào)的人有成千上萬個(gè),那么首先想到的就是要 new Array() 去存放這些人(html 節(jié)點(diǎn))吧。于是就有了如下代碼:
// observer.js function Dep() { this.subs = [] // 存放訂閱者 } Dep.prototype = { addSub: function(sub) { // 添加訂閱者 this.subs.push(sub) }, notify: function() { // 通知訂閱者更新 this.subs.forEach(function(sub) { sub.update() }) } } function observe(data) {...} function defineReactive(data, key, value) { var dep = new Dep() observe(value) // 遍歷嵌套對(duì)象 Object.defineProperty(data, key, { get: function() { if (Dep.target) { // 往訂閱器添加訂閱者 dep.addSub(Dep.target) } return value }, set: function(newValue) { if (value !== newValue) { console.log('值發(fā)生變化', 'newValue:' + newValue + ' ' + 'oldValue:' + value) value = newValue dep.notify() } } }) }
初看代碼也比較順暢了,但可能會(huì)卡在 Dep.target 和 sub.update,由此自然而然地將目光移向 watcher,
// watcher.js function Watcher(vm, exp, cb) { this.vm = vm this.exp = exp this.cb = cb this.value = this.get() } Watcher.prototype = { update: function() { this.run() }, run: function() { // ... if (value !== oldVal) { this.cb.call(this.vm, value) // 觸發(fā) compile 中的回調(diào) } }, get: function() { Dep.target = this // 緩存自己 const value = this.vm.data[this.exp] // 強(qiáng)制執(zhí)行監(jiān)聽器里的 get 函數(shù) Dep.target = null // 釋放自己 return value } }
從代碼中可以看到當(dāng)構(gòu)造 Watcher 實(shí)例時(shí),會(huì)調(diào)用 get() 方法,接著重點(diǎn)關(guān)注 const value = this.vm.data[this.exp] 這句,前面說了當(dāng)要獲取 Model(JS 對(duì)象) 的值時(shí),會(huì)自動(dòng)調(diào)用 Object.defineProperty 的 get 方法,也就是當(dāng)執(zhí)行完這句的時(shí)候,Dep.target 的值傳進(jìn)了 observer.js 中的 Object.defineProperty 的 get 方法中。同時(shí)也一目了然地在 Watcher.prototype 中發(fā)現(xiàn)了 update 方法,其作用即觸發(fā) compile 中綁定的回調(diào)來更新界面。至此解釋了 Observer 中 Dep.target 和 sub.update 的由來。
來歸納下 Watcher 的作用,其充當(dāng)了 observer 和 compile 的橋梁。
1 在自身實(shí)例化的過程中,往訂閱器(dep) 中添加自己
2 當(dāng) model 發(fā)生變動(dòng),dep.notify() 通知時(shí),其能調(diào)用自身的 update 函數(shù),并觸發(fā) compile 綁定的回調(diào)函數(shù)實(shí)現(xiàn)視圖更新
最后再來看下生成 Watcher 實(shí)例的 compile.js 文件。
compile(編譯) 的實(shí)現(xiàn)
首先遍歷解析的過程有多次操作 dom 節(jié)點(diǎn),為提高性能和效率,會(huì)先將跟節(jié)點(diǎn) el 轉(zhuǎn)換成 fragment(文檔碎片) 進(jìn)行解析編譯,解析完成,再將 fragment 添加回原來的真實(shí) dom 節(jié)點(diǎn)中。代碼如下:
function Compile(el, vm) { this.vm = vm this.el = document.querySelector(el) this.fragment = null this.init() } Compile.prototype = { init: function() { if (this.el) { this.fragment = this.nodeToFragment(this.el) // 將節(jié)點(diǎn)轉(zhuǎn)為 fragment 文檔碎片 this.compileElement(this.fragment) // 對(duì) fragment 進(jìn)行編譯解析 this.el.appendChild(this.fragment) } }, nodeToFragment: function(el) { const fragment = document.createDocumentFragment() let child = el.firstChild // △ 第一個(gè) firstChild 是 text while(child) { fragment.appendChild(child) child = el.firstChild } return fragment }, compileElement: function(el) {...}, }
這個(gè)簡(jiǎn)單的 mvvm 框架在對(duì) fragment 編譯解析的過程中對(duì) {{}} 文本元素、v-on:click 事件指令、v-model 指令三種類型進(jìn)行了相應(yīng)的處理。
Compile.prototype = { init: function() { if (this.el) { this.fragment = this.nodeToFragment(this.el) // 將節(jié)點(diǎn)轉(zhuǎn)為 fragment 文檔碎片 this.compileElement(this.fragment) // 對(duì) fragment 進(jìn)行編譯解析 this.el.appendChild(this.fragment) } }, nodeToFragment: function(el) {...}, compileElement: function(el) {...}, compileText: function (node, exp) { // 對(duì)文本類型進(jìn)行處理,將 {{abc}} 替換掉 const self = this const initText = this.vm[exp] this.updateText(node, initText) // 初始化 new Watcher(this.vm, exp, function(value) { // 實(shí)例化訂閱者 self.updateText(node, value) }) }, compileEvent: function (node, vm, exp, dir) { // 對(duì)事件指令進(jìn)行處理 const eventType = dir.split(':')[1] const cb = vm.methods && vm.methods[exp] if (eventType && cb) { node.addEventListener(eventType, cb.bind(vm), false) } }, compileModel: function (node, vm, exp) { // 對(duì) v-model 進(jìn)行處理 let val = vm[exp] const self = this this.modelUpdater(node, val) node.addEventListener('input', function (e) { const newValue = e.target.value self.vm[exp] = newValue // 實(shí)現(xiàn) view 到 model 的綁定 }) }, }
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“前端MVVM框架中雙向綁定的示例分析”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來學(xué)習(xí)!
分享名稱:前端MVVM框架中雙向綁定的示例分析
轉(zhuǎn)載注明:http://www.rwnh.cn/article14/jsccge.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動(dòng)態(tài)網(wǎng)站、全網(wǎng)營(yíng)銷推廣、外貿(mào)建站、靜態(tài)網(wǎng)站、域名注冊(cè)、品牌網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)