这篇算接上一篇:【JavaScript 预编译:函数声明提升,变量声明提升】
说闭包之前我们先介绍几个概念:
当函数执行时(实际上是函数执行前一刻)会创建一个称为执行期上下文的对象(就是上一篇介绍的 Activation Object),一个执行期上下文定义了一个函数执行期间的环境。当函数执行完毕,它所产生的执行期上下文被销毁。
函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文。
每个 JavaScript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以。不可以访问的这些属性仅供 JavaScript 引擎存取,[[scope]] 就是其中一个。
[[scope]] 指的就是我们所说的作用域,其中存储了执行期上下文的集合。
[[scope]] 中所存储的执行期上下文对象的集合呈链式连接,我们把这种链式连接叫做作用域链。查找变量就是从作用域链的顶端依次向下查找。
下面来看一个例子:
function a() { function b() { var b = 234; console.log(b); } var a = 123; b(); console.log(b); } console.log(a); var glob = 100; a();
函数 a 被定义时发生如下过程:(此时 a 在全局的执行环境中,a 的作用域链上只有一个 GO 对象)
其中 Global Object 中还应该有 window,document 等对象,这里暂且不管,下同。函数 a 执行时发生如下过程:(a 执行的前一个会创建一个它自己的执行上下文 AO 对象,并且将这个对象放在作用域链的最顶端)
其中 Activation Object 中还应该有 arguments 等,这里暂且不管,下同。函数 b 被定义时发生的过程跟函数 a 执行时发生的过程是一样的,此时 b 在 a 的执行环境中。(此时 b 的作用域链上的 AO 对象跟 a 的作用域链上 AO 对象是同一个,都是 a 的)
函数 b 执行时发生的过程如下:(b 执行的前一个会创建一个它自己的执行上下文 AO 对象,并且将这个对象放在作用域链的最顶端,其他对象依次下移)
当函数 a 函数 b 执行完成后,他们所产生的执行期上下文全部都会被销毁。
再看一个例子:
function a() { function b() { var bbb = 234; console.log(aaa); } var aaa = 123; return b; } var glob = 100; var demo = a(); demo();
函数 a 被定义时发生如下过程:
函数 a 执行时发生如下过程:
函数 b 被定义时发生的过程跟函数 a 执行时发生的过程是一样的。
函数 demo/b 执行时发生的过程如下:
值得注意的是,在函数 a 执行完成后将函数 b 作为返回值赋给了全局变量 demo。函数 a 执行完成后会销毁自己的执行期上下文,但是被保存到外部的函数 b 的作用域链上还是能够访问到 aaa。所以当 demo 执行的时候也就是 b 执行的时候,能够输出 aaa 的值 123。 这就是我们说的闭包。
当内部函数被保存到外部时,仍然能够访问到原来包含函数内部的变量,就会形成闭包。
闭包会导致原有作用域链不释放,造成内存泄漏。(内存泄漏就是,内存被占用的越来越多,可供使用的越来越少。)
闭包为什么会造成内存泄漏?(这个纯粹是我个人的理解,不知道对不对)
js 垃圾收集机制的原理是找出那些不再继续使用的变量,释放其占用的内存。
按照上面的例子来说,函数 b 执行完成之后本应该释放其作用域链,但是由于函数 b 总是被 变量 demo 引用,所以 js 就认为函数 b 一直是有用的,一直不回收它。因此造成了内存泄漏。
- 实现公有变量;eg:函数累加器
- 可以做缓存(存储结构);eg:eater
- 可以实现封装(属性私有化);eg:Person
- 模块化开发(防止污染全局变量)
前两个作用对应的例子:
eg:函数累加器
function add() { var count = 0; function fnCount() { count++; console.log(count); } return fnCount; } var counter = add(); counter(); counter(); counter(); counter(); counter(); ... 或者 var counter = null; function add() { var count = 0; counter = function() { count++; console.log(count); } } add(); counter(); counter(); counter(); counter(); counter(); ...
eg:eater
function eater() { var food = ''; var handFood = { eating: function() { if(food){ console.log('I am eating ' + food); } else{ console.log('Nothing to eat!'); } }, pushFood: function(myfood) { food = myfood; } }; return handFood; } var myEater = eater(); myEater.eating(); myEater.pushFood('banana'); myEater.eating();