在最近,小米9賣的特別火,在官方搶購的時候基本都是一點既空。這不禁讓我想到了,官網是怎樣控制顧客不停點擊發起請求而不導致官網崩潰的呢?這由此引出了前端性能優化中的----防抖和節流。在閑聊完后你就會發現有些時候在搶購商品的時候,你用鼠標在幾秒鍾不停的按了數十次,或許它僅僅是發送了你第一次點擊搶購的那個請求。所以說 搶購時間內的第一次點擊尤為關鍵!
下面來介紹一下什么是防抖!
防抖:任務頻繁觸發的情況下,只有任務觸發的間隔超過制定的時間間隔的時候,任務才會被執行。
下面引用一下知乎上的一個例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>防抖</title>
</head>
<body>
<button id="debounce">點我防抖!</button>
<script>
window.onload = function() {
// 1、獲取這個按鈕,並綁定事件
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
}
// 2、防抖功能函數,接受傳參
function debounce(fn) {
// 4、創建一個標記用來存放定時器的返回值
let timeout = null;
return function() {
// 5、每次當用戶點擊/輸入的時候,把前一個定時器清除
clearTimeout(timeout);
// 6、然后創建一個新的 setTimeout,
// 這樣就能保證點擊按鈕后的 interval 間隔內
// 如果用戶還點擊了的話,就不會執行 fn 函數
timeout = setTimeout(() => {
fn.call(this, arguments);
}, 1000);
};
}
// 3、需要進行防抖的事件處理
function sayDebounce() {
// ... 有些需要防抖的工作,在這里執行
console.log("防抖成功!");
}
</script>
</body>
</html>
這是知乎上的一個例子,創建一個定時器,如果在規定時間內重復觸發該事件,就會調用clearTimeout清除掉上一個定時器,重置定時器。也就是說,這件事本來就是需要等待的,並非立即執行的,如果用戶反復點擊,那只好重新等待了。所以,fn.call(this, arguments)
其實是將不確定變量替換到函數中了。在這之前已經聊過了arguments,可以看下我之前寫得 閑聊js中的apply、call和arguments
在上面的這個例子中是非立即執行版。當然,我把知乎的例子修改一下它,將它變成立即執行版。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>防抖</title> </head> <body> <button id="debounce">點我防抖!</button> <script> window.onload = function() { // 1、獲取這個按鈕,並綁定事件 var myDebounce = document.getElementById("debounce"); myDebounce.addEventListener("click", debounce(sayDebounce)); } // 2、防抖功能函數,接受傳參 function debounce(fn) { // 4、創建一個標記用來存放定時器的返回值 let timeout = null; //5.創建一個判斷是否可點擊值 let doit = true; return function() { // 5、當doit為真,既用戶重復點擊時,清除定時器 if(doit)clearTimeout(timeout); //6.當doit為false時,既用戶可點擊,再將doit設為true,防止用戶重復點擊 else{ fn(); doit = true; } //7.設置定時器,這樣就能保證點擊按鈕后的 interval 間隔內 // 如果用戶還點擊了的話,就不會執行 將doit設為false函數 timeout = setTimeout(() => { doit = false; }, 1000); }; } // 3、需要進行防抖的事件處理 function sayDebounce() { // ... 有些需要防抖的工作,在這里執行 console.log("防抖成功!"); } </script> </body> </html>
其原理和立即執行版本一樣,只是點擊執行的先后順序不同。
下面 我們來聊一下什么是節流吧!
節流:指定時間間隔內只會執行一次任務。
這有點像我們刷搶購一樣,當我們在某段時間間隔內觸發了多次事件,其實,它只執行一次請求!
下面 我們再來引用知乎的一個例子就會明白了!
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>節流</title> </head> <body> <button id="throttle">點我節流!</button> <script> window.onload = function() { // 1、獲取按鈕,綁定點擊事件 var myThrottle = document.getElementById("throttle"); myThrottle.addEventListener("click", throttle(sayThrottle)); } // 2、節流函數體 function throttle(fn) { // 4、通過閉包保存一個標記 let canRun = true; return function() { // 5、在函數開頭判斷標志是否為 true,不為 true 則中斷函數 if(!canRun) { return; } // 6、將 canRun 設置為 false,防止執行之前再被執行 canRun = false; // 7、定時器 setTimeout( () => { fn.call(this, arguments); // 8、執行完事件(比如調用完接口)之后,重新將這個標志設置為 true canRun = true; }, 1000); }; } // 3、需要節流的事件 function sayThrottle() { console.log("節流成功!"); } </script> </body> </html>
從這個例子可以看出,節流可以防止在某時間間隔內重復發送請求!其和防抖有點相似,但其有本質的區別,雖然都是防止重復觸發事件!
防抖是需要等待多久時間才能再觸發一次事件!
節流是多久時間內只能觸發一次事件!
重繪與回流
在介紹重繪和回流之前,最好先了解一下瀏覽器是如何解析解析URL的,或者看一下《瀏覽器渲染頁面過程剖析》
好!現在我們進入正題 !什么是重繪和回流!
重繪(repaint):當元素樣式的改變不影響布局時,瀏覽器將使用重繪對元素進行更新,此時由於只需要 UI 層面的重新像素繪制,因此損耗較少。
回流(reflow):又叫重排(layout)。當元素的尺寸、結構或者觸發某些屬性時,瀏覽器會重新渲染頁面,稱為回流。此時,瀏覽器需要重新經過計算,計算后還需要重新頁面布局,因此是較重的操作。
或許這概念比較抽象,講起來很難理解!簡單點說,就比如我們頁面中的某些顏色會發生動態改變,而木有影響到尺寸,布局、位置、結構這些改變的,就叫做重繪,而例如動態添加結點、改變尺寸、位置這些的,就叫做回流!
回流的損耗是比較大的!所以盡量不要產生太多的回流!就比如,樣式的動態修改不要多步而盡量應一步到位!
為了避免大量的重繪和回流!
- 避免頻繁操作樣式,可匯總后統一一次修改
- 盡量使用 class 進行樣式修改,而不是直接操作樣式
- 減少 DOM 的操作,可使用字符串一次性插入
回流必定會觸發重繪,重繪不一定會觸發回流。重繪的開銷較小,回流的代價較高。