以 chrome 為例子,一個 Web 頁面的展示,簡單來說可以認為經歷了以下下幾個步驟:
image
JavaScript:一般來說,我們會使用 JavaScript 來實現一些視覺變化的效果。比如做一個動畫或者往頁面里添加一些 DOM 元素等。
Style:計算樣式,這個過程是根據 CSS 選擇器,對每個 DOM 元素匹配對應的 CSS 樣式。這一步結束之后,就確定了每個 DOM 元素上該應用什么 CSS 樣式規則。
Layout:布局,上一步確定了每個 DOM 元素的樣式規則,這一步就是具體計算每個 DOM 元素最終在屏幕上顯示的大小和位置。web 頁面中元素的布局是相對的,因此一個元素的布局發生變化,會聯動地引發其他元素的布局發生變化。比如,<body> 元素的寬度的變化會影響其子元素的寬度,其子元素寬度的變化也會繼續對其孫子元素產生影響。因此對于瀏覽器來說,布局過程是經常發生的。
Paint:繪制,本質上就是填充像素的過程。包括繪制文字、顏色、圖像、邊框和陰影等,也就是一個 DOM 元素所有的可視效果。一般來說,這個繪制過程是在多個層上完成的。
Composite:渲染層合并,由上一步可知,對頁面中 DOM 元素的繪制是在多個層上進行的。在每個層上完成繪制過程之后,瀏覽器會將所有層按照合理的順序合并成一個圖層,然后顯示在屏幕上。對于有位置重疊的元素的頁面,這個過程尤其重要,因為一旦圖層的合并順序出錯,將會導致元素顯示異常。
防抖(Debouncing)和節流(Throttling)
- 防抖技術即是可以把多個順序地調用合并成一次,也就是在一定時間內,規定事件被觸發的次數。
一個簡化的例子:
// 簡單的防抖動函數
function debounce(func, wait, immediate) {
// 定時器變量
var timeout;
return function() {
// 每次觸發 scroll handler 時先清除定時器
clearTimeout(timeout);
// 指定 xx ms 后觸發真正想進行的操作 handler
timeout = setTimeout(func, wait);
};
};
// 實際想綁定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了防抖動
window.addEventListener('scroll',debounce(realFunc,500));
// 沒采用防抖動
window.addEventListener('scroll',realFunc);
- 封裝:
// 防抖動函數
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var myEfficientFn = debounce(function() {
// 滾動中的真正的操作
}, 250);
// 綁定監聽
window.addEventListener('resize', myEfficientFn);
- 有時候,我們希望即使頁面在不斷被滾動,但是滾動 handler 也可以以一定的頻率被觸發(譬如 250ms 觸發一次),這類場景,就要用到另一種技巧,稱為節流函數(throttling)。
- 節流函數,只允許一個函數在 X 毫秒內執行一次。
- 與防抖相比,節流函數最主要的不同在于它保證在 X 毫秒內至少執行一次我們希望觸發的事件 handler。
- 與防抖相比,節流函數多了一個 mustRun 屬性,代表 mustRun 毫秒內,必然會觸發一次 handler ,同樣是利用定時器,看看簡單的示例:
// 簡單的節流函數
function throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果達到了規定的觸發時間間隔,觸發 handler
if(curTime - startTime >= mustRun){
func.apply(context,args);
startTime = curTime;
// 沒達到觸發間隔,重新設定定時器
}else{
timeout = setTimeout(func, wait);
}
};
};
// 實際想綁定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了節流函數
window.addEventListener('scroll',throttle(realFunc,500,1000));