Vue类MVVM前端库实现代码
视图模型
(ModelView ViewModel)是一个基于MVC的设计。通过在HTML上写一些Bindings,用一些指令绑定,可以在Model和ViewModel保持不变的情况下,轻松分离UI设计和业务逻辑,大大减少了繁琐的DOM操作。
MVVM的在线实现太多了。本文是个人总结,结合了源代码和其他一些人的实现
关于双向绑定
vue数据劫持订阅-发布ng脏值检查backbone.js订阅-发布(这尚未使用,也不是主流用法)
双向绑定,从最基本的实现来说,就是在defineProperty绑定的基础上绑定输入事件,实现v-model的功能
代码思维图
:的两个版本
简单版:很简单,但因为是es6,代码极度简化,所以不谈函数的标准版:思路清晰。指的是Vue的一些源代码,代码的功能是高度向上提取的,所以读起来有点困难。它实现了基本功能,包括计算属性、手表和核心功能,但不支持数组。
简单版本
简易版地址:简易版
这个MVVM可能没有在代码逻辑上完美实现,也不是正统的MVVM。但是代码非常简洁,比源代码好理解很多,实现了v-model和v-on方法的功能。代码很小,只有100多行。
类MVVM {构造函数(options){ const { El,data,methods }=options this。方法=方法。目标=null this。观察者(这个,数据)这个。说明(文件。getelementbyid(El))//获取挂载点} //数据监听器拦截所有数据数据传给定义属性用于数据劫持观察者(根,数据){ for(数据中的常量键){ this.definition(根,键,数据[键]) } } //将拦截的数据绑定到这上面定义(根、键、值){ //if(值类型==='object') { //假如价值是对象则接着递归//返回this.observer(value,value) //}让dispatcher=new Dispatcher() //调度员object . defineperproperty(根,键,{ set(新值){ value=新值dispatcher。notify(新值)},get(){ dispatcher。加上(这个。目标)返回值} }) } //指令解析器指令(DOM){ const nodes=DOM。子节点;//返回节点的子节点集合//console.log(节点);//查看节点属性用于(节点的常量节点){ //与因为在相反的获取迭代的价值值if (node.nodeType===1) { //元素节点返回1 const attrs=node.attributes //获取属性for(attr的const attr){ if(attr。name==' v-model '){ let value=attr。value//获取v型的值node.addEventListener('input ',e={ //键盘事件触发这个[值]=e . target。value })这个。目标=新的观察者(节点、“输入”)//储存到订阅者这个[值] //获取一下,将这个目标给调度员} if(attr。name==' @ click '){ let value=attr。value//获取点击事件名node.addEventListener('click ',this.methods[value]).bind(this))} } } if(node。nodetype===3){//文本节点返回3让reg=/\{\{(.*)\}\}/;//匹配{{ }}让match=节点。节点值。匹配(reg)if(match){//匹配都就获取{{}}里面的变量常量值=匹配[1]。trim() this.target=new Watcher(节点,' text ')这个[值]=这个[值]//get set更新一下数据} } } }}//调度员调度订阅发布类Dispatcher { constructor(){ this。watchers=[]}添加(watchers){ this。观察者。push(观察者)//将指令解析器解析的数据节点的订阅者存储进来,便于订阅}通知(新值){ this。观察者。地图(观察者=观察者。更新(新值))//有数据发生,也就是触发设置事件,通知事件就会将新的数据交给订阅者,订阅者负责更新}}//订阅发布者视图模型核心类观察者{构造函数(节点,类型){这个。node=节点this。type=type }更新(值){ if(this。type==' input '){ this。节点。value=value//更新的数据通过订阅者发布到DOM }如果(这个。type===' text '){ this。节点。节点值=value } }!DOCTYPE html html lang=' en ' head meta charset=' UTF-8 ' meta name=' viewport ' content=' width=device-width,initial-scale=1.0 ' meta http-equiv=' X-UA-Compatible ' content=' ie=edge ' title mvvm/title/head body div id=' app '输入类型=' text ' v-model=' text ' { text } } br button @ click=' update '重置/button /div脚本src='http:/index . js '/脚本脚本让mvvm=新MVVM({ el: 'app '、数据: { text : ' hello MVVM ' }、方法: { update(){ this。text=' ' } })/script/body/html这个版本的视图模型因为代码比较少,并且是ES6的原因,思路非常清晰
我们来看看从新视图模型开始,他都做了什么
解读简单版本
新视图模型
首先,通过解构获取所有的新视图模型传进来的对象
类MVVM构造函数(选项){ const { el,data,methods }=options this。方法=方法/提取方法,便于后面将这给方法this.target=null /后面有用观察者(这个,数据)这个。说明(文件。getelementbyid(El))//获取挂载点}属性劫持
开始执行观察者观察者是一个数据监听器,将数据的数据全部拦截下来
观察者(根,数据){ for(数据中的常量键){ this.definition(根,键,数据[键]) } }在这个定义里面把数据数据都劫持到这上面
定义(根,键,值){if(值类型==' object '){//如果value是对象,则返回this.observer(值,值)}让dispatcher=new dispatcher()//dispatcher object . definepreproperty(根,键,{set(新值){value=new value dispatcher。notify(新值)},Get () {dispatcher。add (this.target)返回值}})}此时我们已经可以监控data的数据变化了,但是我们需要在监控之后实时响应页面,所以这里我们使用dispatcher在页面初始化的时候获取(),这样就这样了。target,即后期指令解析器解析的v-model等指令,存储在dispatcher中,主要看后期解析器。
指令分析器
指令解析器通过执行这个获得挂载点。说明(文件。getelementbyid (el))
指令(DOM){ const nodes=DOM . child nodes;//返回node //console.log(nodes)的子节点集合;//检查(节点的const node)的节点属性{//与in的for相反,for的获取迭代值if(node . nodetype===1){///元素节点返回1 const attrs=node.attributes //获取(attrs的const attr){ if(attr。name==' v-model') {let value=attr。value//获取v-model node . addeventlistener(' input ',E={//键盘事件触发此[value]=E . target . value })this . target=new watcher(node,' input') //保存到订阅服务器this[value] //获取。将此. target交给调度员} if (attr。name=' @ click') {let value=attr。value//获取点击事件名称节点. addEventListener('click ',This。方法[值]。bind (this))}}} if (node。nodetype==3){//文本节点返回3个字母reg=/\{\{(。*)\}\}/;//match {{}}让match=node。nodevalue。match(reg)if(match){//变量const值=match [1]。修剪这个。target=newwatcher (node,Text ')this[value]=this[value]//getset update the data } } }这里,代码首先解析出我们的自定义属性,然后,我们将@click事件直接指向方法,方法已经实现
现在代码模型是这样的
调度程序调度程序和订户观察程序
我们需要连接调度员和观察者
所以我们之前创建的变量this.target开始发挥作用
执行解析器使用this.target在当前观察器订阅中存储节点和触发器关键字,然后我们获取数据
保存this.target=new Watcher(节点,‘input’)//给订阅者这个[值] //get,给dispatcher这个. target,在执行这个[值]的时候触发了get事件
Get () {dispatcher。加上(这个。target)返回值}在这个get事件中,我们向调度程序通知了观察程序订阅者,调度程序存储订阅事件。
//调度程序调度订阅发布class dispatcher { constructor(){ this。watchers=[]}添加(watchers){ this。观察者。push(watcher)//存储由指令解析器解析的数据节点的订阅者。订阅{ notify(新值){ this }很方便。观察者.地图(观察者=观察者。update(new value))//数据发生,即触发set事件,notify事件将新数据交给订阅者,订阅者负责更新}}与输入不同,文本节点不仅需要获取,还需要设置,因为订阅者需要更新节点节点。
This.target=new Watcher(节点,' text ')this[value]=this[value]//getset更新数据,因此在订阅服务器上添加事件,然后执行set
set(new value){ value=new value dispatcher。notify (newvalue)},而不是fix执行,订阅发布者执行update来更新节点信息
类观察器{构造函数(节点,类型){这。node=nodethis。type=type}更新(值){if (this。type==' input') {this。节点。value=value//更新后的数据通过订阅者发布到} If(这。type===' text') {this。node.nodevalue=value}}}页面已初始化
更新数据
Node.addEventListener('input ',e={//键盘事件触发此[值]=e.target.value})此[值]表示数据发生变化,触发set事件。由于notfiy事件被触发,notfiy遍历所有节点,并根据页面初始化期间订阅的触发器类型刷新遍历节点中的页面。
现在你可以完成新MVVM的实施过程了
MVVM最简单的版本已经完成
标准版本
标准版本额外实现了组件和观察,因为模块化代码非常混乱,所以看起来仍然很困难
从概念上看,实现的思路基本一致,大家可以参考上图,都是截取属性和解析开头的指令
代码有近300行,所以发布了地址标准版MVVM
执行顺序
1 .新的MVVM2。get $options=so参数3。获取数据供以后劫持4。因为是es5,后面的forEach指向内部的window,这不是我们想要的。因此,将电流存储为me5。_proxyData劫持所有数据数据。6.初始化计算属性。7.通过Object.key()获取计算属性的属性名。8.初始化计算属性并将计算属性装载到虚拟机上。9.启动观察者听数据。10.确定数据是否存在。11.创建一个新的观察者(如果存在)。12.对所有数据执行defineProperty访问监控,以便后续数据更改将触发此get/set。13.开始获取挂载点。14.使用querySelector对象解析el15。创建一个虚拟节点并存储当前的DOM。17.使用子节点来解析对象。18.因为它是5,所以,使用slice.call将对象转换为数组。19.获取后,进行{{}}匹配指令匹配和递归子节点。20.指令匹配:匹配指令。因为我不知道有多少个指令名,所以,这里我们还是用[].slice.call来循环遍历。21.用v-解析指令,并用子字符串(2)截取下面的属性名。22.然后判断指令是否为v-on。这是关键字匹配。如果匹配,则是事件指令。普通指令不匹配。23.解析{{data}} _ getvmget的普通指令会触发MVVM的_proxyData事件。在_proxyData事件中,它将触发数据的get事件。24.此时,数据是在获取观察者的定义活动中获得的。因为没有Dispatcher.target,不这样做会触发Dispatcher。25.到目前为止,_getVMVal已经获得了数据。26.26.modelUpdater更新Dom上的数据。27.数据开始订阅,在订阅中留下一个回调函数来更新DOM。28.从观察器获取这个,订阅的属性。回调29。对此属性返回一个匿名函数,以获取数据30的值。触发get事件,在Dispatcher.garget 31上存储当前观察者的这个。给这个. getter,调用vm的这个VM,执行匿名函数,获取被劫持的数据。触发了MVVM _ proxy data的get事件,然后触发了观察者定义活动的get事件。但是,这次Dispatcher.target有一个值,并且执行了depend事件。32 .它自己的addDep事件在depend中执行。并将观察者自己的这个传递到33.addDep中,以执行调度程序的addSub事件;34.将订阅存储在addUsb事件的Dispatcher中的this.watchers中;35.订阅完成后,删除这些自定义说明;36.重复操作,解析所有指令,on:click=' data '直接执行方法[data]bind。
更新数据:
1.触发输入事件2。trigger _setVMVal事件3。触发MVVM设置事件4。触发观察者设置事件5。触发dep.notify()6。触发观察器运行方法7。触发新的Watcher回调this.cb8在编译9中触发updaterFn事件。更新视图
组件的实现
计算属性的触发器以查看此示例
computed : { gethelloword :函数(){ return this . somestr this . child . somestr;}},实际上,计算属性是defineproperty的扩展
1.首先,从编译中获取{{gethelloword}} '。2.执行更新程序[textupdater]。3.执行_getVMVal获取计算属性的返回值。4.获取VM[组件],然后执行以下获取事件
object . defineperoperty(me,key,{ get : type of computed[key]===' function '?计算[键] :计算[键]。get,set: function () {}})是一个执行计算[getHelloword]的函数,也就是return
this . somestr this . child . somestr;
1.依次获取数据,触发mvvm获取和观察者获取,
初始化完成,这里没有绑定数据,只是初始化完成
1.开始订阅事件newwatcher () 2。该组件不是函数,因此它不是执行this.parseGetter(expOrFn)的函数;3.返回一个覆盖expOrrn 4的匿名函数。开始初始化并执行get()5。存储当前这个,Get vm[getHelloword]6。触发器组件[getHelloword] 7。开始执行这个。一些人。去MVVM,去观察者。9.Dispatcher的depend()被执行,因为Dispatcher.target存储了getHelloWord的this.depend()。执行观察器的addDep()和调度器的addSub()将当前观察器存储到侦听器中。10.开始获取第二个数据this.child.someStr,并将这个GetHelloord存储到当前的Dispatcher中。11.开始获取第三个数据this.child,并将这个GetHelloord存储到当前的调度程序中
这个执行顺序有点混乱,第二和第三方来了
this . parsegetter(expOrFn);执行完毕
目前为什么组件实时属性数据?
因为一旦组件的依赖属性改变,getHelloword的观察器就会被更新,然后执行回调来更新dom
观察的实施
手表的实现相对简单
1.我们只需要告诉用户watch监控的数据。2.这样,wacth就会更新。3.集合被触发。设置触发器notify4.notify更新观察器。5.watcher执行run6.run方法执行watcher的回调。7.监视完成
watch:功能(键,cb) {新Watcher(这个,键,cb)},
版权声明:Vue类MVVM前端库实现代码是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。