贴上将防抖和节流的详细文
- 防抖和节流的作用都是防止函数多次调用。区别在于:假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次;而节流的情况会每隔一定时间(参数wait)调用函数
防抖
- 原理:你尽管触发事件,但是我一定在事件触发 n 秒后才执行。如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n秒后才执行,总之,就是要等你触发完事件 n秒内不再触发事件,我才执行。
- 在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作,需要通过函数防抖动实现
- 防抖函数分为非立即执行版和立即执行版
- 思路:每次触发事件时都取消之前的延时调用方法
非立即执行版
function debounce(func,wait){
let timeout;
return function(){
let context = this;
let args = arguments;
if(timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context,args);
},wait);
}
}
- let context = this 目的在于改变this的指向。因为在闭包中,this指向全局window,正常情况下this值为
- let args - arguments是为了确定event对象
非立即执行版的意思是触发事件后函数不会立即执行,而是在n秒后执行,如果在n秒内又触发了事件,则会重新计算函数执行事件。
- 需要注意的是this和参数。为了保证this指向不变
立即执行版
var count = 1;
var container = document.getElementById('container');
function getUserAction(e) {
container.innerHTML = count++;
};
var setUseAction = debounce(getUserAction, 10000, true);
container.onmousemove = setUseAction;
function debounce(func,wait,immediate){
let timeout,result;
return function(){
let context = this;
let args = arguments;
if(timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow)
result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
}
}
- 这里的return result其实不是必要的,考虑到这里的func可能会有返回值,考虑的齐全一些,所以return 了result;
立即执行版的意思是触发事件后函数会立即执行,然后n秒内不触发事件才能继续执行函数的效果。
结合非立即执行版和立即执行版
function debounce(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(() => {
func.apply(context,args)
},wait);
}
}
}
节流
- 所谓节流,就是指连续触发事件但是在n秒中只执行一次函数。节流会稀释函数的执行频率
- 对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版
- 每次触发事件都判断当前是否有等待执行的延时函数
时间戳版
使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
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);
previoud = now;
}
}
}
定时器版
当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
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)
}
}
}
比较时间戳和定时器方法:
1.时间戳方法会立刻执行,定时器会在n秒后第一次执行
2.时间戳方法停止触发后没有办法再执行事件,定时器方法停止触发后依然会再执行一次事件。
结合定时器和时间戳
实现鼠标移入能立刻执行,停止触发的时候依然会再执行一次。
function throttle(func, wait) {
let timeout, context, args, result;
let previous = 0;
let later = function () {
previous = +new Date(); //获得之前的时间戳
timeout = null;
func.apply(context, args);
};
return function () {
let now = +new Date(); //获得当前时间戳
//remain表示下次触发func剩余的时间
let remain = wait - (now - previous);
context = this;
args = arguments;
//如果没有剩余的时间或者改变了系统时间
if (remain <= 0 && remain > wait) {
//如果timeout为true,即定时器已经在运行及时了
if (timeout) {
clearTimeout(timeout);
timeout = null; //这里让timeout=null主要目的并不是垃圾回收,主要是为了方便下次执行定时器
}
previous = now;
func.apply(context, args);
//如果没有执行定时器
} else if (!timeout) {
timeout = setTimeout(later, remain);
}
}
}
let count = 0;
function getUserAction(e) {
container.innerHTML = count++;
};
container.onmousemove = throttle(getUserAction, 1000);
+new Date()
- new Date()相当于ToNumber(new Date())
-
利用隐士类型转换将时间对象转换为时间戳
-
类似的有:
- +‘1’ //1 转数字
- 1 + ‘’ // ‘’ 转字符串
- !!1 //true 布尔值
-
分析 +new Date()过程
- 运算符new的优先级高于一元运算符+ 过程分解为
var time = new Date();
+time;- 相当于ToNumber(time)
- time是个日期对象,根据ToNumber的转换规则,所以相当于: ToNumver(ToPrimitive(time)
- 根据ToPrimtive的转换规则ToNumber(time.valueOf()),time.valueOf()就是 原始值 得到的是个时间戳,假设time.valueOf()=1503479124652
- 所以ToNumber(1503479124652)返回值是1503479124652这个数字。