1.防抖(debounce )
防抖的原理就是:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,真是任性呐!
第一版
var count = 1;
var container = document.querySelector('.container');
function getUserAction() {
console.log(this);//指向Window对象
container.innerHTML = count++;
}
function debounce (){
var timeout
return function(){
if (timeout) clearTimeout(timeout);
timeout = setTimeout(func, wait);
}
}
container.onmousemove = debounce(getUserAction, 1000);
我们可以看到getUserAction函数的this指向的是window对象,如果我们想指向<div class="container" ></div>我们该如何做呢?
第二版
var count = 1;
var container = document.querySelector('.container');
function getUserAction() {
console.log(this); //指向 <div class="container" ></div>
container.innerHTML = count++;
}
function debounce(func, wait) {
var timeout, context;
return function () {
context = this;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this), wait);
};
}
container.onmousemove = debounce(getUserAction, 1000);
我们利用apply方法改变了this的指向,从而getUserAction方法可以正确指向调用的的对象,那么问题又来了,我们在使用js操作dom元素的时候,函数会给我传一个事件对象,在我们第二版的函数中,如果直接输出参数,会看到输出undefined,想输出时间对象,我们应该如何做呢?
第三版
var count = 1;
var container = document.querySelector('.container');
function getUserAction(e) {
console.log(this); //指向 <div class="container" ></div>
console.log(e); //输出事件对象
container.innerHTML = count++;
}
function debounce(func, wait) {
var timeout, context, args;
return function () {
context = this;
args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, arguments), wait);
};
}
container.onmousemove = debounce(getUserAction, 1000);
这个时候,代码已经很是完善了,但是为了让这个函数更加完善,我们接下来思考一个新的需求。
这个需求就是:
我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
想想这个需求也是很有道理的嘛,那我们加个 immediate 参数判断是否是立刻执行。
第四版(立即执行)
var count = 1;
var container = document.querySelector('.container');
function getUserAction(e) {
console.log(this); //指向 <div class="container" ></div>
console.log(e); //输出事件对象
container.innerHTML = count++;
}
function debounce(func, wait, immediate) {
var timeout, context, args;
return function () {
context = this;
args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callnow = !timeout;
console.log(callnow);
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callnow) {
func.apply(this, args);
}
} else {
timeout = setTimeout(() => func.apply(this, arguments), wait);
}
};
}
container.onmousemove = debounce(getUserAction, 1000, true);
image.png
从图中可以看出立即执行和非立即执行函数的原理区别在于:
立即执行函数:timeout为undefined,所以 callnow第一次为true,当用户一直移动鼠标的时候,callnow都为false,当用户停止移动鼠标的时候,以下代码执行,将timeout变为null,所以当用户再次移动鼠标的时候,
timeout变为null,callnow又会变为true,
timeout = setTimeout(() => {
timeout = null;
}, wait);
2.节流
节流的原理很简单:
如果你持续触发事件,每隔一段时间,只执行一次事件。
根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。
我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。
关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。
时间戳
function throttle(func, wait) {
var previous = 0;
return function () {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
func.apply(this, arguments);
previous = now;
}
};
}
定时器
function throttle(func, wait) {
var timeout;
return function () {
var context = this;
if (!timeout) {
timeout = setTimeout(() => {
func.apply(context);
timeout = null;
}, wait);
}
};
}
网友评论