1. 通过JavaScript来构建虚拟的DOM树结构,并将其呈现到页面中;

2. 当数据改变,引起DOM树结构发生改变,从而生成一颗新的虚拟DOM树,将其与之前的DOM对比,将变化部分应用到真实的DOM树中,即页面中。

为什么要使用vitual dom?(转自:https://www.jianshu.com/p/616999666920)

当你用传统的源生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。
操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验。
虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。例如前面的例子,假如一次操作中有10次更新DOM的动作,虚拟DOM不会立即执行DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性patch到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量。

一、构建虚拟DOM

虚拟DOM,其实就是用JavaScript对象来构建DOM树,

通过JavaScript,我们可以很容易构建它,如下:

var elem = Element({
tagName: 'ul',
props: {'class': 'list'},
children: [
Element({tagName: 'li', children: ['item1']}),
Element({tagName: 'li', children: ['item2']})
]
});
// Element构造函数
function Element({tagName, props, children}){
if(!(this instanceof Element)){
return new Element({tagName, props, children})
}
this.tagName = tagName;
this.props = props || {};
this.children = children || [];
}

通过Element我们可以任意地构建虚拟DOM树了。但是有个问题,虚拟的终归是虚拟的,我们得将其呈现到页面中。

怎么呈现呢?

从上面得知,这是一颗树嘛,那我们就通过遍历,逐个节点地创建真实DOM节点:

  1. createElement;

  2. createTextNode.

怎么遍历呢?

因为这是一颗树嘛,对于树形结构无外乎两种遍历:

  1. 深度优先遍历(DFS)

  2. 广度优先遍历(BFS)

下面我们就来回顾下《数据结构》中,这两种遍历的思想:

1. DFS利用栈来遍历数据

2. BFS利用队列来遍历数据

(为了将孩子节点append到父节点中,采用DFS)

Element.prototype.render = function(){
var el = document.createElement(this.tagName),
props = this.props,
propName,
propValue;
for(propName in props){
propValue = props[propName];
el.setAttribute(propName, propValue);
}
this.children.forEach(function(child){
var childEl = null;
if(child instanceof Element){
childEl = child.render();
}else{
childEl = document.createTextNode(child);
}
el.appendChild(childEl);
});
return el;
};

此时,我们就可以轻松地将虚拟DOM呈现到指定真实DOM中。假设,我们将上诉ul虚拟DOM呈现到页面body中,如下:

var elem = Element({
tagName: 'ul',
props: {'class': 'list'},
children: [
Element({tagName: 'li', children: ['item1']}),
Element({tagName: 'li', children: ['item2']})
]
});
document.querySelector('body').appendChild(elem.render());

二、处理DOM更新

在前一小结,我们成功地实现了虚拟DOM,并将其转化为真实DOM,呈现在页面中。

接下来,我们就处理当DOM更新时,怎样通过新旧虚拟DOM对比,然后将变化部分更新到真实DOM中的问题。

DOM更新,无外乎四种情况,如下:

  1. 新增节点;

  2. 删除节点;

  3. 替换节点;

  4. 父节点相同,对比子节点.

毫无疑问,遍历DOM树仍然采用DFS遍历。

因为我们要将变化的节点更新到真实DOM中,所以还得传入真实的DOM根节点,并且真实的DOM节点与虚拟的DOM节点,树形结构一致,故通过标记可以记录节点变化位置,如下:

实现函数如下:

function updateElement($root, newElem, oldElem, index = 0) {
if (!oldElem){
$root.appendChild(newElem.render());
} else if (!newElem) {
$root.removeChild($root.childNodes[index]);
} else if (changed(newElem, oldElem)) {
if (typeof newElem === 'string') {
$root.childNodes[index].textContent = newElem;
} else {
$root.replaceChild(newElem.render(), $root.childNodes[index]);
}
} else if (newElem.tagName) {
let newLen = newElem.children.length;
let oldLen = oldElem.children.length;
for (let i = 0; i < newLen || i < oldLen; i++) {
updateElement($root.childNodes[index], newElem.children[i], oldElem.children[i], i)
}
}
}

其中的changed方法,简单实现如下:

function changed(elem1, elem2) {
return (typeof elem1 !== typeof elem2) ||
(typeof elem1 === 'string' && elem1 !== elem2) ||
(elem1.type !== elem2.type);
}

好了,一个简单的虚拟DOM就实现了。

三、效果展示

通过JS构建一颗虚拟DOM(如上诉ul),并将其呈现到页面中,然后替换其子节点,动态更新到真实DOM中,如下:

<body>
<button id="refresh">refresh element</button>
<div id="root"></div>
<script src="./virtualDom.js"></script>
<script>
var elem = Element({
tagName: 'ul',
props: {'class': 'list'},
children: [
Element({tagName: 'li', children: ['item1']}),
Element({tagName: 'li', children: ['item2']})
]
});
var newElem = Element({
tagName: 'ul',
props: {'class': 'list'},
children: [
Element({tagName: 'li', children: ['item1']}),
Element({tagName: 'li', children: ['hahaha']})
]
});
var $root = document.querySelector('#root');
var $refresh = document.querySelector('#refresh');
updateElement($root, elem);
$refresh.addEventListener('click', () => {
updateElement($root, newElem, elem);
});
</script>
</body>

vitual dom实现(转)的更多相关文章

  1. 浅谈 Virtual DOM 的那些事

    背景 我们都知道频繁的dom给我们带来的代价是昂贵的,例如我们有时候需要去更新Table 的部分数据,必须去重新重绘表格,这代价实在是太大了,相比于频繁的手动去操作dom而带来性能问题,vdom很好的 ...

  2. React Native For Android 架构初探

    版权声明:本文由王少鸣原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/171 来源:腾云阁 https://www.qclo ...

  3. 关于React setState的实现原理(一)

    前言 首先在学习react的时候就对setSate的实现有比较浓厚的兴趣,那么对于下边的代码,可以快速回答吗? class Root extends React.Component { constru ...

  4. vue react angular对比

    1.数据绑定 1)vue 把一个普通对象传给Vued的data选项,Vue会遍历此对象的所有属性,并使用Object.defineProperty将这些属性全部转为getter/setter.Obje ...

  5. 浅谈React和VDom关系

    组件化 组件的封装 组件的复用 组件的封装 视图 数据 视图和数据之间的变化逻辑 import React, {Component} from 'react'; export default clas ...

  6. 关于DOM的操作以及性能优化问题-重绘重排

     写在前面: 大家都知道DOM的操作很昂贵. 然后贵在什么地方呢? 一.访问DOM元素 二.修改DOM引起的重绘重排 一.访问DOM 像书上的比喻:把DOM和JavaScript(这里指ECMScri ...

  7. 读书笔记:JavaScript DOM 编程艺术(第二版)

    读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...

  8. 页面嵌入dom与被嵌入iframe的攻防

    1.情景一:自己的页面被引入(嵌入)至别人的页面iframe中 if(window.self != window.top){ //url是自己页面的url window.top.location.hr ...

  9. 通俗易懂的来讲讲DOM

    DOM是所有前端开发每天打交道的东西,但是随着jQuery等库的出现,大大简化了DOM操作,导致大家慢慢的“遗忘”了它的本来面貌.不过,要想深入学习前端知识,对DOM的了解是不可或缺的,所以本文力图系 ...

随机推荐

  1. 2019CCF-GAIR全球人工智能与机器人峰会于7月在深圳召开

    全球人工智能与机器人峰会(CCF-GAIR)是由中国计算机学会(CCF)主办,雷锋网.香港中文大学(深圳)承办,得到了深圳市政府的大力指导,是国内人工智能和机器人学术界.工业界及投资界三大领域的顶级交 ...

  2. php----------const 定义的常量和define()定义的常量的区别?

    用法一:const用于类成员变量,一经定义不可修改,define用于全局常量,不可用于类成员变量的定义,const可在类中使用也可以在类外面使用,define不能. 定义:const 常量名=值; 没 ...

  3. js某一元素在数组中的索引

    第一种:数组遍历 function search(arr,dst){ var i = arr.length; while(i-=1){ if (arr[i] == dst){ return i; } ...

  4. 蓝桥杯近三年决赛题之一(15年B组)

    自己计时4小时做了试试,发现只用了2个小时多一点,第二题和第六题实在没办法,不会做,第五题用了暴力(过不了多少数据),其他三题在网上看了看 应该都是对的,下面放上试题及答案. 1. 标题:积分之迷 小 ...

  5. ultiple Endpoints may not be deployed to the same path

    @Configurationpublic class WebSocketConfig { //打war包启动需要注释掉此:否则报 :DeploymentException: Multiple Endp ...

  6. Android-Gradle(五)

    Android studio不仅允许你为你的app和依赖库创建模块,同时也可为Android wear,Android TV,Google App Engine等创建模块,而这些单独的模块又可以在一个 ...

  7. class多态

    多态代码实现: class Animal(object): def __init__(self, name): # Constructor of the class self.name = name ...

  8. Dev TreeList 某一列进行格式化显示

    private void treeList1_GetNodeDisplayValue(object sender, GetNodeDisplayValueEventArgs e)        {   ...

  9. mysql,jdbc、连接池

    show processlist; select * from information_schema.processlist; Command: The type of command the thr ...

  10. 自定义域名访问本地WEB应用

    自定义域名访问本地WEB应用 本地安装了WEB服务端,怎样通过自定义域名方式实现从公网访问本地WEB应用? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动WEB服务端 默认安装的WEB ...