今天就让我们从JavaScript闭包的名称来源、本质、作用等几方面简洁明了地理清闭包的概念。
从一个例子开始
闭包的世界从不缺乏经典的编程实例,下面就是笔者从网友们那里摘抄下来的一个经典例子。
function add () {
var x = 1;
return function () { console.log(++x); };
}
var num = add();
num(); // 2
num(); // 3
num(); // 4
我们从阅读理解的角度对上面这段代码进行分析:
- 首先,声明一个函数
add
- 函数
add
返回一个匿名函数 - 调用函数
add
- 匿名函数被返回
先看到这里,然后我们可以有以下发现:
- 函数
add
执行时,匿名函数只是被返回,却没有被执行,这说明这个匿名函数有可能等会才执行 - 这个等会再执行的匿名函数,是定义在函数
add
中的
继续阅读上面的代码:
- 将返回的匿名函数赋值给
num
- 连续三次调用函数
num
- 函数
add
中的局部变量自增变化
从中又可以有以下发现:
- 一个在“另一个函数”
add
内部定义的、“另一个函数”add
执行时它不会立刻执行的函数num
,当它执行的时候,依旧可以访问到“另一个函数”add
中定义的变量x
。
我们先给出闭包定义:
一个在函数(局部作用域)中定义的、异步执行的函数,叫做闭包。
之所以叫“闭包”,是因为从代码上看,它被一对花括号闭合包裹在一个局部作用域(函数)中。
其实,我们也可以把上面提到的“局部作用域”称作闭包,因为是它在“闭合包裹”。不管是哪种解释,都只是一个切入点,本文采用了第一种定义。
闭包为什么存在
我们刚刚从词法上分析了闭包的含义,也知道了为什么叫闭包。其中提到有关闭包一个非常重要的特点:闭包是异步执行的。Web开发主要是事件驱动编程,以浏览器环境为例,JavaScript主要用于处理与用户和浏览器的交互操作,需要编写各种不同事件的处理程序,而事件的处理程序都是异步的。JavaScript的应用环境要求这门语言能够很好的处理异步执行。即使一个异步程序定义在局部作用域下,但是只要我们可以调用到它,这个异步程序就应该不出问题的执行。为了让它执行不出错,它所处的局部作用域中的变量对象就必须一直保持在内存中,以便可以随时访问到。所以,我们可以认为:闭包,是JavaScript语言的一种机制。
另一个经典的例子
for (var i = 0; i < 5; ++i) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 100 * i);
}(i));
}
// 控制台依次打印出
// 0
// 1
// 2
// 3
// 4
简要分析如下:
- 从
i
为0开始执行循环 - 循环体是一个立刻执行函数,形成一个局部作用域0
-
局部作用域0下的局部变量
i
为0 - 局部作用域0下,有一个异步执行的匿名函数0(延迟0ms后执行)
- 执行下一次循环,
i
为1 - 一个立刻执行函数,形成一个局部作用域1
-
局部作用域1下的局部变量
i
为1 - 局部作用域1下,有一个异步执行的匿名函数1(延迟100ms后执行)
- ......依次类推
- 循环执行完毕后,
setTimeout
异步程序开始执行 - 匿名函数0被调用,打印的值从局部作用域0中获得,即为0
- ......依次类推
- 从而依次打印出 0 1 2 3 4
网友评论