前言
這是個老生常談的話題了,之所以還搬出來講講,原因之一是之前根本就沒在意,近期面臨的一些問題需要用到這兩個小技巧;原因之二,這兩個技巧帶來的優化不小;原因之三,順便複習一下閉包。
開發中你可能會遇到下面的情況:
- 監聽
Window
物件的resize
,scroll
事件 - 拖拽時監聽
mousemove
- 文字輸入時,對輸入字串進行處理,比如要把
markdwon
轉換成html
- 監聽檔案變化,重啟服務
第一種和第三種情況,事件短時間內被頻繁出發,如果在事件中有大量的計算,頻繁操作DOM,資源載入等重行為,可能會導致UI卡頓,嚴重點甚至讓瀏覽器掛掉。對於第四種情況,有的開發者儲存編輯好的檔案喜歡按多次Ctrl+S
,若是快速的重啟服務還能Hold住,但是要是重啟一個應用,就可能多次不必要的重啟。
針對上面這一系列的需求,於是有了debounce
和throttle
兩種解決辦法。
函式節流
函式按照一個週期執行,例如給window
繫結一個resize
事件之後,只要視窗改變大小改變就列印1,如果不採用函式節流,當我們將視窗調節的時候發現控制檯一直列印1
,但是使用了函式節流後我們會發現調節的過程中,每隔一段時間才列印1
。
一個函式節流的簡單實現:
/**
*
* @param func {Function} 實際要執行的函式
* @param wait {Number} 執行間隔,單位是毫秒(ms),預設100ms
*
* @return {Function} 返回一個“節流”函式
*/
function throttle(func, wait = 100) {
// 利用閉包儲存定時器和上次執行時間
let timer = null;
let previous; // 上次執行時間
return function() {
// 儲存函式呼叫時的上下文和引數,傳遞給 fn
const context = this;
const args = arguments;
const now = +new Date();
if (previous && now < previous + wait) { // 週期之中
clearTimeout(timer);
timer = setTimeout(function() {
previous = now;
func.apply(context, args);
}, wait);
} else {
previous = now;
func.apply(context, args);
}
};
}
複製程式碼
使用的方法也很簡單:
const btn = document.getElementById(`btn`);
function demo() {
console.log(`click`);
}
btn.addEventListener(`click`, throttle(demo, 1000));
複製程式碼
看看React中怎麼使用的,下面監聽視窗的resize
和輸入框的onChange
事件:
import React, { Component } from `react`;
import { throttle } from `../../utils/utils`;
export default class Demo extends Component {
constructor() {
super();
this.change = throttle((e) => {
console.log(e.target.value);
console.log(`throttle`);
}, 100);
}
componentDidMount() {
window.addEventListener(`resize`, throttle(this.onWindowResize, 60));
}
componentWillUnmount() {
window.removeEventListener(`resize`, throttle(this.onWindowResize, 60));
}
onWindowResize = () => {
console.log(`resize`);
}
handleChange = (e) => {
e.persist();
this.change(e);
}
render() {
return (
<input
onChange={this.handleChange}
/>
);
}
}
複製程式碼
函式去抖
當事件觸發之後,必須等待某一個時間(N)之後,回撥函式才會執行,假若再等待的時間內,事件又觸發了則重新再等待時間N,直到事件N內事件不被觸發,那麼最後一次觸發過了事件N後,執行函式。
還是視窗resize
,如果一直改變視窗大小,則不會列印1,只有停止改變視窗大小並等待一段時間後,才會列印1。
函式去抖簡單實現:
/**
* @param func {Function} 實際要執行的函式
* @param delay {Number} 延遲時間,單位是毫秒(ms)
* @return {Function}
*/
function debounce(fn, delay = 1000) {
let timer;
// 返回一個函式,這個函式會在一個時間區間結束後的 delay 毫秒時執行 func 函式
return function () {
// 儲存函式呼叫時的上下文和引數,傳遞給func
var context = this
var args = arguments
// 函式被呼叫,清除定時器
clearTimeout(timer)
// 當返回的函式被最後一次呼叫後(也就是使用者停止了某個連續的操作),
// 再過 delay 毫秒就執行 func
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
複製程式碼
應用場景,監聽檔案變化,重啟應用:
const debounce = require(`./debounce`);
watcher.on(`change`, debounce(() => {
const child = spawn(`npm`, [`run`, `dev:electron`], {
cwd,
detached: true,
stdio: `inherit`
})
child.unref();
electron.app.quit();
}, delay));
複製程式碼