初探Vue源码(一) 前言 这段时间因为忙着秋招,好像很久都没有更新过了,现在秋招也没个着落,花了几天的时间看了一下Vue的源码,当然是跟着视频教程走的,但是视频有几节没有,所以还是挺遗憾的,知识点还是比较多的,这里做个总结,希望能够帮助到后面的面试。 一上手就直接翻Vue源码的话,还是比较痛苦的,这里采用的是另一种方式,先对后面出现的相关知识点进行整理,最后再来看源代码部分,这样会好很多,视频也是按照这样的流程走的,还是比较易于理解的。(Vue版本:2.6.12)
第一部分 如何将模板与数据相结合 首先我们回顾一下Vue的使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!-- 1. 写模板 --> <div id="root" > <p>`{{name}}` </p> <p>`{{message}}` </p> </div> <script> console .log(root); let app = new Vue({ el: '#root' , data: { name: 'handsome' , message: 'call...' } }) console .log(root); </script>
那么Vue帮我们做了什么事呢?简单来说它做了4件事:拿到模板、拿到数据、将模板和数据结合、放到页面中,下面我们分别来实现一下这几个步骤
1. 找到模板 1 let template = document .querySelector('#root' );
2. 拿到数据 这里我们模拟一下data的数据
1 2 3 4 let data = { name: 'jyq' , message: 'missing you...' }
3. 将模板和数据结合(难点) 这一部分是今天这部分的难点,我们如何将模板和数据进行结合呢? 首先要明白几个知识点,我们的dom节点是分为元素节点和文本节点的,像我们的被 双大括号 包裹的就是在文本节点中,所以这里的思路是:
遍历页面上的节点元素节点 文本节点利用正则判断是否有 双大括号 ,有的话用数据进行替换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 function compiler (template, data ) { const reg = /\{\{(.+?)\}\}/g let nodeList = template.childNodes; for (let i = 0 ; i < nodeList.length; i++) { let type = nodeList[i].nodeType; if (type === 1 ) { compiler(nodeList[i], data) } else if (type === 3 ) { let str = nodeList[i].nodeValue; str = str.replace(reg, (_, g ) => { let key = g.trim(); let value = data[key]; return value }); nodeList[i].nodeValue = str; } } } let generateNode = template.cloneNode(true );compiler(generateNode, data); root.parentNode.replaceChild(generateNode, root);
4. 放到页面中 1 root.parentNode.replaceChild(generateNode, root);
一些问题 当我们做完上面的步骤后,仔细想一下,真的没有问题了吗,答案很明显,问题有很多。
首先Vue中使用的是虚拟DOM,这一点我相信大多数人都听过,虚拟DOM的引入使得我们在任何情况下,都能以一个相对较少的修改去操作我们的dom元素; 是这里我们只考虑了单属性的情况,也就是类似于{{name}}
这种情况,对于多层级的我们这里是不能处理的,比如说{{name.obj.age}}
这种情况,很明显不符合我们的实际开发; 是我们这里的代码还没有进行整合,会感觉很乱。 所以接下来我们就围绕这三个方面来进行展开。
这里我们先解决第三个问题 代码的整合 在整合之前呢,我们先做一点约定(Vue中的约定),就是我们约定 _ 开头的代表的是内部的私有属性,可读可写; $ 开头的只能读,不能写,这里我把整体结构搭在这里,具体实现你可以查看这里具体实现 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function myVue (options ) { this ._data = options.data; this ._el = options.el; this ._templateDOM = document .querySelector(this ._el); this ._parent = this ._templateDOM.parentNode; this .render(); } myVue.prototype.render = function ( ) { this .compiler(); } myVue.prototype.compiler = function ( ) { let reaHTMLlDOM = this ._templateDOM.cloneNode(true ); compiler(reaHTMLlDOM, this ._data); this .update(reaHTMLlDOM) } myVue.prototype.update = function (reaHTMLlDOM ) { this ._parent.replaceChild(reaHTMLlDOM, document .querySelector('#root' )); } function compiler ( ) {}; let app = new myVue({ el: '#root' , data: { name: 'jquery yes queen' , message: 'miss' } }) }
接下来是第二个问题 针对单属性的情况 这个问题我们的思路是,首先找到这个属性,然后将属性转为数组的形式并用 . 进行分割,然后使用循环对其处理。这里我们的函数名叫做 getValueByPath
1 2 3 4 5 6 7 8 9 10 11 12 function getValueByPath (obj, path ) { let paths = path.split('.' ); let res = obj; let prop; while (prop = paths.shift() ) { res = res[prop]; } return res }
在Vue中我们采用了大量的函数柯里化的处理,目的就是对我们进场用到的一些数据进行缓存,虽说函数柯里化的概念自己已经清楚了,但是仅仅凭那几个例子(我想大多数人了解函数柯里化的时候都看到过相加的那个例子吧),我并不能知道这个技巧拿给我们带来什么,通过在Vue源码中的这种方式,是我对函数柯里化有了全新的认识。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function createGetValueByPath (path ) { let paths = path.split('.' ); return function getValueByPath (obj ) { let res = obj; let prop; while (prop = paths.shift() ) { res = res[prop]; } return res } }
最后一个问题 也就是虚拟DOM的问题 虚拟DOM在这里我就不多赘述了,这里我们要做的就是要将真实DOM转换为虚拟DOM,同时也要提供虚拟DOM到真实DOM的转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 class vNode { constructor (tag, data, value, type ) { this .tag = tag && tag.toLowerCase(); this .data = data; this .value = value; this .type = type; this .children = []; } appendChild (vNode ) { this .children.push(vNode); } } function getVNode (node ) { let nodeType = node.nodeType; let _vNode = null ; if (nodeType === 1 ) { let nodeName = node.nodeName; let attrs = node.attributes; let _attrObj = {}; for (let i=0 ; i<attrs.length; i++ ) { _attrObj[attrs[i].nodeName] = attrs[i].nodeValue; } _vNode = new vNode(nodeName,_attrObj,undefined ,nodeType); let nodeChild = node.childNodes; for (let i=0 ; i<nodeChild.length; i++ ) { _vNode.appendChild(getVNode(nodeChild[i])); } }else if (nodeType === 3 ) { _vNode = new vNode(undefined ,undefined ,node.nodeValue,nodeType); } return _vNode; } function parseVNode (vNode ) { let nodeType = vNode.type; let realNode = null ; if (nodeType === 1 ) { realNode = document .createElement(vNode.tag); let data = vNode.data; Object .keys(data).forEach(value => { realNode.setAttribute(value,data[value]) }) let childNode = vNode.children; childNode.forEach(item => { realNode.appendChild(parseVNode(item)) }) }else if (nodeType === 3 ) { realNode = document .createTextNode(vNode.value); } return realNode } let temp = getVNode(document .querySelector('#root' ));console .log(temp);console .log(parseVNode(temp));
总结 其实在这之前,自己也曾经打开过Vue的源代码,只不过看到这么多代码有点无从下手,经过这几天的学习之后,发现其实阅读源代码带来的收获还是挺大的,就比如说一下平时没注意的知识点,或是知道这个知识点,但是并不清楚为什么要有这个东西,就像函数柯里化一样,虽然知道它的形式,但是平时开发中真的很少会去应用这个代码,在Vue中算是看到一次实践了吧,第一部分内容大概就这么多,秋招快要结束了,也没能拿到一个offer,挺失败的,继续努力吧!