JavaScript之闭包

1. 闭包的定义

mdn对闭包的定义:闭包是函数和声明该函数的词法环境的组合。
可能读了上面的定义你有点懵,但是没有关系,我们继续往下看。
其实对闭包的定义我们可以分为从理论角度或者从实践角度来看。

从理论角度来看:
所有的函数都是。因为它们都在创建的时候就将上层上下文的数据保存了起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用的是最外层的作用域。

当然我们不能从理论角度来定义闭包,我们需要从实践角度来定义闭包,下面是从实践角度来定义的闭包。

从实践角度来看:
需要满足下面两点的才是闭包:

  1. 即使创建它的上下文已经销毁,但是它任然存在(比如:内部函数从父函数中返回)
  2. 在代码中引用了既不是函数参数也不是函数的局部变量的变量。(其实就是父级作用域的变量或者全局的变量)

    2. 闭包的例子

注意:在定义函数的时候,函数的作用域就已经定义好了。

1
2
3
4
5
6
7
8
9
10
11
12
(function(){
const a = 1;
const b = 2;
function add(){
return a + b;
}
window.add = add;
})()
add();
// 此时你只需要判断以上代码是否满足实践角度的两个条件,如果满足的话就是闭包。
// 1. 创建add函数的立即执行函数已经被销毁了,并且通过window将add保存在了外部了。
// 2. a变量和b变量用的是父级作用域的变量。

下面我们再来看一个经典的例子,可能大家才开始学习的时候,都遇到过类似的问题。

首先,我先将问题列出来,大家先看一看:

1
2
3
4
5
6
7
8
9
var data = [];
for(var i = 0 ; i < 3 ; i++){
data[i] = function(){
console.log(i);
};
}
data[0](); // 3
data[1](); // 3
data[2](); // 3

此时你会发现和你想象中打印的不太一样,其原因就是因为作用域的问题,在for循环中,用var定义的变量是一个全局变量,最终循环结束,i保存在内存中不会马上销毁,所以当你在循环完的时候看到的i是3。当执行data[0]、data[1]、data[2]函数的时候,使用的是i为3。

如果你想实现打印出0,1,2效果的话,解决办法就是闭包。

1
2
3
4
5
6
7
8
9
10
11
var data = [];
for(var i = 0 ; i < 3 ; i++){
(function(i){
data[i] = function(){
console.log(i);
};
})(i)
}
data[0](); // 0
data[1](); // 1
data[2](); // 2

大家先试着消化消化闭包,可能对初学者还有点懵,但是没关系,在你的代码生涯中还会出现许多许多的闭包问题,当你多遇到多思考就会真正的理解它了。

在后面我会写关于执行期上下文的笔记,此时我会再将闭包用执行期上下文来给大家展示,此时你就会真正意义上的理解闭包发生的过程了。