JavaScript规定,函数可以嵌套,内部函数和外部函数可以形成一定的上下文关系。另外,在JS中,一切皆是对象,例如函数,所以函数也可以返回函数。
函数被调用后,在默认情况下,上下文活动对象会被立即释放,避免占用系统资源。但是,当函数内部的私有变量、参数、私有函数被外界引用,则这个上下文活动对象暂时会继续存在,直到所有外界引用被注销。
C语言规定,函数不能嵌套,函数之间是平行的关系。但在函数内部可以定义静态局部变量。
C++的shared_ptr指针,可以通过引用计数来决定内存资源释放的时机。
但是,函数的作用域是封闭的,外界无法访问。那么,在什么情况下,外界可以访问到函数内的私有成员呢?
根据作用域,内部函数可以访问外部函数的私有成员。如果内部函数引用了外部函数的私有成员,同时内部函数又被传给外界,或者对外界开放,那么闭包体就形成了。这个外部函数就是一个闭包体,它被调用后,它的调用对象暂时不被注销,其属性会继续存在,通过内部函数,可以持续读写外部函数的私有成员。
典型的闭包体是一个嵌套结构的函数。内部函数引用外部函数的私有成员,同时内部函数又被外部函数引用,当外部函数被调用后,就形成了闭包,这个函数也称为闭包函数。
下面是一个典型的闭包结构。
解析过程简单描述如下:
1 在JavaScript脚本预编译期,声明的函数f和变量c,先被词法预解析。
2 在JavaScript执行期,调用函数f,并传入值5。
3 在解析函数f时,将创建执行环境(函数作用域),创建活动对象,把参数和私有变量、内部函数都映射为活动对象的属性。
4 参数x的值为5,映射到活动对象的x属性。
5 内部函数,通过作用域链,引用了参数x,但是还没有被执行。
6 外部函数被调用后,返回内部函数,导致内部函数被外部变量C引用。
7 JavaScript解析器检测到外部函数活动对象的属性被外界引用,无法注销该活动对象,于是在内存中继续维持该对象的存在。
8 当调用c,即调用内部函数时,可以看到外部函数的参数x存储的值继续存在,于是也就可以实现后续运算操作,返回x+y=5+6=11。
下面的结构形式也可以形成闭包:通过全局变量引用内部函数,实现内部函数对外开放:
使用闭包实例之一:使用闭包实现优雅的打包,定义存储器:
在上面的实例中,通过外部函数设计一个闭包,定义一个永久的存储器。当调用外部函数,生成执行环境之后,就可以利用返回的匿名函数,不断向闭包体内的数组a传入新值,传入的值会一直持续存在。
使用闭包实例之二:在网页中事件处理函数很容易形成闭包
在浏览器中浏览时,首先单击“生成闭包”按钮,生成一个闭包。单击第2个按钮,可以随时查看闭包内私有变量a的值。单击第3,4个按钮时,可以动态修改闭包内变量a的值。
闭包的价值是方便在表达式运算过程中存储数据,但是,它的缺点也不容忽视。
由于函数调用后,无法注销调动对象,会占用系统资源;在脚本中大量使用闭包,容易导致内存泄露。解决的方法是慎用闭包,不要滥用。
由于闭包的作用,其保存的值是动态的,如果处理不当,容易出现异常或错误。
下面实例是设计一个简单的选项卡效果:
在mouseover事件处理函数中跟踪变量i的值,i 的值都变成了3,tab[3]自然是一个null,所以也不能够读取className属性,浏览器会抛出以下异常:
SCRIPT5700:无法设置未定义或null引用的属性"className"
上面的JavaScript代码是一个典型的嵌套函数结构。外部函数为load事件处理函数,内部函数为mouseover事件处理函数,变量 i 为外部函数的私有变量。
通过事件绑定,mouseover事件处理函数被外界引用(li元素)。这样就形成了一个闭包体。虽然在for语句中为每个选项卡 li 分别绑定事件处理函数,但是这个操作是动态的,因此tab[i]中 i 的值也是动态的,所以就出现了上述异常。
解决闭包缺陷最简单的方法是阻断内部函数对外部函数的变量引用,这样就形成不了闭包体。针对以上实例,可以在内部函数(mouseover事件处理函数)外边增加一层防火墙,禁止其直接引用外部变量。
在for语句中,直接调用匿名函数,把外部函数的i变量传给调用函数,在调用函数中接收这个值,而不是引用外部变量 i ,从而规避了闭包体带来的困惑。
闭包确实是js最难理解的东西了,闭包的本质可以说是作用域链形成的要理解闭包,必须要理解这几个概念
① 词法作用域
② 执行上下文
③ 活动对象
④ scope属性
除了模拟类的私有变量和私有方法。闭包还可以用来模拟类的静态变量和方法。
之所以把上述一些js变量和函数称为“静态”,是借用了Java的提法。
这些“静态”变量和方法被保存在闭包中,在内存中是唯一的,不会随着该函数副本的增加而增加。如果一个函数需要被实例化多次, 但其中的一些内部函数并不需要访问任何实例数据,从节省内存的角度考虑,可采用上述构建静态函数的方法。js中的“静态”概念,有一点与Java不同: 如果属性被设为null,即不再有引用指向它,那么它的闭包也将消失, 保存在闭包中的静态变量和方法,也将被垃圾回收器择机回收。
由于js没法像一般语言C++,PHP等使用static达到函数内全局变量的效果,故使用闭包的一直保存在内存的特性达到了这个效果。
闭包的特点使函数拥有了类的感觉,访问局部变量好比访问私有属性,闭包相当于类的公共函数,调用外部函数好比类实例化,始终保持在内存中好比类具有全局的生命周期,bigger感觉瞬间提高了。
附闭包局限性实例的CSS代码:
ref
1 https://www.zhihu.com/question/27712980
2 《HTML5+CSS3+JavaScript从入门到精通(实例版)》
-End-