什么是闭包?
闭包就是有权访问另一个函数作用域中变量的函数。
简单理解就是:闭包是用来连接函数外部和内部的桥梁。
如何从函数外部来访问另一个函数内部的变量呢?
举个例子
// 当函数外部想要访问另一函数内部的变量时,需要使用return
function outer() {
let n = 10;
return function () {
console.log(10);
};
}
const fn = outer();
fn();
闭包的优缺点
优点:
- 私有化数据,在私有化的基础上可以保持数据
缺点:
- 使用不恰当,可能会导致内存泄漏,在不需要使用的时候,即及时将变量置为null
闭包的应用(手写)
- 防抖(保存数据状态和私有化变量和函数)
const debounce = function (func, delay) {
let timer;
// 注意点1:这里就不能使用箭头函数,因为箭头函数中没有this
return function () {
let agrs = arguments;
if (timer) {
clearTimeout(timer);
}
// 注意点2:这里就需要写箭头函数,因为要保证func中的this是外城function中的this
timer = setTimeout(() => {
// 注意点3:将外部的this传递到func中的this中,使用apply
// 注意点4:将外部的参数头传给func,使用arguments
func.apply(this, agrs);
}, delay);
};
};
// 假设业务中需要执行的函数名称为doLayout(), 想使用上面的防抖函数
const do_layout = debounce(doLayout(), 1000);
window.onresize = do_layout;
/*
要保证do_layout中的this和debounce函数中func的this指向相同
*/
- 实现数据缓存
假设我们在业务中处理一个很耗时的函数对象,每次调用都会花费很长的时间,那我们我们可以将计算出来的值保存起来,当调用这个函数的时候,先从缓存中查找,如果找不到,就计算,并更新缓存的值,如果找到了,直接返回从缓存中查找出来的值即可。这就可以使用闭包,因为闭包它是不会释放外部的引用,使得函数内部的值可以保留。
function configCache() {
// 设置一个内部的对象,用来存储缓存数据,这个属性是私有的
let cache = {};
return {
setCache(k, v) {
cache[k] = v;
},
getCache(k) {
return cache[k];
},
};
}
let cacheFunc = configCache();
cacheFunc.setCache('name', '张三');
console.log(cacheFunc.getCache('name')); // 张三
// configCache().setCache('name', '张三');
// console.log(configCache().getCache('name')); // undefined
/*
这里为什么是undefined呢?因为configCache()执行两次,会产生两种不同的执行环境
*/
-
用于实现柯里化和函数式变编程
什么是函数柯里化?
函数柯里化:指的是一个函数,它接收函数作为参数,运行后能够返回一个新的函数,并且这个新的函数能够处理外面函数的剩余参数。 -
封装类和模块
类似与上面的实现缓存 -
用于解决循环中异步问题
这个问题是我以前没怎么注意到的问题
首先我写了下面一个代码
for (let i = 0; i < 4; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 依次输出 0 1 2 3
直到后来我看见了这个
for (var i = 0; i < 4; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 输出结果 4 4 4 4
乍眼一看,没啥区别啊,但是i使用的声明变量不同
说一下这个问题吧。
// 使用var定义循环变量i
for (var i = 0; i < 4; i++) {
setTimeout(function () {
console.log('i=', i);
}, 1000);
}
console.log(i);
/*
输出结果 4 i= 4 i= 4 i= 4 i= 4
由于setTimeout是异步,还没有执行完毕,for循环已经执行完了
感受下执行过程吧:
这段代码 for循环里面嵌套了setTimeout,外层有个console.log
js代码的执行顺序是同步 > 异步 > 回调
那么 先执行for循环,发现里面有个setTimeout,将其放到宏任务队列中,
再执行外部的console.log,输出4
这时候执行栈中的同步任务都执行完毕了,将拿取宏任务队列中的任务回调函数放到
执行栈中去执行,这时候i已经全是4了,所以输出4遍i=4
*/
那么怎么解决这个问题哦呢
- 方法1:使用let
/*
在es6中,let声明个变量可以创建一个自己的块级作用域
在这里这个块级作用域也就是这个循环体
那么这里let本质上就形成了一个闭包
*/
垃圾回收机制
总结
当被问到什么是闭包、谈谈你对闭包的理解,这样回答。
首先闭包的指有权访问另一个函数作用域中变量的函数。闭包的原理它就是,在内存中创建一个包含函数和变量的环境,当函数返回后,这个环境还仍然存在于内存中,因此他可以被外部访问。
后面就是涉及到优缺点、应用场景、垃圾回收机制等等。问到就展开回答。