這篇文章主要介紹了MVVM 框架解析之雙向綁定,現在分享給大家,也給大家做個參考。
MVVM 框架
近年來前端一個明顯的開發趨勢就是架構從傳統的 MVC 模式遷移到 MVVM 模式。在傳統的 MVC 下,目前前端和後端發生資料互動後會刷新整個頁面,導致比較差的使用者體驗。因此我們透過 Ajax 的方式和網關 REST API 作通訊,非同步的刷新頁面的某個區塊,來優化和提升體驗。
MVVM 框架基本概念
#在MVVM 框架中,View(檢視) 和Model(資料) 是不可以直接通訊的,在它們之間存在著ViewModel 這個中間介充當觀察者的角色。當使用者操作 View(視圖),ViewModel 感知到變化,然後通知 Model 發生相應改變;反之當 Model(數據) 發生改變,ViewModel 也能感知到變化,使 View 作出相應更新。這個一來一回的過程就是我們所熟知的雙向綁定。
MVVM 框架的應用場景
MVVM 框架的好處顯而易見:當前端對資料進行操作的時候,可以透過Ajax 請求對資料持久化,只需改變dom 裡需要改變的那部分資料內容,而不必刷新整個頁面。特別是在行動端,刷新頁面的代價太昂貴。雖然有些資源會被緩存,但是頁面的 dom、css、js 都會被瀏覽器重新解析一遍,因此行動端頁面通常會被做成 SPA 單頁應用程式。由此在這基礎上誕生了許多 MVVM 框架,像是 React.js、Vue.js、Angular.js 等等。
MVVM 框架的簡單實作
模擬Vue 的雙向綁定流,實作了一個簡單的MVVM 框架,從上圖可以看出虛線方形中就是先前提到的ViewModel 中間介層,它扮演觀察者的角色。另外可以發現雙向綁定流中的View 到Model 其實是透過input 的事件監聽函數實現的,如果換成React(單向綁定流) 的話,它在這一步交給狀態管理工具(比如Redux)來實現。另外雙向綁定流中的Model 到View 其實各個MVVM 框架實現的都是大同小異的,都用到的核心方法是Object.defineProperty(),透過這個方法可以進行資料劫持,當資料改變時可以捕捉到相應變化,從而進行後續的處理。
Mvvm(入口檔案) 的實作
一般會這樣呼叫Mvvm 框架
const vm = new Mvvm({ el: '#app', data: { title: 'mvvm title', name: 'mvvm name' }, })
但是這樣子的話,如果要得到title 屬性就要形如vm.data.title 這樣取得,為了讓vm.title 就能獲得title 屬性,從而在Mvvm 的prototype 上加上一個代理方法,程式碼如下:
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 实现了 vm.data.title 和 vm.title 的值同步 return self.data[key] }, set: function (newValue) { self.data[key] = newValue } }) } }
實作了代理方法後,就步入主流程的實作
function Mvvm (options) { this.data = options.data // ... observe(this.data) new Compile(options.el, this) }
observer(觀察者) 的實作
observer 的職責是監聽Model(JS 對象) 的變化,最核心的部分就是用到了Object.defineProperty() 的get 和set 方法,當要取得Model(JS 物件) 的值時,會自動呼叫get 方法;當改變了Model(JS 物件) 的值時,會自動呼叫set 方法;從而實現了對資料的劫持,程式碼如下所示。
let data = { number: 0 } observe(data) data.number = 1 // 值发生变化 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) // 遍历嵌套对象 Object.defineProperty(data, key, { get: function() { return value }, set: function(newValue) { if (value !== newValue) { console.log('值发生变化', 'newValue:' + newValue + ' ' + 'oldValue:' + value) value = newValue } } }) }
執行程式碼,可以看到控制台輸出 值變更 newValue:1 oldValue:0,至此就完成了 observer 的邏輯。
Dep(訂閱者陣列) 和watcher(訂閱者) 的關係
觀測到變化後,我們總是要通知給特定的人群,讓他們做出相應的處理吧。為了更方便地理解,我們可以把訂閱當成是訂閱了一個微信公眾號,當微信公眾號的內容有更新時,那麼它會把內容推送(update) 到訂閱了它的人。
那麼訂閱了同個微信公眾號的人有成千上萬個,那麼首先想到的就是要new Array() 去存放這些人(html 節點)吧。於是就有瞭如下程式碼:
// 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) // 遍历嵌套对象 Object.defineProperty(data, key, { get: function() { if (Dep.target) { // 往订阅器添加订阅者 dep.addSub(Dep.target) } return value }, set: function(newValue) { if (value !== newValue) { console.log('值发生变化', 'newValue:' + newValue + ' ' + 'oldValue:' + value) value = newValue dep.notify() } } }) }
初看程式碼也比較順暢了,但可能會卡在 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) // 触发 compile 中的回调 } }, get: function() { Dep.target = this // 缓存自己 const value = this.vm.data[this.exp] // 强制执行监听器里的 get 函数 Dep.target = null // 释放自己 return value } }
从代码中可以看到当构造 Watcher 实例时,会调用 get() 方法,接着重点关注 const value = this.vm.data[this.exp] 这句,前面说了当要获取 Model(JS 对象) 的值时,会自动调用 Object.defineProperty 的 get 方法,也就是当执行完这句的时候,Dep.target 的值传进了 observer.js 中的 Object.defineProperty 的 get 方法中。同时也一目了然地在 Watcher.prototype 中发现了 update 方法,其作用即触发 compile 中绑定的回调来更新界面。至此解释了 Observer 中 Dep.target 和 sub.update 的由来。
来归纳下 Watcher 的作用,其充当了 observer 和 compile 的桥梁。
1 在自身实例化的过程中,往订阅器(dep) 中添加自己
2 当 model 发生变动,dep.notify() 通知时,其能调用自身的 update 函数,并触发 compile 绑定的回调函数实现视图更新
最后再来看下生成 Watcher 实例的 compile.js 文件。
compile(编译) 的实现
首先遍历解析的过程有多次操作 dom 节点,为提高性能和效率,会先将跟节点 el 转换成 fragment(文档碎片) 进行解析编译,解析完成,再将 fragment 添加回原来的真实 dom 节点中。代码如下:
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) // 将节点转为 fragment 文档碎片 this.compileElement(this.fragment) // 对 fragment 进行编译解析 this.el.appendChild(this.fragment) } }, nodeToFragment: function(el) { const fragment = document.createDocumentFragment() let child = el.firstChild // △ 第一个 firstChild 是 text while(child) { fragment.appendChild(child) child = el.firstChild } return fragment }, compileElement: function(el) {...}, }
这个简单的 mvvm 框架在对 fragment 编译解析的过程中对 {{}} 文本元素、v-on:click 事件指令、v-model 指令三种类型进行了相应的处理。
Compile.prototype = { init: function() { if (this.el) { this.fragment = this.nodeToFragment(this.el) // 将节点转为 fragment 文档碎片 this.compileElement(this.fragment) // 对 fragment 进行编译解析 this.el.appendChild(this.fragment) } }, nodeToFragment: function(el) {...}, compileElement: function(el) {...}, compileText: function (node, exp) { // 对文本类型进行处理,将 {{abc}} 替换掉 const self = this const initText = this.vm[exp] this.updateText(node, initText) // 初始化 new Watcher(this.vm, exp, function(value) { // 实例化订阅者 self.updateText(node, value) }) }, compileEvent: function (node, vm, exp, dir) { // 对事件指令进行处理 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) { // 对 v-model 进行处理 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 // 实现 view 到 model 的绑定 }) }, }
在上述代码的 compileTest 函数中看到了期盼已久的 Watcher 实例化,对 Watcher 作用模糊的朋友可以往上回顾下 Watcher 的作用。另外在 compileModel 函数中看到了本文最开始提到的双向绑定流中的 View 到 Model 是借助 input 监听事件变化实现的。
项目地址
本文记录了些阅读 mvvm 框架源码关于双向绑定的心得,并动手实践了一个简版的 mvvm 框架,不足之处在所难免,欢迎指正。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上是MVVM框架如何解析雙向綁定的詳細內容。更多資訊請關注PHP中文網其他相關文章!

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SublimeText3漢化版
中文版,非常好用

Dreamweaver Mac版
視覺化網頁開發工具