原文地址:Virtual DOM is pure overheadjavascript
做者:Rich Harris(Svelte做者)java
譯者:kyrieliu算法
在過去的幾年中,若是你是任何一款 JavaScript 框架的使用者,你都會聽過這麼一句話:Virtual DOM 賊快,這種說法一般是在暗指 virtual DOM 比真實的 DOM 更快。在這句話的影響下,社區的開發者們會問我各式各樣的問題,好比:在不使用 Virtual DOM 的前提下,Svelte 怎麼能這麼快呢?編程
有必要好好聊聊這個話題了。app
在大多數框架裏,開發者經過構建 render 函數來構建整個 app,就好比這個簡單的 React 組件:框架
function HelloMessage(props) {
return (
<div className="greeting"> Hello { props.name } </div>
);
}
複製代碼
若是不用 jsx,也能夠換一種形式表達:dom
function HelloMessage() {
return React.createElement(
'div',
{ className: 'greeting' },
'Hello',
props.name
);
}
複製代碼
上面兩種寫法的結果都是同樣的——生成了表示頁面結構的對象。這個對象就是所謂的 Virtual DOM。函數
當 app 的狀態更新時(好比那個叫 name 的屬性改變了),此時就會生成一個新的 Virtual DOM。此時框架的做用就顯現出來了:對比新老 Virtual DOM 的差別,將最少的必要變動應用在真實的 DOM 上。性能
關於 Virtual DOM 性能的誤解,要追溯到 React 正式發佈的那一天。2013年,在一場名爲 Rethinking Best Practices 的演講中,前 React 核心團隊成員 Pete Hunt 說到:優化
This is actually extremely fast, primarily because most DOM operations tend to be slow. There's been a lot of performance work on the DOM, but most DOM operations tend to drop frames.
等等!Virtual DOM 並非能夠替代 DOM 操做的東西,本質上是做爲後者的補充而存在的。
若是要說 Virtual DOM 更快,那麼必定是將使用了 Virtual DOM 的框架與其餘性能略差的框架做對比,要麼就是作了一些尚未人去作的事情——重寫 DOM 渲染函數:
onEveryStateChange(() => {
document.body.innerHTML = renderMyApp();
});
複製代碼
Pete 不久後澄清:
React is not magic. Just like you can drop into assembler with C and beat the C compiler, you can drop into raw DOM operations and DOM API calls and beat React if you wanted to. However, using C or Java or JavaScript is an order of magnitude performance improvement because you don't have to worry...about the specifics of the platform. With React you can build applications without even thinking about performance and the default state is fast.
但,好像也沒說到點子上...
並不慢。事實一般是:在規避了一些特定的坑後,Virtual DOM 快的飛起。
React 最初的承諾是:開發者能夠在每次狀態改變時從新渲染整個 app 而不用擔憂性能問題。實際上,並不是如此。若是正如 React 最初聲明的那樣,那麼就不會出現諸如 shouldComponentUpdate 這種優化手段了。
就算有了 shouldComponentUpdate,一次性更新整個 app 的 Virtual DOM 是一件工做量很大的事情。就在不久前,React 團隊推出了一種叫作 React Fiber 的東西,其做用是將更新任務分割成更小的任務塊,這意味着在 React 中更新 DOM 這件事情不會長時間阻塞主線程。儘管這樣,也不會減小總的工做量以及花在更新 DOM 上的耗時。
顯而易見的是,天下沒有免費的 diff。在將差別應用於真實的 DOM 以前,須要對比新老 Virtual DOM 的差別(下文簡稱「快照」),這就是咱們所說的 diff。就用剛纔 HelloMessage 組件的例子,假如那個叫 name 的屬性的值由 'world' 變成了 'everybody'。
只有第三步具備真正的價值。至此咱們發現,在絕大多數的更新「案例」中,app 的基本組織結構是不會發生改變的。若是咱們能直接跳到第三步,效率就會大大提高。
if (changed.name) {
text.data = name;
}
複製代碼
如上是 Svelte 在大部分狀況下生成的更新代碼。和傳統 UI 框架不一樣的是,Svelte 會在構建時會捕獲到你的 app 將如何改變,而不是在運行時。
React 及其餘用到 Virtual DOM 的框架所使用的 diff 算法是很快的。能夠說,大部分的開銷都是源於組件自己的。好比,你最好不要這麼寫...
function TerribleComponent(props) {
const value = expensivelyCalculateValue(props.foo);
return (
<p>the value is { value }</p>
);
}
複製代碼
這麼寫很糟糕,由於你的一不當心,儘管改變的不是 props.foo,value 這個值仍是會在每次更新的時候都重複計算。可是,以彷佛更有益的方式進行沒必要要的計算和分配是很是廣泛的:
function MoreRealisticComponent(props) {
const [selected, setSelected] = useState(null);
return (
<div> <p>Selected {selected ? selected.name : 'nothing'}</p> <ul> {props.items.map(item => <li> <button onClick={() => setSelected(item)}> {item.name} </button> </li> )} </ul> </div>
);
}
複製代碼
咱們生成了一組虛擬的 li 元素,每一個 li 都有它本身行內的點擊事件處理函數,在屬性改變時(儘管不是 props.item 改變),列表都會被從新生成一遍。若是你不是對性能有必定的追求,你可能不會去優化它。這件事情其實近乎於沒有意思,由於絕大多數狀況下這一切的運做都是很快的。但,你知道怎麼樣才能更快嗎?不作這件事。
這種默認去作一些沒必要要工做的策略,會一步一步拖垮你的 app,當開發者意識到須要進行優化時,經常會發現已經沒有任何明顯的瓶頸須要去解決的了。
Svelte 的設計思路就是旨在避免這種狀況的出現。
須要明確的是,Virtual DOM 並非一個功能,而是一種策略,而這個策略的最終目的是實現一種聲明式的、狀態驅動的 UI 開發理念。Virtual DOM 是有價值的,由於幫助開發者無需考慮狀態的變化就能構建一個 app,並提供了很是可觀的性能。而這些都意味着更少的 bug、更多能夠專一於業務的時間...
但事實證實,咱們無需使用 Virtual DOM 就能夠實現相似的編程模型,這也正是 Svelte 的用武之地。