vue双向绑定的简单实现

研究了一下vue双向绑定的原理,所以简单记录一下,以下例子只是简单实现,还请大家不要吐槽~

之前也了解过vue是通过数据劫持+订阅发布模式来实现MVVM的双向绑定的,但一直没仔细研究,这次深入学习了一下,借此机会分享给大家。

首先先将流程图给大家看一下

vue双向绑定的简单实现

参考文章:Vue.js双向绑定的实现原理

我虽然参考的是这篇文章,下面的代码也是在阅读几遍后仿造的,自己只是简单添加了个递归实现所有dom子节点的双向绑定,以及添加了一些理解,但真正让我了然于心,让我可以独立写出2遍完整逻辑的其实是这张图,所以个人认为这张流程图才是最重要的,而我参考的这篇文章的作者也是参考这幅图的原作者的。

原文章:剖析Vue原理&实现双向绑定MVVM

站在阅读和理解MVVM的完整逻辑的话,推荐大家看第一篇,但是第二篇原文章的图文更能说明一些问题

如果大家看了我的解释也能够完全理解的话,那就更好啦啦啦啦啦~哈哈

好,下面我会从2个角度开始讲解,先上单向绑定,再由单向绑定过渡到双向绑定;

首先,先为大家解释一下单向绑定model => view层的逻辑
1、劫持dom结构;
2、创建文档碎片,利用文档碎片重构dom结构;
3、在重构的过程中解析dom结构实现MVVM构造函数实例化后的数据初始化视图数据;
4、利用判断dom一级子元素是否依然有子元素从而进行所有子元素的单向绑定;
5、将文档碎片添加至根节点中.

这就是我总结的关于单向绑定的逻辑了,下面利用代码跟大家解释

//dom结构 <div> <input type="text" v-model="msg"> <p>{{msg}}</p> <ul> <li>1</li> <li>{{msg}}</li> <li>{{test}}</li> </ul> </div> //one-way-binding.js //判断每个dom节点是否拥有子节点,若有则返回该节点 function isChild(node){ //这里使用childNodes可以读取text文本节点,所以不用children if(node.childNodes.length ===0){ return false; } else{ return node; } } //利用文档碎片劫持dom结构及数据,进而进行dom的重构 function nodeToFragment(node,vm){ var frag = document.createDocumentFragment(); var child; while(child = node.firstChild){ //一级dom节点数据绑定 compile(child,vm); //判断每个一级dom节点是否有二级节点,若有则递归处理文档碎片 if(isChild(child)){ //递归实现二级dom节点的重构 nodeToFragment(isChild(child),vm); } frag.appendChild(child); } //将文档碎片添加至对应node中,最后为id为app的元素下 node.appendChild(frag); } //初始化绑定数据 function compile(node,vm){ //node节点为元素节点时 if(node.nodeType === 1){ var attr = node.attributes; //遍历当前节点的所有属性 for(var i=0;i<attr.length;i++){ if(attr[i].nodeName === 'v-model'){ //属性名 var name = attr[i].nodeValue; //将data下对应属性名的值赋值给当前节点值 //这里因为node是input标签所以值为node.value node.value = vm.data[name]; //最后标签中的v-model属性也可以功成身退了,删除它 node.removeAttribute(attr[i].nodeName); } } } //node节点为text文本节点#text时 if(node.nodeType === 3){ var reg = /\{\{(.*)\}\}/; if(reg.test(node.nodeValue.trim())){ //将正则匹配到的{{}}中的字符串赋值给name var name = RegExp.$1; //利用name对应赋值相应的节点值 node.nodeValue = vm.data[name]; } } } //MVVM构造函数,这里我就写成Vue了 function Vue(options){ this.id = options.el; this.data = options.data; //将根节点与实例化后的对象作为参数传入 nodeToFragment(document.getElementById(this.id),this); } //实例化 var vm = new Vue({ el:'app', data:{ msg:'hello,two-ways-binding', test:'test key' } })

上述就是简单的单向绑定了,整个逻辑实际上非常简单,我再来跟大家说明一下

1、为了令model层的数据可以绑定到view层的dom上,所以我们想了一个办法来替换dom中的一些元素值,而明显一个个替换时不可取的,因为大量的dom操作会降低程序的运行效率,你想想,每次dom操作可都是一次对dom整体的遍历过程~,所以我们觉得采用文档碎片的形式,将dom一次全部劫持,在内存中执行全部数据绑定操作,最后只进行一次dom操作,即添加子节点来解决这个频繁操作dom的问题,你也可以理解为中间的一层存在于内存中的虚拟dom;

2、那么既然如此,我们就要首先劫持所有dom节点,这里我们利用nodeToFragment函数来劫持;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwjpgd.html