Javascript中的闭包是如何产生的,具体是什么定义?
JavaScript闭包
闭包的概念对于接触C家族语言的人来说可能比较陌生,JavaScript之所以有闭包的概念,自然是因为它的一些不同的特性。在解释闭包前,我们需要先来了解一下JavaScript特有的几个特性:
作用域
什么是作用域,考虑如下的c代码:
void foo(){
//作用域A
int a=1;
if(a>0){
//作用域B
int b=2;
}
cout<<b; ///=> 'b' : undeclared identifier
}
在这个函数中变量a所属的的作用域是整个foo函数,变量b所属的作用域是if语句块,在if块之外访问变量b会显示未定义,但在if块内可以访问外部作用域的变量。
JavaScript也有它的作用域,但有一点不同的是,它没有块作用域(ES6添加了let
和const
关键词,可以声明一个块作用域的变量),只有函数作用域,所以对于下面的代码:
//全局作用域
var foo = function(){
//函数(局部)作用域
var a=1
if(a>0){
var b=2
}
console.log(b)
}
a ///=> a is not defined 因为a的作用域是foo函数,这里是全局作用域。
foo() ///=> 2 因为b在foo函数的作用域内。
在JavaScript中,使用var声明的就是局部变量(在外部作用域中无效),而不使用var直接声明的变量则是全局变量(在所有作用域中都有效)。内部的作用域可以访问外部的作用域
函数就是对象
在JavaScript中,函数创建时,解释器会将函数相关的所有相关资源(参数,变量,声明)打包成一个对象,还会执行一些其他操作(我们后面在讲)。需要注意的是,对象可以是嵌套的。
***
理解上面两个特性之后,我们就来看一种特殊的情况,即嵌套函数。来看一段代码:
function greet(){
//作用域A
var name="me"
return function(){
//作用域B
console.log( 'Hi,' + name )
}
}
在这段代码中,出现了两个函数,一个是greet函数,还有一个是返回的一个匿名函数。
greet函数就是一个正常的函数,在创建时,解释器会打包其内部的变量,声明,参数(没声明参数)。这一切都很正常。
但是对于匿名函数来说,它创建了一个作用域B,在作用域A的内部,所以它可以访问作用域A内的变量,如name
,而在这个匿名函数中,也确实使用到了name
变量,所以在创建该匿名函数后,解析时解释器会将需要用到的变量(name
),声明,参数(无)打包成一个对象,毕竟只有这样,才能保证这个函数在调用的时候能够正常执行。
所以如果我们调用时就会出现这种情况:
greet() //=> 返回一个函数(内部的匿名函数)
//下面调用该匿名函数,有几种方法。
greet()() //=> Hi,me
getgreet = greet() //通常情况下,调用greet之后,greet的作用域A就应该被释放了
getgreet() //=> Hi,me //但是在这里name还可以在作用域B中访问,好像并没有被释放
这就说明,name变量被解析在匿名函数中了,并且实际上,解析匿名函数时,作用域B会链接到作用域A上,再链接到主调函数(即产生了作用域链),以保证作用域B可以保持对作用域A的访问。