浅谈vue原理(二)

  • A+
所属分类:Web前端
摘要

  上篇说了一下vue中的数据劫持和数据代理,就是将data中的数据都添加set/get方法,这使得扩展性更好了,后续的会在这个set/get方法添加我们需要的逻辑;

  上篇说了一下vue中的数据劫持和数据代理,就是将data中的数据都添加set/get方法,这使得扩展性更好了,后续的会在这个set/get方法添加我们需要的逻辑;

  现在我们说说怎么才能够使得data中的数据和html标签中的内容绑定呢?

1.编译模板

  首先我们要思考一下,如果是你,你会怎么让data和html标签中的{{user.name}}这种东西进行匹配啊?

浅谈vue原理(二)

 

  千万别想花里胡哨的东西,最直接的办法就是遍历所有的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实现发布订阅模式

  我们说说设计模式,什么叫做发布订阅模式呢?你想想你微信订阅的