一丶基本概念

防抖(debounce):在函数需要频繁触发时,只有当有足够空闲的时间时,才执行一次。就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。

节流(throttle):预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。

区别:在发生持续触发事件时,防抖设置事件延迟并在空闲时间去触发事件,而节流则是隔一定的时间触发一次。

二丶函数防抖

函数防抖运用的实际场景有:实时搜索,拖拽,登录用户名密码格式验证等等。

实现函数防抖的关键就是对setTimeout()这个方法的运用。先以实时搜索为例分析一下。

首先我们要写一个监听函数用来监听搜索框的value的变化。

var oInp;// 假设在此取得输入框
oInp.oninput = ajax;
// 模拟ajax请求后台数据
function ajax() {
    console.log(this.value);// 搜索框value值
}

写完之后我们会发现每当我们输入一个单词,即每当搜索框内容发生变化时都会触发我们的监听函数来请求后台数据,如此频繁的请求肯定不是我们想要的,所以我们就需要稍加处理一下,使其不再一改变就触发,而是当我们输完之后再触发发送请求。针对这种需求我们可以使用防抖来实现。

var oInp;// 假设在此取得输入框
var timer = null; // 定义一个全局定时器
oInp.oninput = function(e) {
    clearTimeout(timer);
    timer = setTimeout(function(){
        ajax();
    }, 1000);
};
// 模拟ajax请求后台数据
function ajax() {
    console.log(this.value);// 搜索框value值
}

这个整体的实现思想就是,当搜索框内容发生改变时,就会触发一个定时器。但是当搜索框内容再次发生改变时,我们先清除上一个定时器,再重新创建一个定时器。这样,只有当我们结束输入,搜索框内容在一定时间内不再发生改变时才会发送请求。

但是上面的代码块还有两个问题,一个就是this的指向,setTimeout()形成了一个闭包,当执行的时候,ajax()方法中的this实际指向window,所以我们还需要进行以下优化,改变this指向。

var oInp;// 假设在此取得输入框
var timer = null; // 定义一个全局定时器
oInp.oninput = function(e) {
    var _this = this; 
    clearTimeout(timer);
    timer = setTimeout(function(){
        ajax().apply(_this); // 绑定this
    }, 1000);
};
// 模拟ajax请求后台数据
function ajax() {
    console.log(this.value);// 搜索框value值
}

第二个问题就是e -- 事件对象,在上面一系列的方法调用之中,e已经被丢了,变成了undefined,所以我们还需要进行以下优化,将事件对象重新找回来。

var oInp;// 假设在此取得输入框
var timer = null; // 定义一个全局定时器
oInp.oninput = function(e) {
    var _this = this,
        _arg = arguments; // e
    clearTimeout(timer);
    timer = setTimeout(function(){
        ajax().apply(_this, _arg); // 绑定this, 传入e
    }, 1000);
};
// 模拟ajax请求后台数据
function ajax(e) {
    console.log(this.value);// 搜索框value值
    console.log(e); // 事件对象
}

如上就基本实现了函数防抖。为了实现通用性,在这里将防抖封装成一个方法,方便之后重复使用。

function debounce(handle, delay) {
	var timer = null;
	return function() {
		var _this = this,
		    _arg = arguments;
		clearTimeout(timer);
		timer = setTimeout(function() {
			handle.apply(_this, _arg);
		}, delay);
	}
} // 其中 handle 为需要进行防抖操作的函数,delay 为延迟时间

三丶函数节流

函数节流运用的实际场景有:窗口调整,页面滚动,抢购疯狂点击等等。

在这里以疯狂点击为例进行分析。

首先写一个简单的页面,当点击按钮时,数字不断增大,模拟抢购按钮。

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<div id="show">0</div>
	<button id="btn">点我</button>
	<script>
		var oDiv = document.getElementById('show'),
			oBtn = document.getElementById('btn');
			oBtn.onclick = function() {
				oDiv.innerHTML = parseInt(oDiv.innerHTML) + 1;
			}
	</script>
</body>
</html>

我们肯定不希望用户去疯狂点击导致数字不断增加,甚至是使用恶意脚本去实现疯狂点击按钮

for(var i = 0; i < 1000; i ++) {
    oBtn.onclick();
}

所以就要引入一个新思想,那就是在一秒钟之内无论用户点多少次,都只算他点了一次,这就是节流的核心思想。

function throttle(handle, wait) {
	var lasttime = 0;
	return function(e) {
		var nowtime = new Date().getTime();
		if(nowtime - lasttime > wait) {
			handle.apply(this, arguments);
			lasttime = nowtime;
		}
	}
}
function buy() {
	oDiv.innerHTML = parseInt(oDiv.innerHTML) + 1;
}
oBtn.onclick = throttle(buy, 1000);

现在分析一下throttle方法。

参数中 handle 为需要进行节流的方法,wait为等待时间。

因为我们需要实现在一定的等待时间wait内不能执行buy()方法,所以首先需要两个时间戳,一个记录第一次点击的时间lasttime,一个记录当前的时间nowtime,只有当 nowtime 与 lasttime 的时间差大于wait时,才会再次触发buy(),同时改变lasttime为新时间戳。

放在throttle()中就是首先记录初始时间为0,当第一次点击时,获得现在时间为nowtime,时间差大于wait,执行buy(),然后本次点击的时间就成了一个新的时间点,下次点击就需要和这次点击的时间进行判断,所以设置当前时间为初始时间,然后下次点击时继续判断。

实战版本:

/**
 * 函数节流
 * @param method
 * @param delay
 * @returns {Function}
 */
let throttle = (method, delay) => {
    let timer = null;
    return () => {
        let context = this;
        let args = arguments;
        clearTimeout(timer);
        timer = setTimeout(() => {
            method.apply(context, args);
        }, delay);
    };
};

export default throttle;

四丶总结 

防抖和节流虽然实现起来不难,但在实际开发中还是很常用的,因为它们可以极大的优化网络请求性能,提高用户体验。