理解JS闭包
从事web开发工作,尤其主要是做服务器端开发的,难免会对客户端语言JavaScript一些概念有些似懂非懂的,甚至仅停留在实现功能的层面上,接下来的文章,是记录我对JavaScript的一些概念的理解。欢迎大牛拍砖吐糟
理解JS中的闭包,首先理解JavaScript中的作用域以及作用域链的问题,可以参考下我的拙见JS函数作用域及作用域链理解。
什么是闭包
学术说法:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。(你能看懂吗?反正我不能)
专业说法:函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内。(这个你懂吗?我略懂)
通俗说法:所有JavaScript函数都是闭包,因为函数都是对象,都关联到作用域,并且变量都保存在函数作用域内。(这个有点扯,困惑我的闭包,就这么简单)
但是我们通常说的闭包是当一个函数嵌套另一个函数,外部函数将嵌套函数对象作为返回值返回的时候,我们把这种情况称为闭包。
看下面一个例子
function func() { var num = 0; //声明局部变量:num function f() { //嵌套函数,在作用域里 console.log(++num) } return f(); //调用嵌套函数f,并将f的执行结果返回 } func(); //输出:1
函数func()声明了一个局部变量,并定义了一个函数f(),最后将函数f()的执行结果返回。很容易理解输出结果为1。现在我们将这段代码稍作改动,如下面
function func() { var num = 0; //声明局部变量:num function f() { //嵌套函数,在作用域里 console.log(++num) } return f; //调用嵌套函数f,并将f的执行结果返回 } var ff = func(); ff(); //输出:1 ff(); //输出:2 ff(); //输出:3
现在函数func()仅仅返回函数内嵌套的一个函数对象,而不是直接返回结果。在定义函数作用域外面调用嵌套函数,由于这个函数的作用域链是在函数定义的时候就已经创建的。嵌套的函数f()定义在这个作用域链里,并且变量num又是局部变量,不管在何时何地执行函数f(),这种绑定依然有效。因此,局部变量会一直保存下来。正式因为闭包的这一强大特性,才是其显得非常重要。
闭包实现
当一个函数func()创建后,它保存了一个作用域链,并且作用域链会被函数func()中的作用域中可访问的数据对象num填充。
执行此函数func()时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。并且运行期上下文对应自己的作用域链,同时它的作用域链初始化为当前运行函数func()的[[Scope]]所包含的对象。
然后执行环境会创建一个活动对象(call object),用来报讯局部变量,并把这个对象添加至作用域链[[Scope]]中。假如不存在嵌套函数,也没有其他引用的时候,活动对象就会被当做垃圾回收掉。但是这里有嵌套函数f(),并将函数f()作为返回值返回时,就会又一个外部引用指向这个嵌套函数f(),活动对象就不会当做垃圾回收掉,并且活动对象指向的变量所绑定的对象也不会被回收。