随着VueReact的风声水起,伴随着诸多框架的成长,虚拟DOM渐渐成了我们经常议论和讨论的话题。什么是虚拟DOM,虚拟DOM是如何渲染的,那么Vue的虚拟DomReact的虚拟DOM到底有什么区别等等等...一系列的话题都在不断的讨论中。为此也做了一些学习简单的侃一侃虚拟DOM到底是什么?

虚拟Dom详解 - (二)

什么是虚拟Dom

虚拟Dom首次产生是React框架最先提出和使用的,其卓越的性能很快得到广大开发者的认可,继React之后vue2.0也在其核心引入了虚拟DOM的概念。在没有虚拟DOM的时候,我们在创建页面的时候一般都是使用HTML标签一个一个的去搭建我们的页面,既然有了DOM节点以后,为什么不直接使用原生DOM,那么原生DOM到底有什么弊端呢?原因是这个样子的,原生DOM中一个Node节点有N多的属性,一旦对DOM进行操作的时候会影响页面性能的核心问题主要在于DOM操作导致了页面的重绘或重排,为了减少由于重绘和重排对网页性能的影响,所以无论在什么项目中尽可能少的去操作DOM节点是性能优化的一大重点。

所谓的虚拟DOM到底是什么?也就是通过JavaScript语言来描述一段HTML代码。其实使用JavaScript描述一段HTML代码是很简单的:

HTML:

<div class="" id="app">
<p class="text">节点一</p>
</div>

JavaScript:

const createElement = () => {
return {
"tag":"div",
"prop":{
"id":"app"
},
"children":[
{
"tag":"p",
"prop":{
"class":"text"
},
"children":["节点一"]
}
]
}
}

上面的代码中,只是简单的使用了JavaScript语言简单描述了一下HTML部分相对应的代码,此时我们只需要再写入一个创建DOM的方法,按照文档描述将创建好的DOM按照层级添加到里面页面中就好了。

上述JavaScript中所描述的数据类型也就可以简单的理解为是虚拟DOM,虽然这个虚拟DOM是那么的简陋,但是足可以说明情况啦,像VueReact当需要对页面进行渲染更新的时候,则是对比的就是虚拟DOM更新前后的差异只对有差异的部分进行更新,大大减少了对DOM的操作。这里也就是我们经常所说的DIFF算法。

通过上述描述可以总结得出,由于原生DOM节点中的属性和方法过于复杂,操作时过于影响性能,所以使用Object来描述页面中的HTML结构,以达到对性能的提升。

如何创建虚拟DOM

如果熟悉VueReact的朋友可能会知道一点,首先说下Vue,在使用中Vue中的虚拟DOM是使用template完成的,也就是平时我们项目中书写最多的模板,Vue通过vue-loader对其进行编译处理最后形成我们所需要的虚拟DOM,然而在React中则是不是这样的,React是没有template的,React则是使用的是JSX对进行编译,最后产生虚拟DOM,无论是Vue还是React最终的想要得到的就是虚拟DOM

若想要知道虚拟DOM是如何创建的,那么就可简单的实现一下其创建过程,在上面中可以得到一个描述DOM节点的数据文本,我们可以根据其需要对其进行创建:

const vnodeTypes = {
// HTML节点类型
"HTML":"HTML",
// 文本类型
"TEXT":"TEXT",
// 组件类型
"COMPONENT":"COMPONENT"
};
const childTeyps = {
// 为空
"EMPTY":"EMPTY",
// 单个
"SINGLE":"SINGLE",
// 多个
"MULTIPLE":"MULTIPLE"
};
// 新建虚拟DOM
// 所需创建标签名称
// 标签属性
// 标签子元素
function createElement (tag,data,children = null){
// 当前元素的标签类型
let flag;
// 子元素的标签类型
let childrenFlag;
if(typeof tag === "string"){
// 如果是文本的则认为是,普通的HTML标签
// 将其元素的flag设置成HTML类型
flag = vnodeTypes.HTML;
}else if(typeof tag === "function"){
// 如果为函数,则认为其为组件
flag = vnodeTypes.COMPONENT;
}
else {
// 否则是文本类型
flag = vnodeTypes.TEXT;
};
// 判断子元素情况
if(children === null){
// 如果 children 为空
// 则子元素类型为空
childrenFlag = childTeyps.EMPTY;
}else if (Array.isArray(children)){
// 如果 children 为数组
// 获取子元素长度
let len = children.length;
// 如果长度存在
if(len){
// 则设置子元素类型为多个
childrenFlag = childTeyps.MULTIPLE;
}else{
// 否则设置为空
childrenFlag = childTeyps.EMPTY;
}
}else {
// 如果存在并且不为空
// 则设置为单个
childrenFlag = childTeyps.SINGLE;
// 创建文本类型方法,并将 children 的值转为字符串
children = createTextVNode(children+"");
} // 返回虚拟DOM
return {
flag, // 虚拟DOM类型
tag, // 标签
data, // 虚拟DOM属性
children, // 虚拟DOM子节点
childrenFlag, // 虚拟DOM子节点类型
el:null // 挂载元素的父级
};
}; // 新建文本类型虚拟DOM
function createTextVNode (text){
return {
// 节点类型设置为文本
flag:vnodeTypes.TEXT,
// 设置为没有标签
tag:null,
// 没有任何属性
data:null,
// 子元素类型设置为单个
childrenFlag:childTeyps.EMPTY
};
};

通过上面的代码可以简单的实现对虚拟DOM的创建,可以通过调用createElement并传入用来描述虚拟DOM的对象,就可以打印出已经创建好的虚拟DOM节点:

const VNODEData = [
"div",
{id:"test"},
[
createElement("p",{},"节点一")
]
];
let div = createElement(...VNODEData);
console.log(div);

结果:

{
"flag": "HTML",
"tag": "div",
"data": {
"id": "test"
},
"children": [{
"flag": "HTML",
"tag": "p",
"data": {},
"children": {
"flag": "TEXT",
"tag": null,
"data": null,
"childrenFlag": "EMPTY"
},
"childrenFlag": "SINGLE"
}],
"childrenFlag": "MULTIPLE"
}

通过上述方法打印出来的则是按照传入的描述虚拟DOM的对象,已经创建好了一个虚拟DOM树,是不是一件很神奇的事情,其实仔细看下代码也没有什么特别重要的逻辑,只是该变了数据结构而已(可以这样理解,但是不能对外这么说,很丢人的,哈哈)。

既然虚拟DOM节点已经出来了,下一步就是如何渲染出虚拟DOM了,渲染虚拟DOM则需要一个特定的方法,在VueReact中会在HTML有一个idapp的真实DOM节点,最终渲染的时候被替换成了虚拟DOM节点生成的真是的DOM节点,接下来就按照这个思路继续实现一下,在VueReact都有render函数,这里也就同样使用这个名称进行命名了,在开始之前,首先要确认一点的是,无论是首次渲染还是更新都是通过render函数来完成的,所以要对其进行判断,其余的就不多赘述了。

//  渲染虚拟DOM
// 虚拟DOM节点树
// 承载DOM节点的容器,父元素
function render(vnode,container) {
// 首次渲染
mount(vnode,container);
};
// 首次渲染
function mount (vnode,container){
// 所需渲染标签类型
let {flag} = vnode;
// 如果是节点
if(flag === vnodeTypes.HTML){
// 调用创建节点方法
mountMethod.mountElement(vnode,container);
} // 如果是文本
else if(flag === vnodeTypes.TEXT){
// 调用创建文本方法
mountMethod.mountText(vnode,container);
};
};
// 创建各种元素的方法
const mountMethod = {
// 创建HTML元素方法
mountElement(vnode,container){
// 属性,标签名,子元素,子元素类型
let {tag,children,childrenFlag} = vnode;
// 创建的真实节点
let dom = document.createElement(tag);
// 在VNode中保存真实DOM节点
vnode.el = dom;
// 如果不为空,表示有子元素存在
if(childrenFlag !== childTeyps.EMPTY){
// 如果为单个元素
if(childrenFlag === childTeyps.SINGLE){
// 把子元素传入,并把当前创建的DOM节点以父元素传入
// 其实就是要把children挂载到 当前创建的元素中
mount(children,dom);
} // 如果为多个元素
else if(childrenFlag === childTeyps.MULTIPLE){
// 循环子节点,并创建
children.forEach((el) => mount(el,dom));
};
};
// 添加元素节点
container.appendChild(dom);
},
// 创建文本元素方法
mountText(vnode,container){
// 创建真实文本节点
let dom = document.createTextNode(vnode.children);
// 保存dom
vnode.el = dom;
// 添加元素
container.appendChild(dom);
}
};

通过上面的代码,就可完成真实DOM的渲染工作了,虽然但是这也只是完成了其中的一小部分而已。但是很多东西没有添加进去,比如动态添加style样式,给元素绑定样式,添加class等等等,一系列的问题都还没有解决,现在工作也只是简单的初始化而已。其实想要完成上述的功能也不是很难,要知道刚刚所说的所有东西都是添加到DOM节点上的,我们只需要在DOM节点上做文章就可以了,改进mountElement方法:

const mountMethod = {
// 创建HTML元素方法
mountElement(vnode,container){
// 属性,标签名,子元素,子元素类型
let {data,tag,children,childrenFlag} = vnode;
// 创建的真实节点
let dom = document.createElement(tag);
// 添加属性 (✪ω✪)更新了这里哦
data && domAttributeMethod.addData(dom,data);
// 在VNode中保存真实DOM节点
vnode.el = dom;
// 如果不为空,表示有子元素存在
if(childrenFlag !== childTeyps.EMPTY){
// 如果为单个元素
if(childrenFlag === childTeyps.SINGLE){
// 把子元素传入,并把当前创建的DOM节点以父元素传入
// 其实就是要把children挂载到 当前创建的元素中
mount(children,dom);
} // 如果为多个元素
else if(childrenFlag === childTeyps.MULTIPLE){
// 循环子节点,并创建
children.forEach((el) => mount(el,dom));
};
};
// 添加元素节点
container.appendChild(dom);
}
};
// dom添加属性方法
const domAttributeMethod = {
addData (dom,data){
// 挂载属性
for(let key in data){
// dom节点,属性名,旧值(方便做更新),新值
this.patchData(dom,key,null,data[key]);
}
},
patchData (el,key,prv,next){
switch(key){
case "style":
this.setStyle(el,key,prv,next);
break;
case "class":
this.setClass(el,key,prv,next);
break;
default :
this.defaultAttr(el,key,prv,next);
break;
}
},
setStyle(el,key,prv,next){
for(let attr in next){
el.style[attr] = next[attr];
}
},
setClass(el,key,prv,next){
el.setAttribute("class",next);
},
defaultAttr(el,key,prv,next){
if(key[0] === "@"){
this.addEvent(el,key,prv,next);
}
else {
this.setAttribute(el,key,prv,next);
}
},
addEvent(el,key,prv,next){
if(next){
el.addEventListener(key.slice(1),next);
}
},
setAttribute(el,key,prv,next){
el.setAttribute(key,next);
}
};

以上就简单的实现了对虚拟DOM的创建以及属性的以及事件的挂载,算是有一个很大的跨越了,只是完成初始化是远远不够的,还需要对其进一步处理,so有时间的话会继续对虚拟`DOM`的更新进行说明。也就是其`DIFF`算法部分。单一职责,一篇博客只做一件事,哈哈

总结

虚拟DOM在目前流行的几大框架中都作为核心的一部分使用,可见其性能的高效,本文只是简单的做一个简单的剖析,说到头来其实虚拟DOM就是使用JavaScript对象来表示DOM树的信息和结构,这个JavaScript对象可以构建一个真正的DOM树。当状态变更的时候用修改后的新渲染的的JavaScript对象和旧的虚拟DOMJavaScript对象作对比,记录着两棵树的差异。把差别反映到真实的DOM结构上最后操作真正的DOM的时候只操作有差异的部分就可以了。

下次再见,若有哪里有错误请大佬们及时指出,文章中若有错误请在评论区留言,我会尽快做出改正。

虚拟Dom详解 - (一)的更多相关文章

  1. 虚拟DOM详解

    虚拟DOM简介 Virtual Dom可以看做一棵模拟了DOM树的JavaScript对象树,其主要是通过vnode,实现一个无状态的组件,当组件状态发生更新时,然后触发Virtual Dom数据的变 ...

  2. 虚拟Dom详解 - (二)

    第一篇文章中主要讲解了虚拟DOM基本实现,简单的回顾一下,虚拟DOM是使用json数据描述的一段虚拟Node节点树,通过render函数生成其真实DOM节点.并添加到其对应的元素容器中.在创建真实DO ...

  3. brctl创建虚拟网卡详解

    brctl创建虚拟网卡详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 很久之前我分享过一篇关于搭建Openvpn的笔记,在笔记的最后我分享了一个脚本,是用来创建虚拟网卡的,今天 ...

  4. Day04 dom详解及js事件

    day04 dom详解 DOM的基础 Document对象 Element对象 Node对象 innerHTML 事件处理 表单验证   上次课内容回顾: JS中ECMAScript用法: JS定义变 ...

  5. JavaScript(2)---DOM详解

    JavaScript(2)---DOM详解 一.DOM概念 什么是DOM DOM全称为文本对象模型(Document Object Model),它定义了所有HTML元素的对象和属性,以及访问他们的方 ...

  6. JavaScript进阶内容——DOM详解

    JavaScript进阶内容--DOM详解 当我们已经熟练掌握JavaScript的语法之后,我们就该进入更深层次的学习了 首先我们思考一下:JavaScript是用来做什么的? JavaScript ...

  7. Signalr系列之虚拟目录详解与应用中的CDN加速实战

    目录 对SignalR不了解的人可以直接移步下面的目录 SignalR系列目录 前言 前段时间一直有人问我 在用SignalR 2.0开发客服系统[系列1:实现群发通讯]这篇文章中的"/Si ...

  8. Linux 虚拟网络设备详解之 Bridge 网桥

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 前面几篇文章介 ...

  9. XML解析之DOM详解及与SAX解析方法的比较

    XML解析(DOM) XML文件解析方法介绍 我们所用到的NSXMLParser是采用SAX方法解析 SAX(Simple API for XML) 只能读,不能修改,只能顺序访问,适合解析大型XML ...

随机推荐

  1. 启用IIS Express SSL(Https)的注意事项

    2年前搞国外的信用卡支付对接,必须用SSL方式调用第三方支付公司的接口,本地调试需要启用IIS Express的SSl,最近又搞类似需要SSL的项目,忘记怎么设置的了,本以为直接将原来的http后面加 ...

  2. 梳理数据库(MySQL)的主要知识点

    一.数据库类型 常用的关系型数据库 Oracle:功能强大,主要缺点就是贵 MySQL:互联网行业中最流行的数据库,免费.关系数据库场景中的功能 MySQL 都能很好的满足 MariaDB:MySQL ...

  3. 使用java计算数组方差和标准差

    使用java计算数组方差和标准差 觉得有用的话,欢迎一起讨论相互学习~Follow Me 首先给出方差和标准差的计算公式 代码 public class Cal_sta { double Sum(do ...

  4. ireport5.6.0分组显示

    一,ireport中分组 二,java调用实现分组 一,ireport中分组: 1,新建模板文件,纸张随意,名称随意,路径随意 2,连接要分组的数据源 3,添加测试表和数据 CREATE TABLE ...

  5. win10专业版激活步骤

    1.右键开始图标,选择[windows powershell(管理员)],或者命令提示符管理员: 2.打开命令窗口,复制这个命令slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T ...

  6. cmd大全

    CMD命令:开始->运行->键入cmd或command(在命令行里可以看到系统版本.文件系统版本) 1. appwiz.cpl:程序和功能 2. calc:启动计算器 3. certmgr ...

  7. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器出现no compatible source was found for this media问题的解决

    背景分析 EasyDSS流媒体解决方案总体可划分成三个部分:前端视频源设备(PC.手机.摄像机)流媒体数据获取并即时回传.流媒体服务器端直播和录像与回放.客户端直播播放与录像检索回放.前端推流我们使用 ...

  8. 大数据开发工程师面试《一》Shopee虾皮技术面

    一.项目问题 1 做了哪些项目2 使用什么技术3 哪个是你主导的项目,一共开发多少个接口,项目多长时间,数据库有多少个表 二.技术问题 1 用自己擅长的语言实现非递归单链表反转 现场手写2 Hadoo ...

  9. 使用自定义注解和AOP管理shiro权限

    一.场景 在使用shiro框架的时候,遇到了这样的需求:本系统有多个用户,每个用户分配不同角色,每个角色的权限也不一致.比如A用户拥有新闻列表的增删改查权限,而B用户只有查看新闻列表的权限,而没有删除 ...

  10. Python3使用random生成随机数

    本文介绍使用Python3中的random库生成随机数.随机小数.随机序列.随机字符串以及扑克洗牌等方法. 一.生成随机浮点数或小数 1.#生成0-1之间的浮点数 import random rnd ...