管理应用程序状态和用户界面的同步一直是前端UI开发复杂性的主要来源。目前出现了不同的方式来处理这个问题。本文简单讨论其中一种方式virtual dom。

文章概要:

  1. virtual dom 基本概念,存在原因。
  2. virtual dom 简单应用。
  3. virtual dom 简单实现思路。
  4. 小结

1.virtual dom 基本概念

1.1什么是virtual dom?

virtual dom:虚拟节点。它通过JS模拟DOM中的节点,可以通过特定的render方法将模板转换成js,再用特殊的方法h函数得到虚拟节点的结构,通过特定的patch方法渲染成真实的DOM更新。
数据更新时,渲染得到新的 virtual dom,然后与上一次得到的 virtual dom 进行 diff比对,dom diff 在JS层面计算得到所有需要变更 DOM,然后在 patch 过程中将需要变更 DOM更新到UI界面。

图片来源:JavaScript框架中的变化及其检测

1.2为啥要用virtual dom?

DOM 操作是“昂贵”的,怎么个昂贵法?先看一张图浏览器渲染的流程(WebKit 主流程 内容参考浏览器的工作原理

⑴ HTML解析出DOM Tree

⑵ CSS解析出Style Rules

⑶ WebKit内核的浏览器上,处理一个节点的样式的过程称为attachment,DOM Tree关联Style Rules生成Render Tree

⑷ Layout 根据Render Tree计算每个节点的信息

⑸ Painting 根据计算好的信息绘制整个页面

DOM最终呈现在界面的过程复杂

再看一张图,DOM 的大概数据结构

图片来源:Vue.js 技术揭秘

DOM本身数据结构复杂

综上:

DOM最终呈现在界面的过程复杂,DOM本身数据结构复杂

在开发过程中尽量减少直接操作 DOM ,用 JS 模拟 DOM 结构, DOM 变化的对比计算,放在 JS 层来做,计算完成后少量重绘或回流,提高性能。

同时DOM的操作跟数据挂钩,只用关心数据的变化,不需要关心具体DOM的操作。virtual dom这种方式就诞生了。

2.virtual dom 简单应用

virtual dom 的简单应用 此处snabbdom(A virtual dom library with focus on simplicity, modularity, powerful features and performance.) 库来举例(因为vue Virtual DOM 借鉴 Snabbdom库,想更多了解一下),当然也有其他的库 javaScript virtual dom 库。

2.1 snabbdom的几个函数

网站上的inline example (看图中粉色部分)

var snabbdom = require('snabbdom');
var patch = snabbdom.init([ // Init patch function with chosen modules
require('snabbdom/modules/class').default, // makes it easy to toggle classes
require('snabbdom/modules/props').default, // for setting properties on DOM elements
require('snabbdom/modules/style').default, // handles styling on elements with support for animations
require('snabbdom/modules/eventlisteners').default, // attaches event listeners
]);
var h = require('snabbdom/h').default; // helper function for creating vnodes var container = document.getElementById('container'); var vnode = h('div#container.two.classes', {
on: {
click: someFn
}
}, [
h('span', {
style: {
fontWeight: 'bold'
}
}, 'This is bold'),
' and this is just normal text',
h('a', {
props: {
href: '/foo'
}
}, 'I\'ll take you places!')
]);
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode); var newVnode = h('div#container.two.classes', {
on: {
click: anotherEventHandler
}
}, [
h('span', {
style: {
fontWeight: 'normal',
fontStyle: 'italic'
}
}, 'This is now italic type'),
' and this is still just normal text',
h('a', {
props: {
href: '/bar'
}
}, 'I\'ll take you places!')
]);
// Second `patch` invocation
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state

可以看出 snabbdom.init 方法 返回 patch 方法

patch 方法 有两种用法

(1)
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode); (2)
// Second `patch` invocation
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state

然后就是h 函数(生成vnodes)

DOM 结构通常被视为一棵树,而元素则被比成树上的节点(node),同理 virtual dom 的节点就是 Virtual Node 了及vnode。

var h = require('snabbdom/h').default; // helper function for creating vnodes

h函数的使用:h accepts a tag/selector as a string, an optional data object and an optional string or array of children.

h(选择器或者标签字符串,可选object对象属性,子节点数组或者string) 下图从h函vode 对应的dom 结构

2.2 snabbdom的简单应用

js 部分

var snabbdom = require('snabbdom');
// 定义patch 函数
var patch = snabbdom.init([ // Init patch function with chosen modules
require('snabbdom/modules/class').default, // makes it easy to toggle classes
require('snabbdom/modules/props').default, // for setting properties on DOM elements
require('snabbdom/modules/style').default, // handles styling on elements with support for animations
require('snabbdom/modules/eventlisteners').default, // attaches event listeners
]); // 定义h 函数
var h = require('snabbdom/h').default; // helper function for creating vnodes var container = document.getElementById('container'); // 原始数据
var data = ['周杰伦', '林俊杰 ', '陈奕迅']
// 渲染函数
var vnode
render(data) function render(data) {
var newVnode = h('div', {
id: 'container'
}, [
h('button', {
id: 'btn1',
on: {
click: changeSinger
}
}, '替换一个歌手'),
h('button', {
id: 'btn2',
on: {
click: addSinger
}
}, '增加一个歌手'),
h('ul', {}, data.map(function(item) {
return h('li', {
style: {
color: '#f3c8d3'
}
}, item)
}))
]) if (vnode) {
// 重新渲染
patch(vnode, newVnode)
} else {
// 初次渲染
patch(container, newVnode)
} // 存储当前的 vnode,方便下次做对比
vnode = newVnode
} function changeSinger() {
data.splice(0, 1, '五月天')
render(data)
} count = 0 function addSinger() {
count++
data.push('五月天' + count)
render(data)
} // html 部分----------------------------------------------------------------------- <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>demo</title>
</head>
<body>
<div id="container"></div>
<!-- built files will be auto injected -->
</body>
</html>

以上代码 写完安装依赖打包出来,点击‘替换一个歌手’ 按钮,观察Element变化 如下图 变化的地方只有一个li 的文本

// 原始数据
var data = ['周杰伦', '林俊杰 ', '陈奕迅']

// 新数据

var data = ['五月天', '林俊杰 ', '陈奕迅']

virtual dom 这种实现,用 JS 模拟 DOM 结构, DOM diff比对,放在 JS 层来做,找出变化了DOM渲染,提高性能。

同时DOM的操作跟数据挂钩,我们只用关心数据的变化,不需要关心具体DOM的操作。

3.virtual dom 简单实现思路

从上面例子,可以大概推测 实现一个简单的 virtual dom 比较重要的就是

  • h函数生成 vnode (JS 模拟 DOM 结构virtual dom)
  • path(container, vnode) 初次渲染界面(用virtual dom构建了真的DOM树,挂载到container上面)
  • 缓存 vnode
  • 数据更新(生成新的vnode),生成新的newVnode
  • path(vnode, newVnode) 将旧视图更新为新状态(diff算法 比对vnode, newVnode,找出需要修改的dom 修改)
  • 更新缓存 vnode

根据不同的结构生成vnode(可能对模版处理渲染)。

假设此时已经存在一个JS模拟DOM的数据结构。实现简单的如下函数比对vnode, newVnode的不同,并修改需要变化的真实节点。

此时的模拟object 结构

{
tagName: 'ul',
attrs: {
id: 'list'
},
children: [{
tag: 'li',
attrs: {
className: 'item'
}
children: '周杰伦'
}, {
tag: 'li',
attrs: {
className: 'item'
}
children: '林俊杰'
}]
}

递归构建dom树

function createElement(vnode) {
var tagName = vnode.tagName;
var attrs = vnode.attrs || {};
var children = vnode.children;
if (!tagName) {
return
} // 创建真实的 DOM 元素
let element = document.createElement(this.tagName);
// 属性
var attrName;
for (attrName in attrs) {
if (attrs.hasOwnProperty(attrName)) {
// 给 element 添加属性
element.setAttribute(attrName, attrs[attrName]);
}
}
// 子元素是字符串
if (isString(children)) {
let childElement = document.createTextNode(children);
element.appendChild(childElement);
} else {
// 子元素是数组
children.forEach(child => {
// 给 element 添加子元素
element.appendChild(createElement(child));
})
}
vnode.elem = element;
return element;
}

比对不同更行element

function updateChildren(vnode, newVnode) {
var children = vnode.children || []
var newChildren = newVnode.children || []
// 比对 tagName 和 Attrs 不同直接替换节点
if(diffTag(children,newChildren) || diffAttrs(children,newChildren)) {
replaceNode(children, newChildren)
return
}
// 子节点都是字符串 文本不同直接替换节点
if (isString(children)&&isString(newChildren)) {
if (diffText(children, newChildren)) {
replaceNode(children, newChildren)
}
return
}
// 一个是数组 一个字符串 文本 子节点不同替换节点
if (!isString(children) && isString(newChildren)) {
replaceNode(children, newChildren)
return
}
// 一个字符串 文本 一个是数组 子节点不同替换节点
if (isString(children) && !isString(newChildren)) {
replaceNode(children, newChildren)
return
}
// 子节点都是 数组
children.forEach(function(childVnode, index) {
var newChildVnode = newChildren[index]
// 深层次对比,递归
updateChildren(childVnode, newChildVnode)
})
} function isString(value) {
return typeof value === 'string'
} function diffText (text, newtext) {
return text !== newtext
}
function diffTag (children, newChildren) {
return children.tagName !== newChildren.tagName
}
function diffAttrs(oldNode, newNode) {
for (let attr in oldNode.attrs) {
if (oldNode.attrs[attr] != newNode.attrs[attr]) {
return true
}
}
for (let attr in newNode.attrs) {
if (!(oldNode.attrs.hasOwnProperty(attr))) {
return true
}
}
return false;
} function replaceNode (vnode, newVnode) {
var elem = vnode.elem // 真实的 DOM 节点
var parent = elem.parentNode
var newElem = createElement(newVnode)
parent.replaceChild(elem,newElem)
}

简单梳理了下思路,dom diff  比较复杂,上面函数是理想情况下简单实现。

4.小结

文中例子比较粗糙,理解不准确之处,还请教正。

文章简单的描述了virtual dom 是什么,解决了什么问题,以及简单实现的小思路。

撒花~~

参考资料:JavaScript框架中的变化及其检测

浏览器的工作原理

Vue.js 技术揭秘

snabbdom

virtual dom 简单了解的更多相关文章

  1. Virtual DOM的简单实现

    了解React的同学都知道,React提供了一个高效的视图更新机制:Virtual DOM,因为DOM天生就慢,所以操作DOM的时候要小心翼翼,稍微改动就会触发重绘重排,大量消耗性能. 1.Virtu ...

  2. React virtual DOM explained in simple English/简单语言解释React的虚拟DOM

    初学React,其中一个很重要的概念是虚拟DOM,看了一篇文章,顺带翻译一下. If you are using React or learning React, you must have hear ...

  3. 简单说明 Virtual DOM 为啥快

    Virtual DOM 就是用 JS 的对象来描述 DOM 结构的一个 DOM 树.如: var element = { tagName: 'ul', // 节点标签名 props: { // DOM ...

  4. 个人对于Virtual DOM的一些理解

    之前一直认为react的Virtual DOM操作会比传统的操作DOM要快,这其实是错误的,React 从来没有说过 "React 比原生操作 DOM 快".如果没有 Virtua ...

  5. 抛开react,如何理解virtual dom和immutability

    去年以来,React的出现为前端框架设计和编程模式吹来了一阵春风.很多概念,无论是原本已有的.还是由React首先提出的,都因为React的流行而倍受关注,成为大家研究和学习的热点.本篇分享主要就聚焦 ...

  6. 深度剖析:如何实现一个 Virtual DOM 算法

    本文转载自:https://github.com/livoras/blog/issues/13 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步 ...

  7. Virtual DOM 算法

    前端 virtual-dom react.js javascript 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步骤一:用JS对象模拟DOM ...

  8. React v16-alpha 从virtual dom 到 dom 源码简读

    一.物料准备 1.克隆react源码, github 地址:https://github.com/facebook/react.git 2.安装gulp 3.在react源码根目录下: $npm in ...

  9. 深度理解 Virtual DOM

    目录: 1 前言 2 技术发展史 3 Virtual DOM 算法 4 Virtual DOM 实现 5 Virtual DOM 树的差异(Diff算法) 6 结语 7 参考链接 1 前言 我会尽量把 ...

随机推荐

  1. js-事件以及window操作

    属性 当以下情况发生时,出现此事件 onblur 元素失去焦点 onchange 用户改变域的内容 onclick 鼠标点击某个对象 ondblclick 鼠标双击某个对象 onfocus 元素获得焦 ...

  2. java线程之二(synchronize和volatile方法)

    要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现.拿上篇博文中的例子来说明,在多个线程之间共享了Count ...

  3. VS2013使用滚动条缩略图、双击选中高亮、配色方案、代码竖虚线(缩进标尺)

    1.双击代码或选中代码高亮,用以下插件,反应很灵敏,我安装的是第三个 2.代码编辑器的滚动条缩略图是VS自带的,需要打开菜单----工具----选项,如下图设置: 3.VS默认的选中颜色,需要打开菜单 ...

  4. Linux 上安装JDK

    JDK下载 下载 JDK Linux 版本(注意看自己安装 Linux 系统的位数,本人的是64位) 1.oracle 官网下载地址:jdk-7u80-linux-x64.gz(可能需要注册下载,嫌麻 ...

  5. Spring Boot + JPA(hibernate 5) 开发时,数据库表名大小写问题

      (转载)Spring Boot + JPA(hibernate 5) 开发时,数据库表名大小写问题   这几天在用spring boot开发项目, 在开发的过程中遇到一个问题hibernate在执 ...

  6. Python练手例子(4)

    16.一个数如果恰好等于它的因子之和,这个数就称为"完数".例如6=1+2+3.编程找出1000以内的所有完数. 程序分析:请参照程序Python 100例中的第14个例子 #py ...

  7. iview menu组件手动收起与展开

    本文主要介绍menu组件在有子菜单时如何手动的展开与收起. 展开: 在需要展开的地方先设置openname变量如this.openname = ["设置"]; 再在$nextTic ...

  8. scheduling while atomic和bad: scheduling from the idle thread(转)

    https://blog.csdn.net/shanzhizi/article/details/22949121 https://blog.csdn.net/wwwtovvv/article/deta ...

  9. 怎样将PDF文件转换成Excel表格

    PDF文件怎样转换成Excel表格呢?因为很多的数据信息现在都是通过PDF文件进行传输的,所以很多时候,信息的接受者都需要将这些PDF文件所传输的数据信息转换成Excel表格来进行整理,但是我们应该怎 ...

  10. mysql 5.7.21 解压版安装配置方法图文教程

    引用:https://www.jb51.net/article/140951.htm 1.首先,你要下载MySQL解压版,下载地址,图解: 2.解压安装包,根据自己的喜好选择路径,我选择的路径是C:\ ...