简单实现Vue中的虚拟dom
传送门:简单实现Vue中的插值替换(三)
前言:
-
要想简单实现虚拟dom,首先我们要了解虚拟dom,知道自己要实现的是个什么东西。
说起来,我刚开始学习Vue的时候对虚拟dom也是一知半解,我当时一听虚拟dom,脑子里第一反应就是虚拟机 ,不因为什么,就因为它两个都带有虚拟这个词。
虚拟什么意思?虚拟:设想、虚构。
说的更直白一点,就是凭想象编造出来的事物。
-
既然是虚拟出来的,那虚拟出来了个啥呢,或者说Vue根据真实的dom编造出来了一个什么样的东西呢?
其实,简单的理解虚拟dom,就是Vue用对象式的语言,把一个真实的dom描述了一下。
-
我们看一个简单的例子:
<div id="root"> <p class="name" title="Michel">Michel</p> </div>
这是一段正常的dom,那么Vue里面是怎么描述它的呢?我们再来看一下虚拟dom:
【注:这里让大家看的虚拟dom远远没有Vue转换的那么复杂,只是简单些的,好理解。】
{ tag:"div", // 标签 attrs:{id:"root"}, // 属性 type:1,// 元素类型 value: undefined, // 值 children:[ // 孩子元素 { tag:"p", attrs:{class:"name", title:"Michel"}, type:1, value: undefined, children:[ { tag: undefined, attrs: undefined, type: 3, value: 'Michel', children:[] } ] } ] }
看了这个虚拟dom,是不是感觉很简单,一个包含着
tag,attrs,type,value,children
的对象,就把这个dom描述出来了,而且层级也描述的很清楚。 -
这就是虚拟dom,大家还感觉很神秘吗?哈哈。
-
-
说道这里,又得简单的啰嗦两句,为啥要用虚拟dom???
当然是为了更快的编译,更高运行效率。【diff算法比较的就是虚拟dom】
接下来,我们进入正题,如何简简单单的把一串真正的dom转换成虚拟dom呢?
要分以下几步:
-
首先要拿到真正的dom模板
-
解析它,首先获取这个dom的类型,这个看过我前两篇博客的人估计不会陌生,就是对元素标签和文本进行一下区分。
-
如果是元素标签:
那就先获取这个dom的标签名,然后获取它的属性,把这些连同它的类型都给存起来,然后看看它有没有孩子,如果有,那就拿到它的孩子,然后就进入递归喽。
-
如果是文本:
那就更简单了,我们就直接拿到它的值,然后连同类型存起来。
-
当然怎么存,也是有讲究的,我们可以封装一个函数来专门干存储这个事,当然定义一个class类也行。
现在知道怎么干,那下面就填充代码呗:
-
获取dom模板
doument.getElemetById('#root')
,看过我前面博客的知道,这个其实有个简单方法,那就是直接写root
就好了。 -
获取dom类型
node.nodeType
就可以拿到啦 -
如果是元素类型,就是类型是 1:
node.nodeName 获取标签名 node.attributes 获取属性名,这里得到的是一个伪数组,所以我们还需要处理一下,处理成以属性名为key,属性值为value的一个对象 node.childNodes 获取子元素,然后就进入递归了
-
如果是文本,就是类型 3,那就
node.nodeValue 获取一下值
,然后连同类型存起来。 -
存储的话,比如这样:
// 定义一个类 class vNode { constructor(tag, attrs, value, type) { this.tag = tag && tag.toLowerCase(); this.attrs = attrs; this.value = value; this.type = type; this.children = []; } appendChildArr(vNode) { this.children.push(vNode); } } // 然后使用的时候,new vNode 传值就好了。 // 函数的就不写啦,和我前两篇博客定义的函数形式很像啦。
最后我们来看下完整的代码:
<body>
<div id="root">
<p class="name" title="Michel">Michel</p>
<p class="age" title="Michel今年21岁">21</p>
</div>
</body>
<script>
console.log('真实dom', root);
// 定义一个类
class vNode {
constructor(tag, attrs, value, type) {
this.tag = tag && tag.toLowerCase(); // 转换成小写
this.attrs = attrs;
this.value = value;
this.type = type;
this.children = []; // 定义一个数组,存储转换之后的虚拟DOM
}
appendChildArr(vNode) {
this.children.push(vNode);
}
}
// 转换虚拟dom
const getVNode = (node) => {
let nodeType = node.nodeType;//获取dom元素类型
let _vnode = null;
if (nodeType === 1) {
let nodeName = node.nodeName;//获取dom标签名
let attrs = node.attributes;//获取dom属性【获取到的是一个伪数组】
let _attrObj = {};// 定义存储属性的对象
for (let i = 0; i < attrs.length; i++) {
_attrObj[attrs[i].nodeName] = attrs[i].nodeValue; // 以属性名为key,属性值为value
}
_vnode = new vNode(nodeName, _attrObj, undefined, nodeType); // 存一下
let childNodes = node.childNodes; // 拿到子元素
for (let i = 0; i < childNodes.length; i++) {
_vnode.appendChildArr(getVNode(childNodes[i])); // 递归
}
} else if (nodeType === 3) {
_vnode = new vNode(undefined, undefined, node.nodeValue, nodeType);
}
return _vnode;
}
let vRoot = getVNode(root);
console.log('虚拟dom', vRoot);
</script>
OK,我们好说歹说,也算简单的实现了虚拟dom,所以接下来,我们要做的就是,我们怎么给转换回去,毕竟不能干一半是不是。
其实转换回来也简单,大致分以下几步:
-
拿到虚拟dom对象之后,首先就是拿到type,就是节点类型。
-
然后就还是判断,节点类型如果是 1,那就直接创建文本节点了:
document.createTextNode(node.value);// 把节点的值穿进去就行了
-
如果是元素节点 3
-
首先根据标签名创建一个dom:
document.createElement(vnode.tag);
-
然后呢,就是拿到存着属性的对象,然后遍历它,通过
setAttribute
来给创建的这个dom标签设置属性。 -
最后属性也设置完了,那就在去遍历children数组,以同样的方法依法炮制它的子节点,然后通过
appendChild
的方式给插到这个标签里。所以这里,就还是递归,嘿嘿。
-
说到这里,我们就来看一下转换成真实dom的完整代码:
// 将虚拟dom转换为真实的dom
const parseNode = (vnode) => {
let type = vnode.type; // 获取虚拟dom的type类型
let _node = null;
if (type === 3) {
return document.createTextNode(vnode.value); // 创建文本节点
} else if (type === 1) {
_node = document.createElement(vnode.tag); // 创建标签
let attrs = vnode.attrs;
// 遍历属性对象
Object.keys(attrs).forEach(key => {
let attrName = key;
let attrValue = attrs[key];
_node.setAttribute(attrName, attrValue); // 设置属性
})
}
// 子元素
let children = vnode.children;
children.forEach(item => {
_node.appendChild(parseNode(item));//递归遍历子节点然后插入到父节点里
});
return _node;
}
let rRoot = parseNode(vRoot);
console.log('虚拟dom转换的真实dom', rRoot);
这就是我这篇博客要讲的内容了,唉,想起来明天还要加班【'mmp 艹 艹 艹 ',咳咳,大家看,这是我写的一个字符串。】
下一篇博客的话,我们简简单单来说一下,Vue怎么把一个对象搞成响应式的。
传送门:简单实现Vue中的响应式对象