作用域、闭包及其应用
闭包是有权访问另一个作用域中的变量的函数。
如果你找部门A办事,部门A说你得先去B部门盖章,部门B又说你得去部门C盖个章……这叫踢皮球。而闭包与之相反,它是负责到底,部门A的人会自动去和其他部门进行协调。
在JS中,闭包与作用域是分不开的,只要理解了作用域即可理解闭包。
一个简单闭包
在某些情况下,我们可能需要访问另一个作用域中的变量,使用函数即可创建一个作用域:
function Life(){
let count = 0;
return function(num){
console.log(`Before add: ${count}`);
count += num;
console.log(`After add: ${count}`);
}
}
let lifeAdd = Life();
lifeAdd(2); //Before add: 0 After add: 2
这里return出来的函数其实就是闭包给外界的一个接口,如果不这样,给外层作用域中的对象设置一个方法也可以,比如window:
function Life(){
let count = 0;
window.lifeAdd = function(num){
console.log(`Before add: ${count}`);
count += num;
console.log(`After add: ${count}`);
}
}
Life();
lifeAdd(2); //Before add: 0 After add: 2
虽然Life函数已经执行完毕,但由于闭包的存在,Life函数的内部变量一直没有被销毁。
闭包:函数防抖(debounce)与函数节流(throttle)
在实际应用中,控制一段代码的执行时机是很常见的。
有两个很常见的场景类型:
防抖(debounce)
在高频率触发的事件中,两次触发的时间间隔超过一定限度时才执行处理函数,否则重新计时。
就像电梯一样,每进去一个人,电梯就重新计时,几秒钟之后再无人进入时才会关门。
function debounce(fn, interval = 300) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(fn, interval);
};
}
//每次触发事件时,都会设置新的定时器,清除已有定时器
//而如果事件触发间隔超过了300毫秒,已有的定时器就会执行它的回调函数
window.onmousemove = debounce(function(){console.log('---')}, 300)
上面的debounce函数没有向回调函数传递参数,下面是带参数版:
function debounce(fn, interval = 300) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(
()=fn((new Date()).toLocaleString()),
interval
);
};
}
//假设回调函数每次执行都需要打印一个字符串
window.onmousemove = debounce(function (str){
console.log(str)
}, 300)
节流
在高频率触发的事件中,不论触发频率变得多高,都是每隔一段时间执行一次处理函数。
const throttle = (func, wait) = {
let timer = null;
return () = {
if (timer) {
return;
}
timer = setTimeout(() = {
func((new Date()).toLocaleString());
timer = null;
}, wait);
};
};
window.onmousemove = throttle(function (time){
console.log(time)
}, 1000)
防抖与节流的最大区别就是,防抖是只有在事件触发慢下来的时候才执行,而节流是以恒定的频率执行。