之前看过廖雪峰老师的闭包讲解,当时不明白其中的运行原理,今天在看了渡一的js课程后,明白了闭包的原理。那就举两个例子来解释下。
首先要了解预编译中的几个名词:
GO对象(Global Object),全局环境下创建的执行期上下文,就是全局作用域。
AO对象(Activation Object),也叫执行期上下文。在函数被执行前一刻创建,可以称为是局部环境、局部作用域。
Scope chain(作用域链),被创建的局部作用域会继承父级的作用域,形成一条作用域链。
再记住一点
但凡是内部的函数被保存到了外部,它一定生成闭包。内部函数会保存外部函数的劳动成果,保存了还没执行,被放到了外部,而外部的函数自身AO就被内部的闭包函数保存了。
在一个函数的执行前有个预编译,当函数执行时,会创建一个称为执行期上下文的内部对象,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
步骤如下:
- 创建AO对象(Activation Object),执行期上下文。
- 寻找函数的形参和内部的变量声明,将变量声明和形参名作为AO属性名,如果有重复则跳过,初始值为undefined。
- 将实参的值放到形参里面去,将实参值和形参统一。函数内变量声明的值传递到AO对应的属性名。
- 在函数体内部寻找函数声明,创建AO属性名,属性值为函数体。
- GO除了没第三步,其它都和AO一样。
注意!
接下来看廖雪峰老师,讲python闭包那节课中的两个例子
def count():
fs = [ ]
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
--------------------------------
>>> f1()
9
>>> f2()
9
>>> f3()
9
按上面的理论来看,在for循环结束后,原本的fs = [ ]从一个空数组变成了fs = [ f , f , f ],里面都是一样的函数名。
并且在循环体结束后,i = 3。
此时 f1, f2, f3 = count(),所继承的东西是一样的,都是函数名 f。
在这里,因为函数b被return抛出来了,它带着a的AO和GO,而在a的AO里i = 3。
所以在执行f1, f2, f3时,结果就都等于9了。
然后看第二个例子:
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
f1, f2, f3 = count()
---------------------------------
>>> f1()
1
>>> f2()
4
>>> f3()
9
在count的AO里,空数组从fs =[ ]变成了fs = [ f(1) , f(2) , f(3) ]。
这里就不继续列出函数f,g的AO了。
直接从return fs开始。这时候f1, f2, f3从数组中被赋予了三个不同的函数,这三个函数是以数组的形式,被闭包返回了出来,这三个函数都得到了count的AO和GO。
当执行f1, f2, f3时,其实执行的是被返回的g,函数g执行的就是j * j这个表达式。
而由于给数组末尾添加的是f (i),也就是 f(1) , f(2) , f(3) ,将实参替换形参,就得出了1,4,9的结果。
底层的运行大概就是这样了,但还有很多东西没有写出来,闭包最重要的就是弄明白AO和GO的运行步骤、作用域链继承,这些懂了,闭包就没问题了。
闭包的作用:
1.实现共有变量。
2.可以用作缓存。
3.可以实现函数封装,及属性私有化。
4.模块化开发,防止污染全局变量。