更好的理解闭包
闭包是Javascript常说的一个话题,面试官都问的问题,很多高级应用都要依靠闭包实现。
1 | 闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。 |
变量的作用域
这是一个至关重要的问题。变量的定义并不是以代码块作为域的,而是以函数作为作用域。也就是说,如果变量是在某个函数中定义的,那么它的函数以外的地方是不可见的。而如果变量是定义在if或者for这样的代码块中的,它的代码块之外是可见的。
全局变量:指的是定义在全局代码中的变量。
局部变量:指的在函数里定义的变量。
其中,函数内的代码可以像访问自己的局部变量那样访问全局变量,反之则不行。
如果我们声明一个变量时没有使用var语句,该变量就会黙认为全局变量
尽量将全局变量的数量降到最低,以避免命名冲突。想想,如果两个人在同一段脚本的不同函数中使用了相同的全局变量名,就会发生令你捉狂的BUG,也难以找到。
最好使用var来声时变量(ps:es6中,let比var更完美,实现块级作用域)。
最好可以在函数的第一行,定义变量,后期也轻松找到相关变量的定义。
变量提升
1 | var a = 123; |
这里第一个alert()是undefined,这是因为函数域始终优先于全局域,所以局部变量a会覆盖所有与它同名的全局变量,尽管第一次调用时,a还没正式定义(值为undefined),但a已经存在于本地空间了。这就是提升。
当Javascript执行过程进入新的函数时,这个函数内被声明的所有变量都会被提升到函数最开始的地方。
注意是,提升的只是变量,相关的赋值操作并不会被提升。
突破作用域链
上图左边,如果在C点,那就是位于函数N中,是可以访问全局空间、F空间和N空间。其中,a和b是不连通的,因为b在F以外是不可见的。当将N空间扩展到F以外(上图右边),并止步于全局空间以内时,就产生个厉害的东西——闭包。
N和a一样,是在全局空间,而且由于函数还记得它在被定时所设定的环境,因此它依然可以访问F空间并使用b。
相关定义与闭包
事实上,每个函数都可以被认为是一个闭包。因为每个函数都在其所在作用域中维护了某种私有联系。但在大多数时候,该作用域在函数执行完就自行销毁了。
像前面说的突破的作用域,导致作用域被保持。
循环中的闭包
先来看来经典例子:
1 | function F(){ |
运行后,结果是arr=[3,3,3]
显然,这并不是我想要的结果。
原来这里创建了三个闭包,而它们都指向了一个共同的局部变量i。但是,闭包并不会记录它们的值,它们所拥有的只是相关域在创建时的一个连接(即引用)。在这个例子中,变量i恰巧存在于定义这三个函数域中。对这个三个函数中的任保一个而言,当它要去获取某个变量时,它会从其所在的域开始逐级寻找那个距离最近的值。由于循环结时,i的值为3,所以这三个函数都提向了这一共同值。
想要预期的结果,就要换一种闭包形式:
1 | function F(){ |
这样就能获得人们预期的结果了arr=[0,1,2]