摘要:上一篇寫了實現框架的一些基本概念本篇用代碼來實現一個完整的框架思考假設有如下代碼,里面的會和試圖中的一一映射,修改的值,會直接引起試圖中對應數據的變化如何實現上述呢回想下這篇講的觀察者模式和數據監聽主題是什么觀察者是什么觀察者何時訂閱主題主
上一篇寫了實現 MVVM 框架的一些基本概念
本篇用代碼來實現一個完整的 MVVM 框架
思考假設有如下代碼,data里面的name會和試圖中的{{name}}——一一映射,修改data的值,會直接引起試圖中對應數據的變化
{{name}}
如何實現上述 MVVM 呢?
回想下這篇講的觀察者模式和數據監聽:
主題(subject)是什么?
觀察者(observer)是什么?
觀察者何時訂閱主題?
主題何時通知更新?
簡單回答下:
上面例子中,主題應該是data的name屬性,觀察者是試圖里的{{name}},當一開始執行 MVVM 初始化(根據el解析模板發現{{name}})的時候訂閱主題,當data.name發生改變的時候,通知觀察者更新內容,我們可以在一開始監控data.name,當用戶修改data.name的時候調用主題的subject.ontify。
有如下 HTML
{{name}}"is age is {{age}}
從上面 HTML 中我們看出,操作的節點是div#app,需要的數據是name和age,所以實例化 MVVM 可以需要傳遞兩個參數element和data
let vm = MVVM({ element:"#app", data:{ name:"zhangsan", age:20 } }) setInterval(function(){ vm.data.age++ },2000)
我們 MVVM 的構造函數應該怎么寫呢?我們只需要做兩件事情:
我們需要觀察這些數據,當以后這些數據變動時,會做一些事情去調用
需要解析這個模板,把模板中的一些符號替換成對應的數據
初始化是必須做的,將實例化的數據存在自身上面,后面要用,這里就不敘述了。
class MVVM{ constructor(options){ init(options) observe(this.data) this.compile() } init(options){ this.element = document.querySelector(options.element) this.data = options.data } }
先看compile這個方法,它就是在編譯頁面中的節點,如果節點里還有孩子,需要再去遍歷這些孩子,如果遍歷到文本,就進行下一步文本替換。
compile(){ //雖然這里可以直接對節點進行遍歷,但最好還是分開來比較好點 this.traverse(this.el) } traverse(node){ //對節點進行遍歷,如果遇到元素節點,用遞歸繼續遍歷直到遍歷到都是文本為止,進行下一步頁面渲染 node.childNodes.forEach(childNode=>{ if(childNode.nodeType === 1){ this.traverse(childNode) }else if(childNode.nodeType === 3){ this.renderText(childNode) } }) } renderText(textNode){ //到這一步,已經獲取到頁面中的文本了,用正則去匹配 let reg = /{{([^}]*)}}/g //正則或者可以寫稱/{{(.+?)}}/g let match while(match = reg.exec(textNode.textContent)){ //將匹配到的內容賦值給match,match是一個數組 let raw = match[0] let key = match[1].trim() textNode.textContent = textNode.textContent.replace(raw,this.data[key]) //頁面渲染 new Observer(this,key,function(val,oldVal){ textNode.textContent = textNode.textContent.replace(oldVal,val) }) //創建一個觀察者 } }
假設用戶去修改數據時,那數據該如何進行實時的變動呢?
這里就引入了觀察者和主題的概念,我們在解析的過程中創建一個個觀察者,這個觀察者就觀察這個屬性,解析到下個屬性在創建一個觀察者,并觀察這個屬性。
觀察這個屬性就是訂閱這個主題,我們在this.compile()解析完后創建一個觀察者,它有個方法,如果這個屬性變動,我就會修改頁面。
function observe(data){ if(!data || typeof data !== "object")return for(let key in data){ let val = data[key] let subject = new Subject() //創建主題 if(typeof val === "object"){ observe(val) } Object.defineProperty(data,key,{ configurable:true, enumerable:true, get(){ return val }, set(newVal){ val = newVal subject.notify() } }) } }
問題是創建了觀察者后什么時候去觀察這個主題?
在創建后立刻觀察這個主題,可是主題在哪?觀察者有了,就是剛剛new的時候。主題是在observe遍歷屬性時創建的。主題存在在observe局部變量中,外面是訪問不到的,那觀察者怎樣訂閱這個主題呢?
思考到這里發現行不通了,就需要換種思路了。
當創建觀察者時,會調用getValue(),它做什么事情呢,把我設置為場上權限最高的觀察者,因為頁面中有很多觀察者,此時this.key,就是我要訂閱的主題,當我調用this.vm.data[this.key]就等于調用了observe的get方法,因為剛剛我已經把觀察者設置為場上權限最高者,此時currentObserver是存在的,這時觀察者就開始訂閱主題,訂閱的之后在把權限去掉
let currentObserver = null class Observer{ constructor(vm,key,cb){ this.subjects = {} this.vm = vm this.key = key this.cb = cb this.value = this.getValue() } getValue(){ currentObserver = this let value = this.vm.data[this.key] currentObserver = null return value } }
通過currentObserver去訂閱主題,因為在創建觀察者時調用了getValue方法,把currentObserver設置為Observer,通過它去訂閱主題
get:function(){ if(currentObserver){ currentObserver.subscribeTo(subject) } }
主題的構造函數
let id = 0 class Subject{ constructor(){ this.id = id++ this.observers = [] } addObserver(observer){ this.observers.push(observer) } notify(){ this.observers.forEach(observer=>{ observer.update() }) } }
添加觀察者
subscribeTo(subject){ if(!this.subjects[subject.id]){ subject.addObserver(this) this.subjects[subject.id] = subject } }
更新頁面數據,舊值通過自身屬性獲取,新值通過getValue方法獲取
update(){ let oldVal = this.value let value = this.getValue() if(value !== oldVal){ this.value = value this.cb.call(this.vm,value,oldVal) } }
最后貼上完整的單向綁定的代碼
function observe(data){ if(!data || typeof data !== "object")return for(let key in data){ let val = data[key] let subject = new Subject() if(typeof val === "object"){ observe(val) } Object.defineProperty(data,key,{ configurable:true, enumerable:true, get(){ if(currentObserver){ currentObserver.subscribeTo(subject) } return val }, set(newVal){ val = newVal subject.notify() } }) } } let id = 0 class Subject{ constructor(){ this.id = id++ this.observers = [] } addObserver(observer){ this.observers.push(observer) } notify(){ this.observers.forEach(observer=>{ observer.update() }) } } let currentObserver = null class Observer{ constructor(vm,key,cb){ this.subjects = {} this.vm = vm this.key = key this.cb = cb this.value = this.getValue() } update(){ let oldVal = this.value let value = this.getValue() if(value !== oldVal){ this.value = value this.cb.call(this.vm,value,oldVal) } } subscribeTo(subject){ if(!this.subjects[subject.id]){ subject.addObserver(this) this.subjects[subject.id] = subject } } getValue(){ currentObserver = this let value = this.vm.data[this.key] currentObserver = null return value } } class mvvm{ constructor(options){ this.init(options) observe(this.data) this.compile() } init(options){ this.el = document.querySelector(options.el) this.data = options.data } compile(){ this.traverse(this.el) } traverse(node){ node.childNodes.forEach(childNode=>{ if(childNode.nodeType === 1){ this.traverse(childNode) }else if(childNode.nodeType === 3){ this.renderText(childNode) } }) } renderText(textNode){ let reg = /{{([^}]*)}}/g let match while(match = reg.exec(textNode.textContent)){ let raw = match[0] let key = match[1].trim() textNode.textContent = textNode.textContent.replace(raw,this.data[key]) new Observer(this,key,function(val,oldVal){ textNode.textContent = textNode.textContent.replace(oldVal,val) }) } } } let vm = new mvvm({ el:"#app", data:{ name:"uccs", age:20 } }) setInterval(function(){ vm.data.age++ },2000)
本篇詳細講述了 MVVM 單項綁定的原理,下一篇講述雙向綁定
用原生 JS 實現 MVVM 框架MVVM 框架系列:
用原生 JS 實現 MVVM 框架1——觀察者模式和數據監控
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97279.html
摘要:模塊則負責維護,以及各個模塊間的調度思考題了解了的實現機制,你能否自己動手也試著用百來行代碼實現一個庫呢好了本教程第一部分設計篇就寫到這里,具體請移步下一篇教學向行代碼教你實現一個低配版的庫代碼篇我會用給出一版實現。 適讀人群 本文適合對MVVM有一定了解(如有主流框架ng,vue等使用經驗配合本文服用則效果更佳),雖然會用這類框架,但是對框架底層核心實現又不太清楚,或者能說出個所以然...
摘要:綁定實現的歷史綁定的基礎是事件。但臟檢查機制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機制的嘗試,在中引入。掙扎了一段時間后谷歌團隊宣布收回的提議,并在中完全刪除了實現。自然全軍覆沒其他各大瀏覽器實現的時間也較晚。 綁定實現的歷史 綁定的基礎是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發 MVVM 框架的首要問題。主流框架的處理有一下三...
摘要:綁定實現的歷史綁定的基礎是事件。但臟檢查機制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機制的嘗試,在中引入。掙扎了一段時間后谷歌團隊宣布收回的提議,并在中完全刪除了實現。自然全軍覆沒其他各大瀏覽器實現的時間也較晚。 綁定實現的歷史 綁定的基礎是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發 MVVM 框架的首要問題。主流框架的處理有一下三...
摘要:是分發器,是數據與邏輯處理器,會在注冊針對各個命令字的響應回調函數。當按如下方式觸發回調時,回調函數具備事件的特性。 本系列博文從 Shadow Widget 作者的視角,解釋該框架的設計要點。本篇解釋 Shadow Widget 在 MVC、MVVM、Flux 框架之間如何做選擇。 showImg(https://segmentfault.com/img/bVOODj?w=380&h...
閱讀 2624·2021-11-12 10:36
閱讀 2263·2021-08-23 09:47
閱讀 1683·2019-08-30 15:44
閱讀 1405·2019-08-30 14:10
閱讀 2246·2019-08-29 16:52
閱讀 2340·2019-08-29 16:40
閱讀 1590·2019-08-29 16:17
閱讀 2412·2019-08-26 13:21