手机版

JavaScript数据绑定实现了一个简单的MVVM库

时间:2021-11-04 来源:互联网 编辑:宝哥软件园 浏览:

推荐阅读:

实现非常简单的js双向数据绑定。

MVVM是非常流行的Web前端开发模式。通过使用MVVM,我们的代码可以专注于业务逻辑,而不是DOM操作。目前,著名的MVVM框架包括vue、avalon、react等。这些框架各有千秋,但实现思路基本一致:数据绑定视图刷新。出于好奇和自愿,我沿着这个方向编写了一个最简单的MVVM库(mvvm.js),总共有2000多行代码。指令的命名和用法与vue相似。在这里,我想分享一下实现的原则和我对代码组织的想法。

思维安排

从概念上讲,MVVM是一个真正将视图与数据逻辑分开的模型,视图模型是整个模型的焦点。要实现视图模型,需要将数据模型与视图相关联。整个实现思路可以简单概括为五点:

实现一个编译器,从元素的每个节点扫描和提取指令;

实现一个Parser来解析元素上的指令可以通过一个刷新函数将指令的意图更新到dom中(中间可能需要一个专门负责视图刷新的模块)。例如解析节点p v-show='isShow'/p时,首先在Model中获取isShow的值,然后根据isShow改变node.style.display来控制元素的显示和隐藏;

实现一个Watcher可以将Parser中每个指令的刷新功能与Model对应的字段连接起来。

实现一个观察者可以监控对象所有字段中值的变化,一旦发生变化,它就可以获得最新的值并触发通知回调。

使用观察器为观察器中的模型设置监视器。当模型中的值改变时,监视器被触发。在Watcher获得新值后,他调用步骤2中关联的刷新函数,以便在数据更改时可以刷新视图。

效果示例

首先,粗略地看一下最终的使用示例,它类似于其他MVVM框架的实例化:

div id=' mobile-list ' h1 v-text=' title '/h1 ulli v-for=' item in brands ' b v-text=' item . name ' span v-show=' show rank ' rank 3360 { { item . rank } }/span/Li/ul/div var element=document . queryselector(# mobile-list ');var vm=新MVVM(元素,{'title' : 'Mobile List ',' showRank': true,' brands ' :[{ ' name ' : ' Apple ',' rank': 1},{'name': 'Galaxy ',' rank': 2},{'name': 'OPPO ',' rank ' : ' 3 }]});vm.set(“标题”、“前3名移动排名列表”);//=h1Top 3移动排名列表/h1模块划分。

我将MVVM分成五个模块来实现:编译器、解析器、更新器、观察器和观察者。流程可以简单描述为:编译指令后,编译器将指令信息提交给Parser进行解析,Parser更新初始值并订阅Watcher进行数据更改,Observer监控数据更改后反馈给Watcher,Watcher将更改结果通知Updater并找到相应的刷新函数刷新视图。

以上过程如图所示:

以下是这五个模块实现的基本原理的简单介绍(代码中只贴了关键部分,请阅读我的Github中完整的实现)。

1.编译模块编译器。

编译器主要负责扫描和提取元素每个节点的指令。由于编译和解析的过程会多次遍历整个节点树,为了提高编译效率,首先在MVVM构造函数内将元素转换成文档片段形式的副本。片段编译对象就是这个文档片段,不应该是目标元素。编译完所有节点后,文档片段将被添加回原始的真实节点。

VM.compliance组件实现了元素所有节点的扫描和指令提取:

VM.compliance element=function(fragment,root) {var node,child nodes=fragment . child nodes;//扫描子节点(var I=0;i childNodes.lengthI){ node=child nodes[I];if(this . has directive(node)){ this。$ unCompileNodes.push(节点);}//递归扫描子节点if(node . child nodes . length){ this.compile element(node,false);} }//扫描后,编译所有包含指令的if(root){ this.compilellnodes();} } vm.compileAllNodes方法将编译这个中的每个节点。$反编译节点(给Parser指令信息),编译后从缓存队列中移除一个节点,并检查这一点。$反编译节点。length当length===0时,表示所有编译完成,文档片段可以追加到真实节点。

2.指令解析模块Parser。

当编译器提取每个节点的指令时,它可以将它们交给解析器进行分析。每条指令都有不同的解析方法,所有指令的解析方法只需要做好两件事:一是将数据值更新到视图(初始状态),二是将刷新功能订阅到Model的变更监控。这里以解析v-text为例,描述一条指令的一般解析方法:

解析。parsevtext=函数(节点,模型){//获取初始值var text=this。$ model[model];//更新节点的文本. node.textContent=text//对应的刷新函数://updater . updatenodetextcontent(节点,文本);//订阅模型更改观察器. watch(模型,函数(last,old){ node . text content=last;//updater . updatenodetextcontent(节点,文本);});}3.数据订阅模块Watcher。

在前面的示例中,Watcher提供了一种订阅数据更改的Watcher方法。一个参数是模型字段模型,另一个是回调函数,由观察者触发。参数以最后一个新值和旧值传递。守望者得到新值后,可以找到Model对应的回调(刷新函数)来更新视图。模型和刷新函数之间的关系是一对多的,也就是说,模型可以有任意数量的回调函数(刷新函数)来处理它。例如,两个指令v-text='title '和v-html='title '共享一个数据模型字段。

添加数据订阅观察器。观察器的实现如下:

watcher.watch=function(字段、回调、上下文){var回调=this。$回调;if(!object . hasown property . call(this。$model,field)){ console . warn(' field : '字段在model中不存在!');返回;}//创建缓存回调函数的数组,如果(!回调[字段]){回调[字段]=[];}//缓存回调函数回调[字段]。push([回调,上下文]);}当数据模型的field字段发生变化时,Watcher将触发缓存数组中订阅该字段的所有回调。

4.数据监控模块观察者。

观察者是整个mvvm实现的核心基础。我看过一篇文章说O.o (Object.observe)会引爆数据绑定革命,给前端带来很大的影响。不幸的是,ES7草案已经放弃了O.O!目前没有浏览器支持!幸运的是,Object.defineProperty可以通过截取对象属性的访问描述符(get和set)来模拟一个简单的Observer :

//截取获取和设置对象正确属性的方法。定义属性(对象、道具、{get:function () {return this。getvalue(对象,道具);},set:函数(new value){ var old value=this . getvalue(object,prop);if (newValue!==old value){ this . setvalue(object,newValue,prop);//触发更改回调。triggerchange(道具、新值、旧值);}}});然后是如何监控阵列操作(推送、移位等)的问题。).的所有MVVM框架都是通过重写数组的原型来实现的:

observer . RewriteArraymmethods=function(array){ var self=this;var arrayProto=Array.prototypevar arrayMethods=object . create(Arrayproto);var methods=' push | pop | shift | unshift | splice | sort | reverse '。拆分(' | ');methods.forEach(函数(方法){ object . definepreproperty(arrayMethods,方法,函数(){ var i=arguments.lengthvar original=Arrayproto[方法];var args=新数组(I);while(I-){ args[I]=arguments[I];}var result=original.apply(this,args);//触发器回调self.triggerChange(this,method);返回结果;});});数组。_ _ proto _ _=arrayMethods}这个实现参考了vue,非常好,但是数组的长度属性无法监控,所以在MVVM应该避免array.length的操作。

5.视图刷新模块更新程序。

Updater是五个模块中最简单的,只需要负责每个指令对应的刷新功能。经过一系列的折腾,其他四个模块将最终结果交给更新器来更新视图或事件。例如,v-text的刷新功能是:

updater . updatenodetextcontent=function(节点,文本){ node.textContent=text} v-bind : style的刷新功能:

updater . updatenodestyle=function(node,property,value){ node . style[property]=value;}双向数据绑定的实现。

表单元素的双向数据绑定是MVVM最大的特色之一:

其实实现这个神奇功能的原理也很简单。只有两件事可以做:一是在数据发生变化时更新表单值,二是在表单值发生变化时更新数据,使数据值与表单值绑定。

数据更改会更新表单值,这可以通过使用上面提到的Watcher模块轻松实现:

watcher.watch(模型,函数(最后,旧的){ input.value=last});只有通过实时监控表单的值更改事件并更新数据模型的相应字段,表单才会更改和更新数据:

var模型=这个。$ modelinput.addEventListenr('change ',function(){ model[field]=this . value;});其他形式的单选、复选框和选择都有相同的原理。

以上,完成了整个流程和各个模块的基本实现思路。第一次在社区发文章,语言表达能力不是很好。如果你说的或者写的有问题,希望你批评指正!

标签

这个简单的mvvm.js是因为在我自己的框架项目中使用了vue.js,但是只使用了它的指令系统,很多功能只用了四分之一左右,所以我觉得实现数据绑定和视图刷新就足够了。结果没有找到这样的javascript库,就自己造了这样的轮子。

虽然功能和稳定性远不如现在流行的vue等MVVM框架,代码实现可能比较粗糙,但是通过搭建这个轮子,增加了很多知识~进步在于折腾!

目前我的mvvm.js只实现了最基本的功能,以后还会继续改进和加强。感兴趣的话请一起讨论完善~

版权声明:JavaScript数据绑定实现了一个简单的MVVM库是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。