前言
MVVM
是当前时代前端日常业务开发中的必备模式(相关框架如react
,vue
,angular
等), 使用 MVVM
可以将开发者的精力更专注于业务上的逻辑,而不需要关心如何操作 dom
。虽然现在都 9012 年了,mvvm
相关原理的介绍已经烂大街了,但出于学习基础知识的目的(使用 proxy
实现的 vue
3.0 还在开发中), 在参考了之前 vue.js
的整体思路之后,自己动手实现了一个简易的通过 proxy
实现的 mvvm
。
本项目代码已经开源在github,项目正在持续完善中,欢迎交流学习,喜欢请点个 star 吧!
最终效果
<html> <body> <div id="app"> <div>{{title}}</div> </div> </body> </html>
import MVVM from '@fe_korey/mvvm'; new MVVM({ view: document.getElementById('app'), model: { title: 'hello mvvm!' }, mounted() { console.log('主程编译完成,欢迎使用MVVM!'); } });
结构概览
Complier
模块实现解析、收集指令,并初始化视图Observer
模块实现了数据的监听,包括添加订阅者和通知订阅者Parser
模块实现解析指令,提供该指令的更新视图的更新方法Watcher
模块实现建立指令与数据的关联Dep
模块实现一个订阅中心,负责收集,触发数据模型各值的订阅列表
流程为:Complier
收集编译好指令后,根据指令不同选择不同的Parser
,根据Parser
在Watcher
中订阅数据的变化并更新初始视图。Observer
监听数据变化然后通知给 Watcher
,Watcher
再将变化结果通知给对应Parser
里的 update
刷新函数进行视图的刷新。
模块详解
Complier
将整个数据模型 data
传入Observer
模块进行数据监听
this.$data = new Observer(option.model).getData();
循环遍历整个 dom
,对每个 dom
元素的所有指令进行扫描提取
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); } } }
对每个指令进行编译,选择对应的解析器Parser
const parser = this.selectParsers({ node, dirName, dirValue, cs: this });
将得到的解析器Parser
传入Watcher
,并初始化该 dom
节点的视图
const watcher = new Watcher(parser); parser.update({ newVal: watcher.value });
所有指令解析完毕后,触发 MVVM
编译完成回调$mounted()
this.$mounted();
使用文档碎片document.createDocumentFragment()
来代替真实 dom
节点片段,待所有指令编译完成后,再将文档碎片追加回真实 dom
节点
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 }); }
不同的解析器提供不同的视图刷新函数update()
,通过update
更新dom
视图
//text.js function update(newVal) { this.el.textContent = _toString(newVal); }
OnParser
解析事件绑定,与数据模型中的 methods
字段对应
//详见 https://github.com/zhaoky/mvvm/blob/master/src/core/parser/on.ts el.addEventListener(handlerType, e => { handlerFn(scope, e); });
ForParser
解析数组
详见 https://github.com/zhaoky/mvvm/blob/master/src/core/parser/for.ts
ModelParser
解析双向绑定,目前支持input[text/password] & textarea
,input[radio]
,input[checkbox]
,select
四种情况的双向绑定,双绑原理:
数据变化更新表单:跟其他指令更新视图一样,通过update
方法触发更新表单的value
function update({ newVal }) { this.model.el.value = _toString(newVal); }
表单变化更新数据:监听表单变化事件如input
,change
,在回调里set
数据模型
this.model.el.addEventListener('input', e => { model.watcher.set(e.target.value); });
Observer
MVVM
模型中的核心,一般通过 Object.defineProperty
的 get
,set
方法进行数据的监听,在 get
里添加订阅者,set
里通知订阅者更新视图。在本项目采用 Proxy
来实现数据监听,好处有三:
Proxy
可以直接监听对象而非属性
Proxy
可以直接监听数组的变化
Proxy
有多达 13 种拦截方法,查阅
而劣势是兼容性问题,且无法通过 polyfill
磨平。查阅兼容性
注意 Proxy
只会监听自身的每一个属性,如果属性是对象,则该对象不会被监听,所以需要递归监听
设置监听后,返回一个 Proxy
替代原数据对象
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
模块里对每一个解析后的 Parser
进行指令与数据模型直接的绑定,并触发 Observer
的 get
监听,添加订阅者(Watcher
)
this._getter(this.parser.dirValue)(this.scope || this.parser.cs.$data);
当数据模型变化时,就会触发 -> Observer
的 set
监听 -> Dep
的 notfiy
方法(通知订阅者的所有订阅列表) -> 执行订阅列表所有 Watcher
的 update
方法 -> 执行对应 Parser
的 update
-> 完成更新视图
Watcher
里的 set
方法用于设置双向绑定值,注意访问层级
Dep
MVVM
的订阅中心,在这里收集数据模型的每个属性的订阅列表- 包含添加订阅者,通知订阅者等方法
- 本质是一种发布/订阅模式
class Dep { constructor() { this.dependList = []; } addDep() { this.dependList.push(dep); } notfiy() { this.dependList.forEach(item => { item.update(); }); } }
后记
目前该 mvvm
项目只实现了数据绑定
和视图更新
的功能,通过这个简易轮子的实现,对 dom
操作,proxy
,发布订阅模式
等若干基础知识都进行了再次理解,查漏补缺。同时欢迎大家一起探讨交流,后面会继续完善!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]