Skip to main content

闭包的形

JS 的文章中有很大一部分都是在讨论 JS 的闭包,闭包的概念在 JS 中确实是比较基础的,是你进阶深入了解 JS 的“通行证”,理解闭包,能让你的 JS 更上一层楼。

首先,我们得知道,到底什么样的代码我们可以称之为“闭包”?其次,闭包存在的价值是什么?要想理解闭包的价值得知道闭包产生的本质原因是什么,这也就涉及到 JS 引擎的垃圾回收(GC)机制。

取其神,忘其形。

什么是闭包?

是函数 A 里面嵌套函数 B,而 B 又引用 A 中的变量,C 又在引用 B,导致 C 间接引用 A,导致 A 不会被回收,这样的情形是闭包?

还是说,只要该函数被其他地方引用,内存不会被回收就是闭包?

Chrome 中观察下面闭包:

function init() {
var foo = 'bar';
document.addEventListener('click', function () {
foo = 'baz';
});
}

init 执行完,显然事件回调函数不能销毁,因为不然绑定事件不是白绑定了嘛?

要保证事件回调函数能正常执行,就不能销毁,它不能销毁,foo 也不能销毁,foo 不能销毁就要存(包)起来不让销毁,JS 引擎就把 foo 存到了一个这个函数能访问到,但这个函数定义的域外无法访问的区域,这个区域就叫“闭包”。

闭包的形式是函数,函数的本质是作用域,故闭包的本质也是作用域。在 chrome 中也是放在 scope 里面。

其实,闭包无处不在,比如:jQuery、zepto 的核心代码都包含在一个大的闭包中,所以下面我先写一个最简单最原始的闭包,以便让你在大脑里产生闭包的画面:

function A() {
var str = 'Hello Closure!';
function B() {
console.log(str);
}
return B;
}
var C = A();
C(); //Hello Closure!

这是最简单的闭包。

有了初步认识后,我们简单分析一下它和普通函数有什么不同,上面代码翻译成自然语言如下:

  1. 定义普通函数 A
  2. 在 A 中定义普通函数 B
  3. 在 A 中返回 B
  4. 执行 A, 并把 A 的返回结果赋值给变量 C
  5. 执行 C

把这 5 步操作总结成一句话就是:

函数A的内部函数B被函数A外的一个变量 C 引用。

把这句话再加工一下就变成了闭包的定义:

当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。

因此,当你执行上述 5 步操作时,就已经定义了一个闭包!

这就是闭包。

另外要注意的是,最后不一定要 return,只要调用内部函数也会产生闭包: