- A+
上篇说了一下vue中的数据劫持和数据代理,就是将data中的数据都添加set/get方法,这使得扩展性更好了,后续的会在这个set/get方法添加我们需要的逻辑;
现在我们说说怎么才能够使得data中的数据和html标签中的内容绑定呢?
1.编译模板
首先我们要思考一下,如果是你,你会怎么让data和html标签中的{{user.name}}这种东西进行匹配啊?
千万别想花里胡哨的东西,最直接的办法就是遍历所有的html标签,根据正则匹配到有两个大括号的就行啊,然后就取出大括号中的数据user.name,然后切割一下,就成了[user,name],再之后遍历这个数组,拿到data[user][name]就行了,而且由于前面已经做好数据代理,我们可以直接这样取值myVue[user][name],取到值之后,就把html标签中{{xxx}}进行覆盖,然后继续找下一个有{{}}占位符的;
这就是大概的逻辑,基于这个就可以将html中的标签中的{{user.name}}变成实际的数据了;
但是还需要解决一个问题,怎么拿到所有的html标签呢?而且在遍历之后把数据渲染到页面上效率提高一点呢?
所以我们需要知道一个容器:document.createDocumentFragment,这是一个虚拟节点的容器树,我们可以将当多个dom元素丢到DocumentFragment中,再统一将DocumentFragment添加到页面,会减少页面渲染dom的次数,效率会明显提升。有兴趣的可以自行了解一下这个
ok,理论说完了,大概就是这么几步
(1)首先根据实例化myVue实例时候传进去的el属性 "#app",就可以找到dom节点
let myVue = new MyVue({ el: '#app', data: { message: { a: { b: 1 } } } })
(2)根据document.createDocumentFragment创建虚拟节点的容器树,遍历所有的dom节点都丢到容器树中
(3)再遍历虚拟容器树中每个节点,使用正则匹配到有两个大括号的节点,将节点中的表达式取出来,例如user.name
(4) 根据表达式取出data中的值,由于进行了数据劫持,data中的值可以直接用myVue[user][name]获取,取到了值之后,就将对应的容器树中的对应节点中{{user.name}}覆盖掉
(5)将虚拟容器树渲染到页面中,我们就能看出效果了;
下面用代码简单实现一下模板编译的代码:
function compile (el, vm) { return new Compile(el, vm); } function Compile (el, vm) { //找到el代表的那个dom节点,并挂载到myVue实例中 vm.$el = document.querySelector(el); //创建虚拟节点容器树 let fragment = document.createDocumentFragment(); //将el下所有的dom节点都放到容器树中,注意appendChild方法,这里是将将dom节点移动到容器树中啊,不是死循环! while (child = vm.$el.firstChild) { // console.log('count:' + vm.$el.childElementCount); fragment.appendChild(child) }; //遍历虚拟节点中的所有节点,将真实数据填充覆盖这种占位符{{}} replace(fragment, vm); //将虚拟节点树中内容渲染到页面中 vm.$el.appendChild(fragment); } function replace (n, vm) { //遍历容器树中所有的节点,解析出{{}}里面的内容,然后将数据覆盖到节点中去 Array.from(n.childNodes).forEach(node => { //console.log('nodeType:' + node.nodeType); let nodeText = node.textContent; let reg = /{{(.*)}}/; // 节点类型常用的有元素节点,属性节点和文本节点,值分别是1,2,3 //一定要弄清楚这三种节点,比如<p id="123">hello</p>,这个p标签整个的就是元素节点,nodeType==1 //id="123"可以看作是属性节点,nodeType==2 //hello 表示文本节点,nodeType==3 //因为占位符{{}}只在文本节点中,所以需要判断是否等于3 if (node.nodeType === 3 && reg.test(nodeText)) { // RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串,以此类推,RegExp.$2。。。 let arr = RegExp.$1.split("."); let val = vm; // 这个for循环就是取出这样的值:myVue[name][user] arr.forEach(i => { val = val[i]; }) // 把值覆盖到虚拟节点的占位符{{xxx}}这里 node.textContent = nodeText.replace(reg, val); } // 最开始第一个遍历的节点是<div id="app">这一行后面有个你看不到的换行,所以nodeType等于3,但是没有占位符{{}},所以会进行递归调用内部 //的每一个节点,直到找到是文本节点而且有占位符{{}} if (node.childNodes) { replace(node, vm); } }) }
2.js实现发布订阅模式
我们说说设计模式,什么叫做发布订阅模式呢?你想想你微信订阅的