JavaScript中的虚拟DOM是什么?
讲解JavaScript的虚拟DOM实现和应用
一、什么是虚拟DOM?
虚拟DOM(Virtual DOM)是 React 框架的核心概念之一,也是前端 Web 开发中的一种优化技术。
在 HTML 和 JavaScript 的交互中,当页面发生变化时,我们需要做出更新,一般的做法是直接操作 DOM,改变其中的元素或属性。但这种方式将会造成大量的 DOM 操作,特别是当页面结构较为复杂时,操作的效率会极为低下。虚拟DOM 的目标就是针对这个问题,即通过创建一种虚拟的内存数据结构,模拟真实 DOM 树,让所有的改动在虚拟 DOM 中完成。这样可以有效降低真实 DOM 的操作频率,提升性能。等到所有操作完成之后,再将虚拟 DOM 转换为真实 DOM,并且只对发生变化的内容进行操作,减少了操作成本。这就是虚拟DOM的最大优点。
虚拟DOM 的基本原理如下:
(1)将 DOM 的结构以 JavaScript 对象的形式表示出来,将其存储在内存中。
(2)每当状态变更时,新的 DOM 树就会通过 diff 算法与旧的 DOM 树比较,找出差异,只对差异部分做更新。
(3)经过更新后的虚拟 DOM 同旧的 DOM 树进行比较,更改发生变化的部分。二、JavaScript虚拟DOM实现
为了更好的理解虚拟DOM,我们可以通过自己实现一个简单的虚拟DOM来对其进行深入的学习。
假设现在我们要实现一个 Button 组件,对于这个组件,我们需要定义它的属性和事件:
“`
class Button extends Component {render() {
const { children, onClick } = this.props;
return (
);
}
}
“`在上面的代码中,我们可以看到,Button 组件继承自 React 的 Component 容器组件,在 render 函数中,组件的 DOM 结构被定义在函数返回的 HTML 元素中。
如果我们要实现一个虚拟DOM,我们可以从以下三个方面入手:虚拟DOM对象、虚拟DOM的diff算法、虚拟DOM的渲染过程。
1、虚拟DOM对象
首先,我们来创建一个虚拟DOM对象,这里我们将使用JSON对象来模拟虚拟DOM。一个标准的虚拟DOM对象必须具有以下属性:
(1)tag:当前节点的标签名
(2)props:当前节点的属性对象
(3)children:当前节点的子节点我们来看一下代码的实现:
“`
let element = {
tag: ‘Button’,
props: {
onClick: ()=>{alert(‘Hello World!’)}
},
children: [‘Click me!’],
};
“`在上面的代码中,我们创建了一个名为 element 的虚拟DOM对象,其中 tag 为 Button 标签,props 为 onClick 事件,children 为 Click me! 文字。
2、虚拟DOM的diff算法
相较于操作 Real DOM,最大的优势在于 diff 算法,这个算法的目的是比较新旧虚拟 DOM 树之间的差异,找出需要更新的地方,删除不再需要的节点,插入新的节点等。
我们可以通过如下四种情况来判断需要更新哪些节点:
(1)根节点更新,需重新渲染整个页面
(2)节点类型发生变化
(3)节点属性发生变化
(4)子节点有变化,需要递归对子节点进行判断由于实现 diff 算法过于复杂,我们在这里只是简单的讲解一下:当新旧 Virtual DOM 树之间有变化的时候,diff 算法会对比它们之间的不同之处,最终返回一个需要更新的变化列表,React 在实际应用中的Virtual DOM采用的是深度优先遍历算法来进行比较,同时还使用了优化算法,对于同级别相邻的节点,列表采用了移动算法,而非删除和插入,以此来大大提高渲染效率。
3、虚拟DOM的渲染过程
从 React16之后,官方给出了另一个虚拟DOM的实现,也就是 Fiber,这个实现方式是使用链表代替递归调用,让虚拟 DOM 可以被更好的管理,可以暂停、中止和恢复 DOM 渲染过程,同时控制任务与优先级,可以根据任务的优先级高低决定是否执行。
这里我们将演示简单的虚拟DOM渲染过程,在这个例子中,我们将虚拟DOM渲染为实际的页面元素。
首先我们要准备一段类型与实际 DOM 的映射表,这里我们简单定义了几种基本的 HTML 元素类型:
“`
const VDOM_TYPE = {
TEXT: ‘text’,
ELEMENT: ‘element’,
COMPONENT: ‘component’,
};const TAG_NAME_MAP = {
div: ‘div’,
h1: ‘h1’,
h2: ‘h2’,
h3: ‘h3’,
span: ‘span’,
button: ‘button’,
input: ‘input’,
select: ‘select’,
};
“`接下来我们来定义一个 createDOM 函数,该函数用于依据传入的虚拟 DOM 节点对象,生成实际的 DOM 节点。该函数的实现方式十分简单,会依据虚拟 DOM 对象的类型生成相应的真实 DOM 节点元素,并将属性和子元素进行赋值:
“`
/**
* 根据 VirtualDOM 对象类型生成实际的 DOM 元素
* @param {*} vdom
*/
function createDOM(vdom) {
let type = vdom.type;
let dom = null;if (type === VDOM_TYPE.TEXT) {
dom = document.createTextNode(vdom.props.textContent);
} else if (type === VDOM_TYPE.COMPONENT) {
const componentInstance = new vdom.type(vdom.props);
const componentInstanceDOM = componentInstance.render().type;
dom = createDOM(componentInstanceDOM);
componentInstance._dom = dom;
} else {
dom = document.createElement(TAG_NAME_MAP[type]);// 给 DOM 设置属性
setProps(dom, vdom.props);// 将子元素添加到父元素中
if (vdom.props && vdom.props.children && vdom.props.children.length) {
vdom.props.children.map(createDOM).forEach(dom.appendChild.bind(dom));
}
}
return dom;
}
“`接下来我们来看一下 setProps 函数,该函数将传递进来的 props 赋值给 DOM 节点的属性:
“`
/**
* 将 vnode 的属性赋值到真实的 dom 节点上
* @param {*} dom
* @param {*} props
*/
function setProps(dom, props) {
// 将 vnode.props 属性值赋值到真实的 dom 节点上
Object.keys(props).forEach(propName => {
if(propName === ‘children’) return;// 获取 props 上的属性值,并赋值给 dom
const propValue = props[propName];
dom.setAttribute(propName, propValue);
});
}
“`最后,我们将 createDOM 函数返回的 DOM 元素,插入到 DOM 树中:
“`
/**
* 将生成的 DOM 元素插入到页面中
* @param {*} newDom
* @param {*} targetDom
*/
function render(newDom, targetDom) {
targetDom.appendChild(createDOM(newDom));
}
“`到这里,一个简单的虚拟 DOM 就实现了。我们来看一个使用这个虚拟 DOM 的例子:
“`
const Hello = function(props) {
return {
type: VDOM_TYPE.ELEMENT,
props: {…props},
children:[
{
type: VDOM_TYPE.ELEMENT,
props: {textContent: ‘Hello, ‘},
children:[],
},
{
type: VDOM_TYPE.TEXT,
props: {textContent: props.name},
children:[],
},
]
};
};const World = function(props) {
return {
type: VDOM_TYPE.ELEMENT,
props: {…props},
children:[
{
type: VDOM_TYPE.ELEMENT,
props: {textContent: ‘World!’},
children:[],
}
]
};
};render(
{
type: VDOM_TYPE.ELEMENT,
props: {},
children: [
{
type: Hello,
props: {name: ‘John’},
children: [],
},
{
type: World,
props: {},
children: [],
},
],
},
document.getElementById(‘root’)
);
“`在上面的代码中,我们定义了一个Hello和World组件,每个组件生成一段虚拟DOM结构,最后我们将两个组件合并到一起,然后将它生成实际 DOM 并插入到页面的 root 元素中。运行结果会在页面上显示出 Hello, John! World! 这句话。
三、虚拟DOM的应用
虚拟 DOM 有很多的应用场景,因为它的性能优势与扩展性都是非常好的。
1、提高渲染效率
虚拟DOM通过 diff 算法实现了针对 Real DOM 操作的优化,可以简化 DOM 操作、提高性能。同时,虚拟 DOM 还可以避免浏览器的重排及重绘,从而极大地提高渲染效率。
2、跨平台渲染
React Native 则利用虚拟 DOM 技术实现了跨平台渲染,使得我们不需要为每个平台都单独开发,在 React Native 中使用与 Web 开发相同的技术栈,开发出可用于 iOS、Android 等平台的应用。
3、实现状态管理
虚拟 DOM 可以建立在 React 之上,通过这种方式,可以在创建和操作虚拟 DOM 的同时,利用 React 的状态管理机制,更好的管理应用状态。这样可以方便地管理组件的异步状态,通过对虚拟 DOM 的树形结构存储和访问,可以轻松地实现对状态的管理。
四、小结
虚拟 DOM 是 Web 开发中一个重要的技术,它通过在内存中创建虚拟树形结构,模拟真实 DOM 树,达到节省更新成本、提高渲染效率的目的。虚拟 DOM 的核心思想是避免不必要的 DOM 操作,减少了浏览器的重排和重绘,同时极大地提高了渲染效率。虚拟 DOM 不仅提高了 Web 开发的效率,同时可以应用于移动开发、桌面应用开发等其他领域,是现代化软件开发的重要组成部分。
2023年06月10日 14:04