新聞中心

創(chuàng)新互聯(lián)公司專注于衢州網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供衢州營銷型網(wǎng)站建設(shè),衢州網(wǎng)站制作、衢州網(wǎng)頁設(shè)計(jì)、衢州網(wǎng)站官網(wǎng)定制、微信平臺(tái)小程序開發(fā)服務(wù),打造衢州網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供衢州網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
聲明式用戶界面
編寫聲明式UI的傳統(tǒng)方法是更改元素的innerHTML屬性。例如,如果我想向
document.body.innerHTML = 'Hello World!';
// now has aHello World!child.
我們可以認(rèn)識(shí)到innerHTML允許我們以聲明地方式定義UI,但它的效率不高。
效率低下源于每次更改用戶界面時(shí)的解析、破壞和重建innerHTML,都需要遵循四個(gè)步驟:
- 解析innerHTML字符串到DOM節(jié)點(diǎn)樹中。
- 移除所有內(nèi)容元素。
- 將DOM節(jié)點(diǎn)樹插入元素。
- 執(zhí)行布局計(jì)算和重繪屏幕。
這個(gè)過程在計(jì)算上非常昂貴,并且可能導(dǎo)致渲染速度顯著降低。
命令式用戶界面
那么,這個(gè)問題是如何解決的呢?那就是選擇使用DOM, 這種方法要比innerHTML方法快3倍。
const div = document.createElement('div');
div.textContent = 'Hello World!';
document.body.appendChild(div);
然而,我們可以認(rèn)識(shí)到,手動(dòng)編寫這個(gè)可能很麻煩,特別是當(dāng)UI中有很多交互時(shí),因?yàn)槲覀冃枰钍降刂付總€(gè)步驟。以聲明的方式編寫UI要優(yōu)雅得多。
不過,React作者創(chuàng)建了VirtualDOM,允許我們以一種比innerHTML更快的呈現(xiàn)方式編寫UI,而且是聲明式的。
理解VirtualDOM
為了最好地了解VirtualDOM是如何工作的,讓我們概述一下流程,然后構(gòu)建一個(gè)示例。
VirtualDOM是一種呈現(xiàn)UI的方法。該方法利用模仿DOM樹的JavaScript對象樹(“虛擬”節(jié)點(diǎn))。
//Hello World!
const div = document.createElement('div');
div.style = 'color: red';
div.textContent = 'Hello World!';
以上
const divVNode = {
type: 'div',
props: {
style: 'color: red'
}
children: ['Hello World!']
};
我們可以注意到虛擬節(jié)點(diǎn)有三個(gè)屬性:
- tag:將元素的標(biāo)記名稱存儲(chǔ)為字符串。
- props:將元素的屬性和屬性存儲(chǔ)為對象。
- children:將元素的虛擬節(jié)點(diǎn)子級(jí)存儲(chǔ)為數(shù)組。
使用虛擬節(jié)點(diǎn),我們可以對當(dāng)前的UI進(jìn)行建模,以及當(dāng)我們更新UI時(shí)希望它改變成什么。
假設(shè)我想將
//Hello World!
const div = document.createElement('div');
div.style = 'color: red';
div.textContent = 'Hello World!';
// Change from "Hello World!" to "Hello Universe!"
div.textContent = 'Hello Universe!';
但是使用VirtualDOM,我可以指定當(dāng)前UI的外觀(舊虛擬節(jié)點(diǎn))和我希望它的外觀(新虛擬節(jié)點(diǎn))。
const oldVNode = {
type: 'div',
props: {
style: 'color: red'
}
children: ['Hello World!']
};
const newVNode = {
type: 'div',
props: {
style: 'color: red'
}
children: ['Hello Universe!']
};
然而要讓Virtual DOM真正將更改應(yīng)用到UI,還需要計(jì)算舊虛擬節(jié)點(diǎn)和新虛擬節(jié)點(diǎn)之間的差異。
{
type: 'div',
props: {
style: 'color: red'
}
- children: ['Hello World!']
+ children: ['Hello Universe!']
};
當(dāng)我們知道了二者之間的差別,就可以通過Virtual DOM改變UI。
div.replaceChild(newChild, oldChild);
Virtual DOM只是進(jìn)行了必要的修改,并不是替換了整個(gè)UI。
構(gòu)建自己的Virtual DOM
在本文中,我們將模仿Million.js的 Virtual DOM API。我們的API將包含三個(gè)主要功能:m, createElement, and patch。
m (tag, props, children)
m 函數(shù)是創(chuàng)建虛擬節(jié)點(diǎn)的輔助函數(shù)。虛擬節(jié)點(diǎn)包含三個(gè)屬性:
- tag:將虛擬節(jié)點(diǎn)的名稱標(biāo)記為字符串;
- props:作為對象的節(jié)點(diǎn)的屬性/屬性;
- children:虛擬節(jié)點(diǎn)的子節(jié)點(diǎn)作為數(shù)組。
m幫助程序函數(shù)的示例實(shí)現(xiàn)如下:
const m = (tag, props = {}, children = []) => ({
tag,
props,
children,
});
這樣創(chuàng)建虛擬節(jié)點(diǎn)就簡單多了。
m('div', { style: 'color: red' }, ['Hello World!']);
#createElement(vnode)
該createElement函數(shù)將虛擬節(jié)點(diǎn)轉(zhuǎn)換為真實(shí)的DOM元素。這很重要,因?yàn)槲覀儗⒃趐atch函數(shù)中使用它。
實(shí)現(xiàn)如下:
- 如果虛擬節(jié)點(diǎn)是文本,則返回文本節(jié)點(diǎn);
- tag使用虛擬節(jié)點(diǎn)的屬性創(chuàng)建一個(gè)新的DOM節(jié)點(diǎn);
- 遍歷虛擬節(jié)點(diǎn)props 并將它們添加到DOM節(jié)點(diǎn)。
- 遍歷children,在每個(gè)子級(jí)上遞歸調(diào)用createElement并將其添加到DOM節(jié)點(diǎn)。
const createElement = (vnode) => {
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
const el = document.createElement(vnode.tag);
for (const prop in vnode.props) {
el[prop] = vnode.props[prop];
}
for (const child of vnode.children) {
el.appendChild(createElement(child));
}
return el;
};
這樣就可以輕松地將虛擬節(jié)點(diǎn)轉(zhuǎn)變成DOM節(jié)點(diǎn)。
//Hello World!
createElement(
m('div', { style: 'color: red' }, ['Hello World!'])
);
#patch(el, newVNode, oldVNode)
該patch函數(shù)采用現(xiàn)有的DOM節(jié)點(diǎn)、舊的虛擬節(jié)點(diǎn)和新的虛擬節(jié)點(diǎn)。
實(shí)現(xiàn)如下:
- 計(jì)算兩個(gè)虛擬節(jié)點(diǎn)之間的差異;
- 如果虛擬節(jié)點(diǎn)是字符串,則將DOM節(jié)點(diǎn)的文本內(nèi)容替換為新節(jié)點(diǎn);
- 如果虛擬節(jié)點(diǎn)是對象,且tag、props、 children不同,則更新節(jié)點(diǎn)。
const patch = (el, newVNode, oldVNode) => {
if (!newVNode && newVNode !== '') return el.remove();
if (
typeof oldVNode === 'string' ||
typeof newVNode === 'string'
) {
if (oldVNode !== newVNode) {
return el.replaceWith(createElement(newVNode));
}
} else {
if (oldVNode.tag !== newVNode.tag) {
return el.replaceWith(createElement(newVNode));
}
// patch props
for (const prop in {
...oldVNode.props,
...newVNode.props,
}) {
if (newVNode.props[prop] === undefined) {
delete el[prop];
} else if (
oldVNode.props[prop] === undefined ||
oldVNode.props[prop] !== newVNode.props[prop]
) {
el[prop] = newVNode.props[prop];
}
}
// patch children
for (let i = oldVNode.children.length - 1; i >= 0; --i) {
patch(
el.childNodes[i],
newVNode.children[i],
oldVNode.children[i]
);
}
for (
let i = oldVNode.children.length;
i < newVNode.children.length;
i++
) {
el.appendChild(createElement(newVNode.children[i]));
}
}
};
這樣就可以使用patch功能更新UI了。
const oldVNode = m('div', { style: 'color: red' }, [
'Hello World!',
]);
const newVNode = m('div', { style: 'color: red' }, [
'Hello Universe!',
]);
const el = createElement(oldVNode);
// Hello World!
patch(el, oldVNode, newVNode);
// Hello Universe!
Virtual DOM是純開銷
當(dāng)前,Virtual DOM實(shí)現(xiàn)在計(jì)算新舊虛擬節(jié)點(diǎn)之間的差異時(shí)會(huì)產(chǎn)生計(jì)算成本。
即使使用非常有效的差分算法 (如list-diff2),當(dāng)虛擬節(jié)點(diǎn)樹大于虛擬節(jié)點(diǎn)的兩位數(shù)時(shí),差異成本也會(huì)變得顯著。
樹區(qū)分算法是出了名的慢。時(shí)間復(fù)雜度可以從O(n)轉(zhuǎn)O(n ^ 3)取決于虛擬節(jié)點(diǎn)樹的復(fù)雜性。這與DOM操縱相去甚遠(yuǎn),后者是O(1)。
Virtual DOM的未來
編譯器是新框架”-- 湯姆·戴爾
Ember的創(chuàng)建者湯姆是最早倡導(dǎo)為JavaScript UI庫使用編譯器開源狂熱者之一。
現(xiàn)在,我們知道湯姆的賭注是正確的。JavaScript生態(tài)系統(tǒng)見證了Solid、Svelte等“已編譯”庫的興起,它們放棄了Virtual DOM。這些庫使用編譯器預(yù)渲染,并在使用時(shí)生成代碼來跳過不必要的渲染。
另一方面,Virtual DOM落后于這一趨勢。當(dāng)前的虛擬DOM庫本質(zhì)上與“按需” 編譯器不兼容。因此,Virtual DOM的渲染速度通常是比現(xiàn)代“No Virtual DOM” UI庫慢幾個(gè)數(shù)量級(jí)。
如果我們希望Virtual DOM在未來的渲染速度上具有競爭力,那就需要重新設(shè)計(jì)Virtual DOM以允許編譯器增強(qiáng)。
當(dāng)前名稱:VirtualDOM的歷史和未來
URL地址:http://fisionsoft.com.cn/article/cdoigcg.html


咨詢
建站咨詢
