javascript闭包有些神秘。在工作中经常用到,甚至有时候在使用闭包的时候没有意识到使用了闭包。
概念
执行上下文环境
在Javascript中,执行上下文环境分为以下2类:
1.全局代码—全局环境,代码首次执行时的环境。

2.函数代码—函数环境,函数执行时函数内部的环境。

调用函数
1.javascript创建一个自己的执行上下文环境。
2.执行上下文环境包含它自己的变量。
3.执行上文环境放到执行栈。执行栈用来跟踪代码在执行过程中执行的位置。
函数执行结束
当函数遇到return声明或一个关闭括号}时,函数执行结束。
1.函数执行上下文环境从执行栈弹出。
2.函数返回return值。如果没有return,返回undefined。
3.函数执行上下文环境被销毁。当前上下文环境的所有变量被销毁。
简单例子
代码如下所示。

执行过程:
1.第4行。定义一个全局变量a,赋值3。
2.第5~8行。定义一个全局变量addTwo,赋值一个函数。
3.第9行。定义一个变量b,执行addTwo函数,执行后的结果赋值给b。
4.在全局执行上下文环境内存中找到变量addTwo和变量a,把a的值作为参数传给函数addTwo,准备执行函数。
5.切换执行上下文环境。创建一个新的局部执行上下文环境,并且压入调用栈。
6.在执行上下文环境中定义变量x。因为3被传递给arguments,变量x被赋值3。
7.接下来,在执行上下文环境中声明一个变量ret,赋值undefined。
8.第6行。执行加操作。从当前执行环境找到变量x,值为3。并且第2个操作数是2。加操作的执行结果赋值给ret。
9.第7行。返回ret的值5,函数结束。
10.第7~8行。函数执行结束,对应的执行上下文环境销毁。变量x和变量ret从内存中销毁。执行环境从调用栈中弹出并且返回值。第6行把返回值赋值给b。
11.第10行。输出出5。
scope

在本例子中,有一个当前局部执行上下文环境和一个全局执行上下文环境。javascript查找变量值:如果在当前局部执行上下文环境中没有找到,则到调用它的环境中查找。依次向上查找,直到全局执行上下文环境(如果全局执行上下文环境中也没有,值为undefined)。
1.第4行。在全局执行环境声明变量val1,赋值2。
2.第5~8行。声明变量multiplyThis,赋值一个函数。
3.第9行。在全局执行环境中声明一个变量multiiplied。
4.从全局环境内存中获取变量multiplyThis,传递6给arguments,执行函数。
5.新的函数调用 = 创建新的执行环境。创建一个新的当前局部执行环境。
6.在当前执行环境,声明变量n,赋值6。
7.第6行。在当前执行环境,声明变量ret。
8.第6行。执行相乘操作。两个操作数是n和val1。查找n变量,在当前执行环境找到n变量,值为6。查找val1变量,在当前执行环境中没有val1变量。进而查找调用环境,调用环境是全局执行环境。在全局执行环境找到变量val1,值为2。
9.两个数相乘6*2=12。赋值12给ret。
10.返回ret变量值。当前执行上下文环境销毁,变量ret和n被销毁。因为val1属于全局执行环境,val1没有被销毁。
11.第9行。12被赋值给multiplied。
12.最后,第20行。输出multiplied的值。 从这个例子中可以看出,函数能够访问调用者的执行环境的变量。
返回一个函数的函数
下面让我们看一下返回一个函数的例子,分析一下它的执行过程。

1.第3行。在全局执行环境声明一个变量val,赋值7。
2.第5~11行。在全局执行环境定义一个createAdder变量,赋值一个函数。第3~7行定义函数addNumbers。
3.第12行。在全局执行环境定义adder变量,赋值undefined。
4.第12行。在全局执行环境内存中找到函数createAdder,然后调用函数。
5.调用函数createAdder。一个新的当前局部执行上下文环境被创建。把新的执行环境压入调用栈。
6.到第6~9行。在当前执行环境,声明一个新的变量,即函数addNumbers。
7.第10行。返回addNumers指向的函数对象。然后从调用栈中弹出当前执行环境。
8.return以后,当前执行环境被销毁。同时addNumers变量被销毁。函数对象一直是存在的,return这个函数对象并且赋值给变量adder。
9.第13行。在全局执行环境定义一个变量num,赋值undefind。
10.下一步需要执行函数。执行变量名为adder的函数。在全局执行环境中找到这个变量,这个变量指向的函数有两个参数。
11.提取参数值,用于在调用函数时传递正确的arguments。第一个参数是7,第2个参数是8。
12.执行adder函数。一个新的当前执行上下文环境被创建。在当前执行环境中两个变量a,b被创建。分别被赋值arguments的7和8。
13.第17行。在当前执行环境声明一个变量ret。
14.第17行。执行加操作。两个操作数分别是a的值和b的值。相加的结果赋值给ret变量。
15.在函数中return ret。当前的执行环境被销毁,从调用栈中弹出。变量a,b和ret也被销毁。
16.返回值被赋值给sum变量。
17.输出sum的值是15。
和预期的一致,sun的值是15。通过这个例子我们可以看出,(1)一个变量指向函数对象,函数定义仅仅是一个对象。直到函数执行时,才是代码片段。(2)每当函数被调用时,一个临时的执行上下文环境被创建。当函数执行完后,这个执行环境销毁。
闭包

1.第4~11行。在全局执行环境创建一个变量createCounter,赋值一个函数。
2.第12行。在全局执行环境声明一个increment变量。
3.第12行。调用createCounter函数,给变量increment赋值函数的返回值。
4.第4~11行。调用函数。创建一个新的执行上下文环境。
5.第5行。在当前执行环境中,声明一个counter变量,赋值0。
6.第6~9行。在当前执行环境中,声明myFunction变量,赋值一个新的function。
7.第10行。返回myFunction变量指向的函数。当前执行环境被销毁。myFunction和counter被销毁。
8.第12行。createCounter函数返回的函数对象赋值给increment。myFuncrion引用的函数对象不再被myFuncrion引用,而是被全局执行环境的increment引用。
9.第13行。声明一个变量c1。
10.第13行。查找变量increment,然后执行这个函数。
11.创建一个新的执行环境。
12.第7行。counter = counter + 1。在当前执行环境没有找到变量counter,然后到全局执行环境中查找,没有找到。Javascript将会这样计算:counter = undefined + 1。最终,在当前执行环境声明一个变量counter,赋值1(undefined是0)。
13.第8行。返回counter的值1。销毁当前执行环境和变量counter。
14.回到第13行。返回值1赋值给c1。
15.第14行。重复10~14的步骤,c2也被赋值1。
16.第15行。重复10~14的步骤,c2也被赋值1。
17.第16行。输出c1,c2,c3的值。
实际上,代码执行后,执行结果是1,2和3。而不是,按照上面分析的结果1,1和1。increment函数能够记住counter的值。那么是怎么做到的呢?我们看另外一种机制:闭包。
闭包工作机制
当定义一个函数并且把这个函数赋值给一个变量时,存储了函数定义同时,也存储了一个闭包。闭包类似于一个背包。函数定义携带一个背包。背包中包含了函数创建时作用域范围内的所有变量。

1.第4~11行。在全局执行环境创建一个变量createCounter,赋值一个函数。
2.第12行。在全局执行环境声明一个increment变量。
3.第12行。需要调用createCounter函数,把函数的返回值赋值给increment变量。
4.第4~11行。调用函数。创建一个新的当前局部执行上下文环境。
5.第5行。在当前执行环境,定义一个counter变量,把0赋值给counter。
6.第6~9行。在当前执行环境,声明一个myFunction变量。同时创建一个闭包,闭包作为函数定义的一个组成部分。闭包包含当前作用域范围内的所有变量,在这个例子中,包含counter变量。
7.第10行。返回myFunction指向的函数。当前执行环境被销毁。myFunction和counter也被销毁。返回函数的定义和函数的闭包。
8.第12行。createCounter执行后返回函数,赋值给increment。increment现在包含一个函数定义(和闭包)。
9.第13行。声明一个变量c1。
10.第13行。查找increment,并且调用它。
11.创建一个新的执行上下文环境。
12.第7行。counter = counter + 1。从闭包中查找变量counter。在闭包中包含一个变量counter,它的值是0。在第7行后,counter的值被设置为1,并且存储在闭包中。闭包中包含的counter的值是1。
13.第8行。返回counter的值1,销毁当前执行环境。
14.返回到13行。返回的1赋值给c1。
15.第14行。重复10~14步。查找闭包时,当前闭包的counter值是1。在12步或第4行,counter值被设置。值加1并且存储在increment的闭包中。c2被赋值2。
16.第15行。重复10~14步,c3被赋值为3。
17.第16行。输出c1,c2和c3的值。
从上面的例子中,可以看出闭包是如何工作的。当定义一个函数时,函数包含函数的定义和闭包。闭包是在创建函数时在作用域内的所有变量的收集。 任何一个函数都有闭包。即使是在全局作用域的函数。在全局作用域的函数也创建一个闭包。由于在全局作用域创建函数,它们可以访问全局作用域的变量。在这里闭包概念不明显。
当一个函数返回一个函数时,闭包的概念明显的体现出来了。返回的函数能够访问不是全局作用域内的变量,访问的变量仅仅存在闭包中。
总结
闭包类似于一个背包。当一个函数被创建、被传递或被返回的时候,函数携带一个背包。背包中包含函数创建时函数所在的作用域的所有变量。
https://medium.com/dailyjs/i-never-understood-javascript-closures-9663703368e8