第9行,在调用上下文(全局执行上下文)中,createCounter返回的值被赋给了increment。变量increment现在包含了一个函数定义,也就是createCounter返回的函数定义。它不再被标记为myFunction,但定义没有变化。在全局上下文中,它被标记为increment。
第10行,声明一个新变量(c1)。
继续第10行,查找变量increment,它是一个函数,然后调用它。它包含了之前返回的函数定义,也就是第4-5行所定义的内容。
创建新的执行上下文,没有参数,开始执行这个函数。
第4行,counter = counter + 1,在本地执行上下文中查找变量counter。我们只是创建了上下文,并没有声明任何局部变量。在全局执行上下文中,没有标记为counter的变量。Javascript将会执行counter = undefined + 1,声明一个标记为counter的新局部变量,并为其指定数值1,因为undefined其实被视为0。
第5行,我们返回counter的值,也就是数值1。我们销毁本地执行上下文和变量conter。
第10行,返回值(1)被分配给c1。
第11行,我们重复步骤10-14,c2也被赋值为1。
第12行,我们重复步骤10-14,c3也被赋值为1。
第13行,我们记录变量c1、c2和c3的值。
亲自尝试一下,看看会发生什么。你会注意到它并不像你预想地那样输出1、1和1,而是输出了1、2和3。为什么会这样?
increment函数会记住counter的值,为什么会这样?
counter是全局执行上下文的一部分吗?试试console.log(counter),你将得到undefined,所以它不是。
所以,肯定存在另一种被我们忽略的机制——也就是闭包。
下面是它的工作原理。每当声明一个新函数并将其赋值给变量时,实际上是保存了函数定义和闭包。闭包包含了创建函数时声明的所有变量,就像一个背包一样——函数定义附带一个小背包。这个背包保存了创建函数时声明的所有变量。
所以我们上面的解释都是错误的,让我们再试一次,但这次是正确的。
1: function createCounter() { 2: let counter = 0 3: const myFunction = function() { 4: counter = counter + 1 5: return counter 6: } 7: return myFunction 8: } 9: const increment = createCounter() 10: const c1 = increment() 11: const c2 = increment() 12: const c3 = increment() 13: console.log('example increment', c1, c2, c3)第1-8行,我们在全局执行上下文中创建了一个新变量createCounter,它包含了一个函数定义,与上面相同。
第9行,我们在全局执行上下文中声明一个名为increment的新变量,与上面相同。
第9行,我们调用createCounter函数并将其返回值赋给increment变量,与上面相同。
第1-8行,调用函数,创建新的本地执行上下文,与上面相同。
第2行,在本地执行上下文中声明一个名为counter的新变量,并赋值为0,与上面相同。
第3-6行,在本地执行上下文中声明名为myFunction的新变量。变量的内容是另一个函数的定义,即第4行和第5行所定义的内容。我们还创建了一个闭包并将其作为函数定义的一部分,闭包含包含函数作用域内的变量,在本例中为变量counter(值为0)。
第7行,返回myFunction变量的内容,删除本地执行上下文。myFunction和counter不再存在,控制权返回到调用上下文。所以我们返回函数定义及其闭包,闭包中包含创建函数时声明的变量。