js 回顾之前写的js闭包文章

很久以前,我曾经参考阮一峰dalao的一篇闭包科普,写了一篇文章(讲述我对js闭包的理解)。最近,我在知乎上发现了这个问题《阮一峰关于Javascript中闭包的解读是否正确?》,所以打算参考这个问题,更正一下之前的说法。

参考:https://www.zhihu.com/question/27712980

 

本文是对很久以前一篇文章的更正:

http://www.xie4ever.com/2016/11/22/js-%E7%A0%94%E7%A9%B6%E4%B8%8Bjs%E7%9A%84%E9%97%AD%E5%8C%85/

一、一些问题

(1)函数内部声明变量的问题

函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

这个说法是有问题的。“在函数内部声明变量不使用var”不等于“声明了一个全局变量”,而是“根本没有声明变量”,相当于访问了window的属性。

(或许称为“设置了window的属性比较好?”,似乎也不能这么说,window的属性应该是固定的,不能自己设置,也许可以理解为“给window的属性中加了一个变量”,即“访问window的属性所创建的变量”)。

举个例子:

在function中定义了一个c(这里的c就是window的属性),随后就可以访问这个属性。

“全局变量”和“访问window的属性所创建的变量”存在区别,“访问window的属性所创建的变量”是可以delete的(相当于删掉了window属性中的某个变量),而“全局变量”是不能delete的。

(2)不严谨的闭包说明

闭包就是能够读取其他函数内部变量的函数。

这个说法不严谨,“其他函数”不能是“任意的其它函数”(但是我个人理解尚浅,举不出来反例,有点难受)。

查看一下wikipedia对闭包的定义:

闭包是由函式和与其相关的参照环境组合而成的实体。

这个定义指明了闭包的组成部分,又限定的闭包的作用范围,是比较严谨的说法。

况且,闭包只需要有扩充变量的作用范围即可,不需要有“其他函数的存在”(我没理解这句话是什么意思)。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。

即便在函数定义了函数,如果没有引用父函数作用域内的变量,照样不是闭包。

(3)例子有点复杂

阮一峰大佬出了这样一个思考题:

运行结果为:

阮一峰dalao完全没有解释this和运行时机制,所以我们自己尝试理解这段程序。

首先看到object.getNameFunc(),调用了object对象中的getNameFunc()方法。这个方法的返回值是另一个方法本身。我们可以改写一下:

运行结果变成了:

可以看见object.getNameFunc()就是另一个方法。为了执行这个方法,我们又加上了“()”,于是object.getNameFunc()()意为“执行方法中return的方法”。

那么问题来了,这个方法的内容为return this.name,这里的name指的是object类中定义的变量name,还是全局变量中的name?

要搞明白的是这里的“this”到底指向谁。对于this这个知识点,可以参考:

http://web.jobbole.com/82262/

要清楚:“this跟函数在哪里定义没有半毛钱关系,函数在哪里调用才决定了this到底引用的是啥。”、“函数在哪里调用决定了this指向的是啥”。

我们从最初的调用看起:

上面也提到了,object.getNameFunc()只是负责获取一个方法,我们称之为fun方法:

获取了fun方法之后,object.getNameFunc()就可以替换成fun,变成:

这里就是关键了,fun方法很明显是直接调用的,等价于:

fun方法中的this指向window,所以this.name指向全局变量中的name。

题外话,如何返回object对象中的name呢?

我们要牢记“谁调用指向谁”。既然要返回object对象中的name,那么this当然要指向object对象,也就是说必须得由object亲自调用某个方法返回this.name。

这个例子不适合使用闭包,我们稍微改写一下:

这里的getNameFunc方法是由object对象亲自调用的,所以this指向object对象,this.name指向objcet对象中的name变量。

二、回顾闭包

举个例子(因为a方法调用了另一个方法,并且引用了a方法中的变量,所以这是一个典型的闭包):

在上面的例子中,变量test在方法a中定义,但使在setTimeout方法的参数中对它保持了引用。

从理论上来说,当方法a执行完后(方法a执行完毕),方法a在执行过程中产生的变量、对象都可以被销毁。但是,因为test已经被引用,所以不能随这个方法a的执行结束而被销毁(我还没用完呢,你销毁了我怎么办),直到定时器里的方法(引用test的方法)被执行完毕。

再举个例子(因为obj方法的返回值是个对象,对象中有set和get两个方法,都引用了obj方法中的变量a,所以这是个稍微复杂一点的闭包):

在上面的例子中,从理论上来说,obj方法在执行完后,函数体内东西都应该被回收。但是,因为obj方法执行后的返回值b对象具有set和get方法,而这两个方法对a保持了引用,所以obj执行过程中产生的a就不能销毁。

直到set和get方法执行完毕,b对象被回收后,obj方法中的a才会被回收。

参照上面的两个例子,我们可以这样理解闭包:

闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

三、总结

有种突然开窍的感觉!