1、为什么使用闭包
引入:如果需要在页面上显示一个浏览时间,从打开页面的瞬间开始计时,每过一秒钟加一。(实质为累加器)
非闭包写法:
定义了一个全局变量,每次累加都是更新这个全局变量,but编程语言有一条不成文的铁律,就是尽可能少定义全局变量
为什么呢???
1、全局变量难以控制,可以在任何地方进行读写,意味着可能会被不相干的程序改写。
2、回收机制会每隔一段时间进行一次回收操作,释放不需要被占用的内存。一般局部变量(定义在函数中的变量),在函数调用完成后与之对应的执行环境会被推出执行栈。而全局变量因为随时可以被任何程序在任何地方读写,所以回收机制很难统计何时需要释放全局变量所占用的内存,也就导致全局变量一般是在全局执行环境被销毁时才会释放,占用内存的生命周期长。
// 秒数
let second = 0;
// 累加器
function counter() {
second += 1;
return second;
}
const recordSecond = setInterval(function () {
// 到达10秒后停止
if (second === 10) {
clearInterval(recordSecond);
console.log('计时结束!');
return;
}
// 调用累加器,输出当前秒数
console.log(`${counter()}秒`);
}, 1000);
闭包写法:
// 累加器
function counter() {
// 秒数
let second = 0;
function doCounter() {
// 到达10秒后停止
if (second === 10) {
clearInterval(recordSecond);
console.log('计时结束!');
return;
}
second += 1;
console.log(`${second}秒`);
}
return doCounter;
}
// 得到累加器
const doCounterFn = counter();
const recordSecond = setInterval(function () {
// 调用累加器
doCounterFn();
}, 1000);
分析:
当我们将函数调用的返回值赋值给一个变量时,如果调用的这个函数返回的是一个函数,那么变量得到的则是对这个返回函数的引用(返回函数的内存地址),当一个函数存在被引用的关系,垃圾回收机制则不会回收它所占的内存,也就是保留该函数的整个执行上下文占用的内存(变量对象,作用域链等)。而因为返回的这个函数同时又是某个函数的内部函数,产生了作用域嵌套,形成了闭包,返回函数的作用域链上包括了外部函数的作用域,也就是返回函数中可以访问外部函数中定义的变量。
所以,当我们通过 doCounterFn 间接调用 doCounter 时,虽然 doCounterFn 的作用域链上并不存在变量 second,但 doCounter 被执行时依旧能访问它的作用域链上的变量,也就是它声明时所在的作用域内的任何变量,这就是作用域延长的典型例子。
2、总结
闭包的使用场景:
- 创建私有变量
- 延长变量的生命周期
(当我们需要重复使用一个对象,但又想保护这个对象不被其他代码污染。)
闭包的作用:
使得一个外部函数有权访问一个内部函数作用域。
闭包的形成条件:
需要访问作用域
函数嵌套(物理条件)
被嵌套函数在另一个外部作用域中被调用
闭包的缺点:
比起普通函数闭包对内存的占用更多,闭包在处理速度和内存消耗方面对脚本性能具有负面影响。因此使用完毕手动赋空标志回收dodoCounterFn=null
闭包的应用:
1、防抖
function debounce(fn, wait) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
2、节流
function throttle(fn, wait) {
let timer = null
return function(...args) {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, wait)
}
}
可结合博文:防抖为什么使用闭包?