前端都会去了解的前端编程

JavaScript 闭包

2016-12-13  本文已影响620人  韩宝亿

对于JavaScript的学习来说,闭包这一块一直是玄而又玄的东西,初学者很难掌握,我是学了大半年的JS,现在又回过来看【闭包】这部分,这里稍微总结一下,我采用由浅入深一步步的方式讲解,为了能帮助初学者们更好的理解,我将从下面几个方面展开:

1.创建内部函数

支持内部函数的语言允许我们在必要的地方集合小型实用函数,以避免【污染命名空间】。
所谓内部函数,就是定义在另一个函数中的函数


Paste_Image.png

innerFn()就是一个被包含在outerFn()作用域中的内部函数。这意味着在outerFn()内部调用innerFn()是有效的,而在outerFn()外部调用innerFn()则是无效的。


Paste_Image.png
这种技术特别适合于小型、单用途的函数。例如,【递归】通常最适合通过内部函数来表达

1.1在任何地方调用内部函数

JavaScript中的内部函数能够逃脱定义它们的外部函数。

JavaScript的机制是只要存在调用内部函数的可能,JavaScript就需要保留被引用的函数,只有最后一个变量废弃,它的【垃圾收集器】才能出面释放相应的内存空间。

1.2理解变量作用域

内部函数也可以拥有自己的变量,只不过这些变量都被限制在内部函数的作用域中:


Paste_Image.png

每当通过引用或其他方式调用这个内部函数时,都会创建一个新的innerVar变量,然后递增。

内部函数可以像其他函数一样引用全局变量:


Paste_Image.png

现在,每次调用内部函数都会持续地递增这个全局变量的值。

当这个变量是父函数的局部变量时,因为内部函数会继承父函数的作用域,所以内部函数也可以引用这个变量:


Paste_Image.png

通过每个引用调用innerFn()都会独立地递增outerVar。因为第二次函数调用的作用域中创建并绑定了一个新的outerFn()的【实例】。

当内部函数在定义它的作用域的外部被引用时,就创建了该内部函数的一个【闭包】。从本质上讲,如果内部函数引用了位于外部函数中的变量,相当于授权该变量能够被延迟使用。因此,当外部函数调用完成后,这些变量的内存不会被释放,因为闭包仍然需要使用它们。

2.处理闭包之间的交互

当存在多个内部函数时,很可能会出现意料之外的闭包。假设我们又定义了一个递增函数,这个函数中的增量为2:


Paste_Image.png

这里,我们通过【对象】返回两个内部函数的引用调用任何一个内部函数。

这两个内部函数引用了同一个局部变量,因此它们共享同一个【封闭环境】。当innerFn1()为outerVar递增1时,就为调用innerFn2()设置了outerVar的新的起点值,反之同理。

3.在jQuery中创建闭包

3.1 $(document).ready()的参数

由于我们通常把$(document).ready()放在代码结构的顶层,因而这个函数不会成为闭包。但是,我们的代码通常都是在这个函数内部编写的,所以这些代码都处于一个内部函数中:


Paste_Image.png

这里和前面的例子其实是一样的,只不过外部函数是传入到$(document).ready()中的一个回调函数。innerFn()引用了位于回调函数作用域中的readyVar,因此innerFn()及其环境就创建了一个闭包。两次调用这个内部函数,两次输出保持了readyVar的值。

3.2绑定事件处理程序

.ready()结构通常用于包装其他的jQuery代码,包括【事件处理程序】的赋值。因为处理程序是函数,它们也就变成了内部函数;而且因为这些内部函数会被保存并在以后调用,于是它们也会创建闭包:


Paste_Image.png

这里的变量counter可以被.click()处理程序中的代码引用,由于创建了闭包,每次单击按钮都会引用counter的同一个实例。也就是说,消息会持续显示一组递增的值,而不会每次都显示1。

事件处理程序同其他函数一样,也能够共享它们的封闭环境:


Paste_Image.png

因为这两个函数引用的是同一个变量counter,所以两个链接的递增和递减操作会影响同一个值,而不是各自独立的值。

3.3在循环中绑定处理程序

4、 应对内存泄漏的风险

4.1避免意外的引用循环

闭包可能会导致在不经意间创建引用循环。因为函数是必须在内存中的对象,所以位于函数封闭环境中的所有变量也需要保存在内存中:


Paste_Image.png

这里创建了一个名为outerVar的对象,该对象在内部函数innerFn()中被引用。然后,为outerVar创建了一个指向innerFn()的属性,之后返回了innerFn()。这样就在innerFn()上创建了一个引用outerVar的闭包,而outerVar又引用了innerFn()。


Paste_Image.png
这里我们修改了innerFn(),使它不在引用outerVar。但是,这样做仍然没有断开循环。即使innerFn()不再引用outerVar,outerVar也仍然位于innerFn()的【封闭环境】中。由于闭包的原因,位于outerFn()中的所以变量都隐含地被innerFn()所引用。因此,闭包会使意外的创建这些引用循环变得易如反掌。

4.2 控制DOM与JavaScript的循环

旧版本IE中存在一种难以处理的引用循环问题。当一个循环中同时包含DOM元素和常规的JavaScript对象时,IE无法释放任何一个对象,除非关闭浏览器。

Paste_Image.png

当指定单击事件处理程序时,就创建了一个在其封闭的环境中包含button变量的闭包。而且,现在的button也包含一个指向闭包(onclick属性自身)的引用。这样,就导致了在IE中即使离开当前页面也不会释放这个循环。
【解决方法】:

Paste_Image.png

因为hello()函数不再包含button,引用就成了单向的(从button到hello)、不存在的循环,所以就不会造成内存泄漏了。

【用jQuery化解引用循环】

Paste_Image.png

即便此时仍然会创建一个闭包,并且也会导致循环,但这里的代码却不会使IE发生内存泄漏。因为jQuery会手动释放自己指定的所有事件处理程序。只要坚持用jQuery的事件绑定方法,就无需为这种特定的常见原因导致的内存泄漏而担心。

上一篇下一篇

猜你喜欢

热点阅读