這篇文章主要介紹如何使用proxy實(shí)現(xiàn)一個(gè)MVVM庫(kù)的方法,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
在岳塘等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站建設(shè)、網(wǎng)站制作 網(wǎng)站設(shè)計(jì)制作按需網(wǎng)站開發(fā),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站設(shè)計(jì),全網(wǎng)營(yíng)銷推廣,成都外貿(mào)網(wǎng)站建設(shè)公司,岳塘網(wǎng)站建設(shè)費(fèi)用合理。
前言
MVVM
是當(dāng)前時(shí)代前端日常業(yè)務(wù)開發(fā)中的必備模式(相關(guān)框架如react
,vue
,angular
等), 使用 MVVM
可以將開發(fā)者的精力更專注于業(yè)務(wù)上的邏輯,而不需要關(guān)心如何操作 dom
。雖然現(xiàn)在都 9012 年了,mvvm
相關(guān)原理的介紹已經(jīng)爛大街了,但出于學(xué)習(xí)基礎(chǔ)知識(shí)的目的(使用 proxy
實(shí)現(xiàn)的 vue
3.0 還在開發(fā)中), 在參考了之前 vue.js
的整體思路之后,自己動(dòng)手實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的通過(guò) proxy
實(shí)現(xiàn)的 mvvm
。
本項(xiàng)目代碼已經(jīng)開源在github,項(xiàng)目正在持續(xù)完善中,歡迎交流學(xué)習(xí),喜歡請(qǐng)點(diǎn)個(gè) star 吧!
最終效果
{{title}}
import MVVM from '@fe_korey/mvvm'; new MVVM({ view: document.getElementById('app'), model: { title: 'hello mvvm!' }, mounted() { console.log('主程編譯完成,歡迎使用MVVM!'); } });
結(jié)構(gòu)概覽
Complier
模塊實(shí)現(xiàn)解析、收集指令,并初始化視圖
Observer
模塊實(shí)現(xiàn)了數(shù)據(jù)的監(jiān)聽,包括添加訂閱者和通知訂閱者
Parser
模塊實(shí)現(xiàn)解析指令,提供該指令的更新視圖的更新方法
Watcher
模塊實(shí)現(xiàn)建立指令與數(shù)據(jù)的關(guān)聯(lián)
Dep
模塊實(shí)現(xiàn)一個(gè)訂閱中心,負(fù)責(zé)收集,觸發(fā)數(shù)據(jù)模型各值的訂閱列表
流程為:Complier
收集編譯好指令后,根據(jù)指令不同選擇不同的Parser
,根據(jù)Parser
在Watcher
中訂閱數(shù)據(jù)的變化并更新初始視圖。Observer
監(jiān)聽數(shù)據(jù)變化然后通知給 Watcher
,Watcher
再將變化結(jié)果通知給對(duì)應(yīng)Parser
里的 update
刷新函數(shù)進(jìn)行視圖的刷新。
模塊詳解
Complier
將整個(gè)數(shù)據(jù)模型 data
傳入Observer
模塊進(jìn)行數(shù)據(jù)監(jiān)聽
this.$data = new Observer(option.model).getData();
循環(huán)遍歷整個(gè) dom
,對(duì)每個(gè) dom
元素的所有指令進(jìn)行掃描提取
function collectDir(element) { const children = element.childNodes; const childrenLen = children.length; for (let i = 0; i < childrenLen; i++) { const node = children[i]; const nodeType = node.nodeType; if (nodeType !== 1 && nodeType !== 3) { continue; } if (hasDirective(node)) { this.$queue.push(node); } if (node.hasChildNodes() && !hasLateCompileChilds(node)) { collectDir(element); } } }
對(duì)每個(gè)指令進(jìn)行編譯,選擇對(duì)應(yīng)的解析器Parser
const parser = this.selectParsers({ node, dirName, dirValue, cs: this });
將得到的解析器Parser
傳入Watcher
,并初始化該 dom
節(jié)點(diǎn)的視圖
const watcher = new Watcher(parser); parser.update({ newVal: watcher.value });
所有指令解析完畢后,觸發(fā) MVVM
編譯完成回調(diào)$mounted()
this.$mounted();
使用文檔碎片document.createDocumentFragment()
來(lái)代替真實(shí) dom
節(jié)點(diǎn)片段,待所有指令編譯完成后,再將文檔碎片追加回真實(shí) dom
節(jié)點(diǎn)
let child; const fragment = document.createDocumentFragment(); while ((child = this.$element.firstChild)) { fragment.appendChild(child); } //解析完后 this.$element.appendChild(fragment); delete $fragment;
Parser
在Complier
模塊編譯后的指令,選擇不同聽解析器解析,目前包括ClassParser
,DisplayParser
,ForParser
,IfParser
,StyleParser
,TextParser
,ModelParser
,OnParser
,OtherParser
等解析模塊。
switch (name) { case 'text': parser = new TextParser({ node, dirValue, cs }); break; case 'style': parser = new StyleParser({ node, dirValue, cs }); break; case 'class': parser = new ClassParser({ node, dirValue, cs }); break; case 'for': parser = new ForParser({ node, dirValue, cs }); break; case 'on': parser = new OnParser({ node, dirName, dirValue, cs }); break; case 'display': parser = new DisplayParser({ node, dirName, dirValue, cs }); break; case 'if': parser = new IfParser({ node, dirValue, cs }); break; case 'model': parser = new ModelParser({ node, dirValue, cs }); break; default: parser = new OtherParser({ node, dirName, dirValue, cs }); }
不同的解析器提供不同的視圖刷新函數(shù)update()
,通過(guò)update
更新dom
視圖
//text.js function update(newVal) { this.el.textContent = _toString(newVal); }
OnParser
解析事件綁定,與數(shù)據(jù)模型中的 methods
字段對(duì)應(yīng)
//詳見 https://github.com/zhaoky/mvvm/blob/master/src/core/parser/on.ts el.addEventListener(handlerType, e => { handlerFn(scope, e); });
ForParser
解析數(shù)組
詳見 https://github.com/zhaoky/mvvm/blob/master/src/core/parser/for.ts
ModelParser
解析雙向綁定,目前支持input[text/password] & textarea
,input[radio]
,input[checkbox]
,select
四種情況的雙向綁定,雙綁原理:
數(shù)據(jù)變化更新表單:跟其他指令更新視圖一樣,通過(guò)update
方法觸發(fā)更新表單的value
function update({ newVal }) { this.model.el.value = _toString(newVal); }
表單變化更新數(shù)據(jù):監(jiān)聽表單變化事件如input
,change
,在回調(diào)里set
數(shù)據(jù)模型
this.model.el.addEventListener('input', e => { model.watcher.set(e.target.value); });
Observer
MVVM
模型中的核心,一般通過(guò) Object.defineProperty
的 get
,set
方法進(jìn)行數(shù)據(jù)的監(jiān)聽,在 get
里添加訂閱者,set
里通知訂閱者更新視圖。在本項(xiàng)目采用 Proxy
來(lái)實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽,好處有三:
Proxy
可以直接監(jiān)聽對(duì)象而非屬性
Proxy
可以直接監(jiān)聽數(shù)組的變化
Proxy
有多達(dá) 13 種攔截方法,查閱
而劣勢(shì)是兼容性問題,且無(wú)法通過(guò) polyfill
磨平。查閱兼容性
注意 Proxy
只會(huì)監(jiān)聽自身的每一個(gè)屬性,如果屬性是對(duì)象,則該對(duì)象不會(huì)被監(jiān)聽,所以需要遞歸監(jiān)聽
設(shè)置監(jiān)聽后,返回一個(gè) Proxy
替代原數(shù)據(jù)對(duì)象
var proxy = new Proxy(data, { get: function(target, key, receiver) { //如果滿足條件則添加訂閱者 dep.addDep(curWatcher); return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { //如果滿足條件則通知訂閱者 dep.notfiy(); return Reflect.set(target, key, value, receiver); } });
Watcher
在 Complier
模塊里對(duì)每一個(gè)解析后的 Parser
進(jìn)行指令與數(shù)據(jù)模型直接的綁定,并觸發(fā) Observer
的 get
監(jiān)聽,添加訂閱者(Watcher
)
this._getter(this.parser.dirValue)(this.scope || this.parser.cs.$data);
當(dāng)數(shù)據(jù)模型變化時(shí),就會(huì)觸發(fā) -> Observer
的 set
監(jiān)聽 -> Dep
的 notfiy
方法(通知訂閱者的所有訂閱列表) -> 執(zhí)行訂閱列表所有 Watcher
的 update
方法 -> 執(zhí)行對(duì)應(yīng) Parser
的 update
-> 完成更新視圖
Watcher
里的 set
方法用于設(shè)置雙向綁定值,注意訪問層級(jí)
Dep
MVVM
的訂閱中心,在這里收集數(shù)據(jù)模型的每個(gè)屬性的訂閱列表
包含添加訂閱者,通知訂閱者等方法
本質(zhì)是一種發(fā)布/訂閱模式
class Dep { constructor() { this.dependList = []; } addDep() { this.dependList.push(dep); } notfiy() { this.dependList.forEach(item => { item.update(); }); } }
以上是“如何使用proxy實(shí)現(xiàn)一個(gè)MVVM庫(kù)的方法”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!