JavaScript闭包讨论(一)

时间:2021-10-10 22:41:05

在js中函数是一个最重要的概念,函数不仅是一个函数也是一个对象,甚至可以实现继承。当然这么多的身份集中在函数本身身上麻烦自然就不少,js中函数允许嵌套,于是闭包的概念应运而生。在谈论闭包之前,先了解下js的函数。

function f(){
//TODO....
}

函数的定义如上所示,并不陌生,实际上在函数执行的时候会给函数生成一个执行环境,这个执行环境是js中的一个很重要的概念。所谓的执行环境就是函数或者变量允许访问的域。这个域限制了他们在执行过程中允许访问的其它数据。
可以这么理解
函数一旦运行就会被关进小屋子里面,在执行结束之前他只能获取屋子里面有的东西,至于屋子里面该有什么我们之后讨论。当函数执行完毕后才会被放出来,同时小屋子也被摧毁掉。

在js当中最先被关进屋子的肯定是window对象了,因为当浏览器窗口打开的时候window对象就被创建。并且只有当浏览器被关掉的时候window对象在被释放。所以我们自定义的任何函数,变量都是在window的屋子中执行的。


下面该说说屋子里有什么了。也就是说函数的执行环境里面都有哪些东西。当屋子被创建的时候,会生成一个变量对象。也就是说屋子里面有个宝箱,宝箱里装着环境里定义的所有变量和函数,例如:

function f(val){
return val+1;
}
f();

当调用函数f的时候我们得知道发生了什么:

  1. window对象被关进屋子里面
  2. 在window的屋子里面f函数被关进另一个屋子里面
  3. f函数的屋子里面有一个宝箱,里面有val这个变量,arguments数组变量和this变量等。其中后两者是每个函数都有的,val是函数传来的参数,好比f被关进屋子的时候把val也带了进来
  4. 还有一点是f函数的屋子里面的宝箱连着一个绳子,绳子通向屋外,连接着window对象屋子的宝箱。

上面说的绳子叫做作用域链(说链子可能会更好),有了这个链子,f函数就可以用window宝箱里面的东西了(说管道更贴切)


总结下大概就是这么一套官方术语:函数在第一次被调用的时候会创建一个执行环境,级响应的作用域链,然后会用this,arguments等其他参数初始化活动对象,函数中活动对象被赋值给变量对象。在作用于链中外部活动对象始终位于第二位,一次类推。

然而事情并不总是简单的,看下面这个例子:

function f(val){
val++;
return function(){
val++
console.log(val);
}
}

var ff=f(1);
ff();

定义了函数f,传入一个参数,另参数自增,然后返回一个匿名函数,匿名函数用到了外部函数的val对象,自增后输出。

然后我们在window的执行环境中首先调用f,f会返回一个匿名函数,我们把返回的函数赋值给ff,然后f执行完毕,再调用ff。最后发现控制台输出值为3。结果看起来跟我们预想的一样,但是我有个问题想不明白。当第一次调用f函数时候返回的匿名函数给力ff,这时候f函数已经执行完毕,上面提到了,当函数执行完毕的时候会毁掉小屋子,那么既然屋子被毁掉了,那么其中的变量对象(宝箱)也没有了。既然宝箱没有了,其中的val自然也没了,那么当调用ff 的时候val实际上不应该没有了么?为什么还会引用成功?


其实上面的例子就是一个闭包。先说说什么是闭包:闭包是一个有权访问另外一个函数作用域中变量的函数。拆分下来说,闭包是一个函数,但是这个函数有个特点,他的执行环境中可以访问到其他函数执行环境的变量。并且其他环境销毁时候他依然能够访问。

那么闭包函数是怎么做到的呢?实际上闭包函数在创建执行环境时候会把他能访问的函数的作用域中的活动对象添加到自己的作用于链前端。并且当其他函数执行结束后因为他仍然引用其活动对象,因此活动对象会仍然保留在内存中。直到闭包函数执行完毕。

说了这些我想对闭包应该有个初步了解吧。后面再讨论闭包的更多问题。