寫前端最主要就是針對DOM來操作,DOM就是Document Object Model,它表示了HTML結構並提供API讓Javascript操作。所有在DOM裡的物件都可以稱為node,document本身就是document node,HTML元件就是element node,而我們常用的 document.getElementById 就是給Javascript使用的HTML DOM API。
Javascript藉由操作DOM來改變UI,但是操作DOM的代價是很高的,DOM的改變通常會帶來重排(Reflow)與重繪(Repaint),重排就是重新計算HTML elements的位置或寬高,重繪就是重新顯示HTML elements的顏色...等樣式。通常效能的瓶頸就是在操作DOM上面,Reflow完會再觸發Repaint,所以比單純Repaint要花更上多效能。所以~想要優化網頁效能,最重要的就是減少DOM的操作,或是使用 documentFragment 。
DocumentFragment也是DOM node,但它不是真的存在DOM tree中,它只存在記憶體中,可以說它是虛擬的DOM,我們可以利用它先把要建立的node append到DocumentFragment中,然後才一次更新到DOM tree上。
假設我們要建立100個div到畫面上
直接操作DOM的寫法 DEMO
var start = new Date().getTime(), end;
for(var i = 0; i < 100; i++) {
var ele = document.createElement('div');
end = new Date().getTime();
$(ele).text('I am Element#' + i + ', ' + ((end - start) / 1000) + "sec");
$(ele).css({
background: 'gray',
padding: 10,
margin: 10
});
$('body').append(ele);
}
end = new Date().getTime();
document.write((end - start) / 1000 + "sec");
使用documentFragment優化的寫法 DEMO
var start = new Date().getTime(),
end,
fragment = document.createDocumentFragment();
for(var i = 0; i < 100; i++) {
var ele = document.createElement('div');
end = new Date().getTime();
$(ele).text('I am Element#' + i + ', ' + ((end - start) / 1000) + "sec");
$(ele).css({
background: 'gray',
padding: 10,
margin: 10
});
fragment.appendChild(ele);
}
$('body').append(fragment);
end = new Date().getTime();
document.write((end - start) / 1000 + "sec");
直接從上面兩個DEMO的link中,可以看到執行的秒數,我使用Chrome大概會差兩倍秒數,也可以調整for迴圈次數差異會更顯著。DocumentFragment會一次更新有變動的DOM,只需要reflow一次,所以效能上當然也會更好。
React也是用類似documentFragment這樣的概念提供 Virtual DOM 給我們使用,它不是用真實的DOM做操作,而是抽象的DOM,我們不需要自己去處理原本DOM與要更新的DOM之間的差異,Virtual DOM會幫我們做diff運算,這也是使用React上很方便的地方!
如果對Virtual DOM很有興趣,可以再看這篇深度剖析:如何实现一个 Virtual DOM 算法,有深入講解Virtual DOM的做法。
雖然說Virtual DOM是抽象的DOM,但透過JSX的撰寫方式,其實感覺上並不是真的那麼抽象,因為我們可以直覺的了解到畫面的結構。我們可以透過ReactDOM.render(),把element append到真實的DOM上面。
前面提到的JSX是一個element
const ele = <div>Hello, world!</div>;
要把element顯示在畫面上,首先需要一個實際在HTML上的DOM(element node)
<div id="main"></div>
在這個div裡面的所有element都會由React DOM生成,我們可以用 ReactDOM.render() 來產生
const ele = <div>Hello, world!</div>;
ReactDOM.render(
ele,
document.getElementById('main')
);
當你把React element render出來後,它就會是你定義的樣子,除非重新執行render,或是之後會提到的update props or state,畫面才會再被reflow。
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
// render once
tick();
而從下面的程式碼,我們可以看出來,除非每次重新呼叫render才會再執行
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
// render every second
setInterval(tick, 1000);