搞懂Javascript闭包概念

1、 前言

有这么一个需求,页面上有两个计数器,交互逻辑完全一模一样,但是里面的数据是完全独立的,需要实现这么一个计数器
如果是之前的我,肯定会在这两个计数器的触发按钮上传参,来分别判断然后实现对应的交互,但是如果使用闭包的话就会简单很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function counterCreator() {
var index = 1;
function counter() {
return index ++;
}
return counter;
}

// test
var counterA = counterCreator();
var counterB = counterCreator();
counterA(); // 1
counterA(); // 2
counterB(); // 1
counterB(); // 2

2、 什么是闭包(Closure)

2.1 闭包的概念

MDN中对闭包的定义是:闭包是函数和声明该函数的词法环境的组合。
《JavaScript高级程序设计》的描述:闭包是指有权访问另一个函数作用域中的变量的函数。
《JavaScript权威指南》的描述: 从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
《你不知道的JavaScript》的描述:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
一些文章对闭包的概念解释为:能够读取其它函数内部变量的函数。

下面的一段代码就是闭包的架子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 第一种写法
function Closure() {
let n = 1;
// 这里是闭包函数执行时依赖的变量,每次执行闭包函数时都能访问和修改
return function() {
// 这个函数最终会被赋值给一个变量
}
}

// 第二种写法
(function (data) {
return function () {
console.log(data);
};
})(i),

2.2 闭包的产生

闭包的产生其实来自于js的变量作用域。在js中,变量的作用域属于函数作用域,当函数执行完成之后,作用域就会被清理,内存也就随之被回收。但是由于闭包函数是建立在函数内部的子函数,闭包函数又能够访问父函数的变量,所以就会导致当父函数执行完成时,其作用域不会销毁,该变量永久的保存在内存中直到闭包函数也不存在时才进行销毁。

3、 闭包的应用

  1. 防抖和节流
  2. 柯里化

4、 常见的关于闭包的面试题

4.1 for 循环中打印

请问输出内容是是什么?

1
2
3
4
5
for (var i = 0; i < 4; i++) {
setTimeout(function() {
console.log(i);
}, 300);
}

答案:输出都是4,js 执行的时候首先会先执行主线程,异步相关的会存到异步队列里,当主线程执行完毕开始执行异步队列, 主线程执行完毕后,此时 i 的值为 4,所以再执行异步队列的时候,打印出来的都是 4

如何修正:

1、 改为es6写法

1
2
3
4
5
6
7
8
9
10
11
12
for (let i = 0; i < 4; i++) {
setTimeout(function() {
console.log(i); // 输出 0 1 2 3
}, 300);
}

// 或者可以这样写
for (let i = 0; i < 4; i++) {
setTimeout(() => {
console.log(i) // 输出 0 1 2 3
}, 300);
}

2、 使用闭包

1
2
3
4
5
6
7
8
9
10
for (var i = 0; i < 4; i++) {
setTimeout(
(function (i) {
return function () {
console.log(i);
};
})(i),
300
);
}

4.2 为多个dom元素添加click事件

错误写法

1
2
3
4
5
6
7
8
9
10
11
<button>click1</button>
<button>click2</button>
<button>click3</button>
<script>
let dom = document.querySelectorAll('button');
for (var i = 0; i < dom.length; i++) {
dom[i].onclick = function () {
alert(i)
}
}
</script>

如何修正:

1、 改为es6写法

1
2
3
4
5
6
let dom = document.querySelectorAll('button');
for (let i = 0; i < dom.length; i++) {
dom[i].onclick = function () {
alert(i)
}
}

2、 使用闭包

1
2
3
4
5
6
7
8
let dom = document.querySelectorAll('button');
for (var i = 0; i < dom.length; i++) {
(function (i) {
dom[i].onclick = function () {
alert(i)
}
})(i)
}

搞懂Javascript闭包概念
https://xypecho.github.io/2019/12/02/搞懂Javascript闭包概念/
作者
很青的青蛙
发布于
2019年12月2日
许可协议