函數的防抖和節流

在前端開發過程當中,常常會須要綁定一些持續觸發的事件,如 resizescrollmousemove等等,可是處於性能方向的考慮,並不但願在事件持續觸發的過程當中那麼頻繁地去執行函數。對於這種狀況,通常來說,防抖節流是比較好的解決方案。javascript

事件頻繁觸發狀況
<!DOCTYPE html>
<html>
<head>
	<title>防抖和節流</title>
</head>
<body>
	<div id="content" 
	     style="height:150px; line-height:150px; text-align:center; color: #fff; background-color:#ccc; font-size:80px;">
	</div>
	<script type="text/javascript">
		let num = 1;
        let content = document.getElementById('content');
        function add () {
         	content.innerHTML = num++;
        }
        content.onmousemove = add;
	</script>
</body>
</html>
複製代碼

在上述代碼中,div元素綁定了mousemove事件,當鼠標在 div(灰色)區域中移動的時候會持續地去觸發該事件致使頻繁執行函數,效果以下:html

從執行的結果上,能夠看到,在沒有經過其它操做的狀況下,函數被頻繁地執行致使頁面上數據變化特別快,對於這種狀況,能夠採用防抖和節流的方式,實現優化。前端

防抖
防抖,就是指觸發事件後在 n 秒內函數只能執行一次,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間
複製代碼
  • 非當即執行版
非當即執行版: 觸發事件後函數不會當即執行,而是在 n 秒後執行,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間
複製代碼

代碼以下:java

<!DOCTYPE html>
<html>
<head>
	<title>防抖和節流</title>
</head>
<body>
	<div id="content" 
	     style="height:150px; line-height:150px; text-align:center; color: #fff; background-color:#ccc; font-size:80px;">
	</div>
	<script type="text/javascript">
		let num = 1;
        let content = document.getElementById('content');
        function add () {
         	content.innerHTML = num++;
        }

        //防抖事件 - 非當即執行版
        function fdEvent (func,wait) {
        	let timeout;
        	return function () {
        		let context = this;
        		let args = arguments

        		if(timeout) clearTimeout(timeout);

                timeout = setTimeout(function() {

                  func.apply(context, args)

                }, wait);
        	}
        }
        content.onmousemove = fdEvent(add,1000);

	</script>
</body>
</html>
複製代碼

效果以下:bash

從執行的結果上,能夠看到,在觸發事件後函數 1 秒後才執行,若是在觸發事件後的 1 秒內又觸發了事件,則會從新計算函數的執行時間。app

  • 當即執行版
當即執行版: 觸發事件後函數會當即執行,而後 n 秒內不觸發事件才能繼續執行函數的效果
複製代碼

代碼以下:函數

<!DOCTYPE html>
<html>
<head>
	<title>防抖和節流</title>
</head>
<body>
	<div id="content" 
	     style="height:150px; line-height:150px; text-align:center; color: #fff; background-color:#ccc; font-size:80px;">
	</div>
	<script type="text/javascript">
		let num = 1;
        let content = document.getElementById('content');
        function add () {
         	content.innerHTML = num++;
        }

        //防抖事件 - 當即執行版
        function fdEvent (func,wait) {
        	let timeout;
        	return function () {
        		let context = this;
        		let args = arguments;

        		if(timeout) clearTimeout(timeout);

                let callNow = !timeout;
                timeout = setTimeout(function() {
                    timeout = null;
                }, wait);

                if (callNow) func.apply(context, args)
        	}
        }
        content.onmousemove = fdEvent(add,1000);

	</script>
</body>
</html>
複製代碼

效果以下:性能

在開發過程當中,根據不一樣的需求場景來決定咱們須要哪個版本的防抖函數,通常來說,基本的防抖函數都可以符合大部分的需求。可是也能夠實現非當即執行版和當即執行版相互結合的方式,來實現防抖函數。代碼以下:優化

/**
 * @desc 函數防抖
 * @param func 函數
 * @param wait 延遲執行毫秒數
 * @param immediate true 表當即執行,false 表非當即執行
 */
function fdEvent(func,wait,immediate) {
    let timeout;

    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(() => {
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}
複製代碼
節流
節流,就是指連續觸發事件可是在 n 秒中只執行一次函數。節流會稀釋函數的執行頻率。
複製代碼
  • 時間戳版
<!DOCTYPE html>
<html>
<head>
	<title>防抖和節流</title>
</head>
<body>
	<div id="content" 
	     style="height:150px; line-height:150px; text-align:center; color: #fff; background-color:#ccc; font-size:80px;">
	</div>
	<script type="text/javascript">
		let num = 1;
        let content = document.getElementById('content');
        function add () {
         	content.innerHTML = num++;
        }

        //節流事件 - 時間戳版
        function throttle (func,wait) {
        	let previous = 0;
        	return function () {
        		let now = Date.now();
                let context = this;
                let args = arguments;
                if(now - previous > wait){
                    func.apply(context, args);
                    previous = now;
                }
        	}
        }
        content.onmousemove = throttle(add,1000);

	</script>
</body>
</html>
複製代碼

效果以下:ui

從執行的結果上,能夠看到,在持續觸發事件的過程當中,函數會當即執行,而且每 1s 執行一次

  • 定時器版
<!DOCTYPE html>
<html>
<head>
	<title>防抖和節流</title>
</head>
<body>
	<div id="content" 
	     style="height:150px; line-height:150px; text-align:center; color: #fff; background-color:#ccc; font-size:80px;">
	</div>
	<script type="text/javascript">
		let num = 1;
        let content = document.getElementById('content');
        function add () {
         	content.innerHTML = num++;
        }

        //節流事件 - 定時器版
        function throttle (func,wait) {
            let timeout;
        	return function () {
                let context = this;
                let args = arguments;
                if (!timeout){
                    timeout = setTimeout(() =>{
                        timeout = null;
                        func.apply(context, args)
                    },wait)
                }
        	}
        }
        content.onmousemove = throttle(add,1000);

	</script>
</body>
</html>
複製代碼

效果以下:

從執行的結果上,能夠看到,在持續觸發事件的過程當中,函數不會當即執行,而且每 1s 執行一次,在中止觸發事件後,函數還會再執行一次 。

從兩次執行的效果上,能夠看到,其實時間戳版和定時器版的節流函數的區別就是:

時間戳版的函數觸發是在時間段內開始的時候,而定時器版的函數觸發是在時間段內結束的時候
複製代碼

一樣地,兩種方式,也是結合使用:

/**
 * @desc 函數節流
 * @param func 函數
 * @param wait 延遲執行毫秒數
 * @param type 1 表時間戳版,2 表定時器版
 */
function throttle(func, wait ,type) {
    if(type===1){
        let previous = 0;
    }else if(type===2){
        let timeout;
    }
    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }
    }
}
複製代碼
相關文章
相關標籤/搜索