一、概念
什么是闭包?
根据MDN中定义,闭包是那些能够访问自由变量的函数。
什么是自由变量?
自由变量是指在函数中使用的,但不是函数的参数,也不是函数内部定义的局部变量的变量。
总结:闭包 = 函数 + 函数中能访问的自由变量。
例如:
var a = 100;
function f1() {
console.log(a)
}
f1();
f1 函数可以访问变量 a,但是 a 既不是 f1 函数的局部变量,也不是 f1 函数的参数,所以 a 就是自由变量。
那么,函数 f1 + f1 函数访问的自由变量 a 不就是构成了一个闭包嘛……
没错,就是这样!!!
在《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。
这里跟我们平时遇到的闭包就有点不同了,实际上闭包分为两种概念:
从理论上讲:
所有的函数都可以称之为闭包。因为它们满足上面说的这种概念。
从实践中讲:
函数满足下面条件才是闭包:
- 尽管创建它(自由变量)的上下文已经销毁,但是它仍然存在。
- 在代码中引用了自由变量
二、闭包形成过程
先看来一个例子:
function foo(){
var a = 100;
function bar(){
console.log(a++);
}
return bar;
}
var fn = foo();
fn();
一个简单的闭包例子,fn执行结果为 100。
问题:foo()执行完毕之后,函数上下文就出栈了(销毁),而变量a是存在于执行上下文中的VO变量中的,为什么fn还能访问到这个这个作用域的值?
分析
我们通过之前学的执行上下文知识进行分析上面过程:
- 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
- 全局执行上下文初始化
- 执行 foo 函数,创建 foo 函数执行上下文,foo 执行上下文被压入执行上下文栈
- foo 执行上下文初始化,创建作用域链、变量对象、this等
- foo 函数执行完毕,foo 执行上下文从执行上下文栈中弹出
- 执行 bar 函数,创建 bar 函数执行上下文,bar 执行上下文被压入执行上下文栈
- bar 执行上下文初始化,创建作用域链、变量对象、this等
- bar 函数执行完毕,bar 函数上下文从执行上下文栈中弹出
通过这个过程,我们知道了
bar执行上下文会维护一个作用域链:
barContext = {
Scope: [AO, fooContext.AO, globalContext.VO],
}
没错,foo执行上下文是被销毁了,但是bar内部是有作用域链的。同时我们上节中说到,在函数创建的时候,会copy自己创建的作用域链到当前上下文[[scope]]属性中。
所以,当 bar 函数引用了 fooContext.AO 中的值的时候,即使 fooContext 被销毁了,但是 JavaScript 依然会让 fooContext.AO 活在内存中,bar 函数依然可以通过 bar 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。
例题练习:
// 示例一
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();