vitual dom实现(转)
1. 通过JavaScript来构建虚拟的DOM树结构,并将其呈现到页面中;
2. 当数据改变,引起DOM树结构发生改变,从而生成一颗新的虚拟DOM树,将其与之前的DOM对比,将变化部分应用到真实的DOM树中,即页面中。
为什么要使用vitual dom?(转自:https://www.jianshu.com/p/616999666920)
一、构建虚拟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实现(转)的更多相关文章
- 浅谈 Virtual DOM 的那些事
背景 我们都知道频繁的dom给我们带来的代价是昂贵的,例如我们有时候需要去更新Table 的部分数据,必须去重新重绘表格,这代价实在是太大了,相比于频繁的手动去操作dom而带来性能问题,vdom很好的 ...
- React Native For Android 架构初探
版权声明:本文由王少鸣原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/171 来源:腾云阁 https://www.qclo ...
- 关于React setState的实现原理(一)
前言 首先在学习react的时候就对setSate的实现有比较浓厚的兴趣,那么对于下边的代码,可以快速回答吗? class Root extends React.Component { constru ...
- vue react angular对比
1.数据绑定 1)vue 把一个普通对象传给Vued的data选项,Vue会遍历此对象的所有属性,并使用Object.defineProperty将这些属性全部转为getter/setter.Obje ...
- 浅谈React和VDom关系
组件化 组件的封装 组件的复用 组件的封装 视图 数据 视图和数据之间的变化逻辑 import React, {Component} from 'react'; export default clas ...
- 关于DOM的操作以及性能优化问题-重绘重排
写在前面: 大家都知道DOM的操作很昂贵. 然后贵在什么地方呢? 一.访问DOM元素 二.修改DOM引起的重绘重排 一.访问DOM 像书上的比喻:把DOM和JavaScript(这里指ECMScri ...
- 读书笔记:JavaScript DOM 编程艺术(第二版)
读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...
- 页面嵌入dom与被嵌入iframe的攻防
1.情景一:自己的页面被引入(嵌入)至别人的页面iframe中 if(window.self != window.top){ //url是自己页面的url window.top.location.hr ...
- 通俗易懂的来讲讲DOM
DOM是所有前端开发每天打交道的东西,但是随着jQuery等库的出现,大大简化了DOM操作,导致大家慢慢的“遗忘”了它的本来面貌.不过,要想深入学习前端知识,对DOM的了解是不可或缺的,所以本文力图系 ...
随机推荐
- git加速和只下载部分目录
浅复制 工作要用到的.git有1.8G太大了.下载过程要好几个小时,太慢了.可以这样操作 git clone 默认会下载项目的完整历史版本,如果你只关心最新版的代码,而不关心之前的历史信息,可以使用 ...
- 北京大学Cousera学习笔记--5-计算导论与C语言基础--计算机的基本原理-设计程序
只要你认真的思考,你就会发现这个世界是如此的简单,正如我们想象的一样,正因为如此,我们的思考才更加的有价值 1.单词:关键字(有特定含义的):其他词用关键字定义出来 2.数和计算符号:数据类型+运算符 ...
- fork()相关的源码解析
fork()的真正执行采用的是do_fork()函数,所以下文将从do_fork()函数对fork()进行源码解析.下图是do_fork()的源码函数设计: 从上图我们可以看到do_fork()涉及到 ...
- CSRF原理
全称跨站伪造
- 数据迁移时 提示 No changes detected
1.删除数据库中django_migrations 中对应的信息 2.删除app下的migrations对应的文件 3.重新执行就可成功 如不成功 ,直接删库 ,重新迁移
- mysql 用行号rownum更新顺序号字段
UPDATE customer SET OrderNo = ( SELECT afterOrder.rowNum FROM ( SELECT @row_number := CASE WHEN @cus ...
- 阿里云HttpClient跨天之后解析不了域名
也许这是一个少见的情况,我使用HttpClient写了一个调用第三方服务的请求,在本机测试和腾讯云上测试都没有问题,但是放到阿里云之后,刚启动的时候是没有问题的,但是每次过零点之后,就会报异常: ja ...
- IT题库7-线程加锁
转载:http://www.cnblogs.com/linjiqin/p/3208843.html 一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏.例如:两个线程T ...
- jq+bootstrap响应式系统管理页面
用bootstrap搭建的一个系统管理页面基于经典的HTML+Css 弊端:代码量太大,用vue组件化开发的思想更加便捷且方便!不过如果是做纯展示的系统页面,只需要一些简单的数据交互,用jq配合boo ...
- node获取当前路径的三种方法
node提供了3种获取路径的方法 ,在当前目录下,运行node const {resolve} = require('path') console.log('__dirname : ' + __di ...