闭包
是我们每一个前端打工人绕不开的技能点,它经常出现在:同事之间吹水,面试官刁难时等一系列‘高端局’
中。时至今日,要是问你闭包到底是做什么的,你请答出个一二三吗?
一、有趣的作用域链
在开始聊闭包之前呢,先和大家来聊一聊
JavaScript
中作用域的概念,在ES5
中作用域有两种:全局作用域
和函数级作用域
,我们知道,当我们去访问一个变量
的时候,JavaScript会从当前作用域一级一级的往上‘冒泡’
,直到我们的window(全局)
,由此我们也打趣的叫作用域链
,我们通过案例更好的去感受一下:
var a = 1;
function foo () {
var a = 2;
function poo () {
console.log(a);
}
poo();
}
function zoo () {
console.log(a);
};
foo(); // 2
zoo(); // 1
console.log(a); // 1
从案例中我们可以看到,每一层函数
是一个单独作用域
,函数内部
可以访问函数外部
的变量,如果找不到就会再往上找,直到全局作用域(window)
。
二、堆和栈傻傻分不清
2.1 内存释放
我们再来聊一聊JavaScript
中的堆内存
和栈内存
,我们都知道引用数据类型
的值存储在堆内存中(比如我们的函数)
,一般函数执行完成内存就会被释放掉,下面举例说明一下内存释放
。
-
全局下的变量只有当页面关闭的时候内存才会被释放。
var obj = { name: 'iyongbao'; } console.log(obj.name); // iyongbao
-
如果是函数,执行完成会立即被释放掉
function foo () { var a = 2; console.log(a); } var f = foo(); // 2 console.log(f); // undefined
2.2 内存释放(特殊情况)
正常情况下,函数执行完成会立即被释放,但是也有特殊情况,那就是如果函数执行完后,函数中的私有变量被作用域外的变量引用的时,栈内存就不会去释放栈内存中的基本值。
function foo () {
var a = 2;
function zoo () {
a++;
console.log(a);
}
zoo();
return zoo;
}
var f = foo(); // 3
f(); // 4
f(); // 5
由此我们可以看出,foo
函数执行完
返回给f
变量一个函数,a变量
并没有
被释放。
三、走进闭包
3.1 闭包到底是什么
其实
2.2
就是一个闭包
,由此我们可以总结一下闭包的概念:闭包是指能够访问一个函数内部私有变量
的函数。这里注意闭包是一个函数。
3.2 闭包是如何形成的
当函数存在外部作用域的值的引用时就会产生闭包。
var a = 3;
function foo (c) {
var b = 5;
function zoo () {
console.log(a + b + c);
}
zoo();
}
foo();
3.3 闭包的作用
闭包
可以使我们的变量私有化
,內存不会被销毁,并且使我们的变量很好的与外部作用域隔离,防止变量的全局污染
,如果你用过JQuery可能会深有体会。
3.4 闭包的实际应用
3.4.1 块级作用域(立即执行函数)
这个例子很经典,说到会计作用域就会想到var
和let
,在ES6之前,我们做循环会遇到下面这种情况:
var fooArr = [];
for (var i = 0; i < 5; i ++) {
fooArr[i] = function () {
console.log(i);
}
}
fooArr.forEach(fn => {
fn();
})
结果我们发现打印了五次5
因此我们就可以借助闭包来解决这个问题。
var fooArr = [];
for (var i = 0; i < 5; i ++) {
fooArr[i] = (function (i) {
return function () {
console.log(i);
}
})(i)
}
fooArr.forEach(fn => {
fn();
})
// 0 1 2 3 4
此时得到了我们想要的结果啦!
3.4.2 变量私有
形成一个闭包后,我们可以使用闭包中的变量,下面这个案例留给大家去思考看一看会打印出什么东东。
function foo() {
var a = 'foo';
function zoo() {
console.log(a);
}
return zoo;
}
function poo(fn) {
var a = 'poo';
fn();
}
poo(foo());
3.4.3 防抖和节流
// 节流
function throttle(fn, timeout) {
let timer = null
return function (...arg) {
if(timer) return
timer = setTimeout(() => {
fn.apply(this, arg)
timer = null
}, timeout)
}
}
// 防抖
function debounce(fn, timeout){
let timer = null
return function(...arg){
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arg)
}, timeout)
}
}
3.4.4 函数柯里化
每次只输入一部分函数去处理,该函数会返回一个新函数,再将剩余参数输入到返回的新函数中,最终实现某个功能。
function add (a, b) {
return a + b;
}
console.log(add(1+2)); // 3
如果写成柯里化是这个样子的:
function add (a) {
return function (b) {
return a + b;
}
}
let currey = add(1);
console.log(currey(2)); // 3
// 或者也可以这样写
//console.log(add(2)(1)); // 3
3.5 使用闭包要注意什么
首先是使用闭包容易导致内存泄露,因为闭包会包含其他作用域的变量引用,因此闭包会占用更多的内存空间,所以闭包应该谨慎使用。
四、总结
希望通过阅读本文大家能够对JavaScript中的闭包有一个新的认识,能够在我们的工作中去灵活运用。也希望能够在面试中游刃有余,我是勇宝,一个前端小学生。
最近工作比较忙,网站有几天没有进行维护了,清明回来后会积极的去更新,欢迎大家评论区留言。