说起 闭包 这个词,其实没有一个非常明确的概念。
官方的解释:
一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。闭包的特点:
- 作为一个函数变量的一个引用,当函数返回时,其处于激活状态
- 一个闭包就是当一个函数返回时,一个没有释放资源的栈区
看起来有些抽象,说白了可以这样理解:闭包 就是有权访问另一个函数作用域中变量的函数
即使这么说,还是难以理解,这方面必须先从 JavaScript
的作用域说起:
作用域链
先来看一个例子:
var foo = "Hello";
function f() {
console.log(foo);
var foo = "I am li";
console.log(foo);
}
f();
console.log(foo);
console.log(foo1);
看完之后,我说下答案。从上往下依次为:”undefined” , “I am li”, “Hello” , “JS报错”
后三个输出的值都可以理解,但是第一个呢?不是声明了一个全局变量 foo
吗?这就是标识符在作用域链中的寻找,是这样的:
JS是没有块级作用域的,但有执行环境。当创建第一个 foo
变量时,首先在全局的执行环境创建一个变量对象,并把 foo
变量放入这个对象中。然后创建函数 f1
,此时创建函数自己的执行环境,并把函数中的变量(包括 arguments
对象和内部对象)放入一个活动对象中。其实函数自己的执行环境和全局执行环境是有联系的,当函数被创建时, 会拷贝全局执行环境的变量对象。当函数内部变量被使用时,现在函数的活动对象中需找有没有这个变量的定义。如果没有,再向上一级需找,依次向上,直到全局环境中的对象。这就是所谓的 作用域链 。上面例子的图解如下:
这就好解释为什么第一个输出为 undefined
原因了:首先在函数的执行环境中寻找,找到了 foo
的标识符,然后就不继找下去了,foo
当然是 undefined
下面看闭包:
闭包
还是先来看一个例子:
function f1() {
var i = 0;
return function () {
i++;
console.log(i);
}
}
var f3 = f1();
f3();
f3();
这应该是最简单的一个闭包了:一个函数内部定义了另一个函数然后返回。按照我们习惯的理解,两个 f3
被调用的结果都应该是 1
吧。事与愿违,第一个输出是 1
,第二个输出是 2
,如果继续调用下去, i
的值会依次加下去。
上面已经说过作用域链了,函数的执行环境被创建时,[[scope]]
是一个函数的内部属性,实际上拷贝了外部作用域的环境对象,然后函数内部的活动对象被创建。这时候,函数的作用域链是 活动对象 + [[scope]]保存的对象
看一个例子:
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
console.log(x + y + z);
}
return bar();
};
foo();
从全局说起, 全局变量 x
创建后,被加入作用域链 [scope chain]
此时的全局变量对象(称之为 VO
)是 :
VO = {
x: 10,
foo: <function>
}
作用域链 [[scope chain]]
为:
[scope chain] = {
VO = {...}
}
函数 foo
被创建时,[[scope]]
自动创建, foo
的 [[scope]]
为:
[[scope]] = {
VO = {...}
}
看出来了吧,[[scope]]
拷贝的是外部作用域的 `变量对象/活动对象
当函数被激活时,即foo
被调用时,foo
的活动对象(称之为 AO1
)为:
AO1 = {
y: 20,
bar: <function>
}
此时的作用域链应为:
[scope chain] = {
VO = {...},
AO1 = {...}
}
同理,函数函数 bar
被创建时,bar
的 [[scope]]
为:
[[scope]] = {
VO: {...},
AO1: {...}
}
当函数 bar
被调用时,bar
的活动对象(称之为 AO2
)为:
AO2 = {
z: 30
}
此时的作用域链为:
[scope chain] = {
VO: {...},
AO1: {...},
AO2: {...}
}
这就是上面例子执行的全过程!
图解如下:
可以看到,函数的 [[scope]]
引用着外层函数的 [[scope]]
,外层函数的 [[scope]]
引用着全局的 VO
变量对象。于是,当内层函数不被销毁时,始终引用着外层作用域的 变量对象/[[scope]]
,这就是闭包的原理。
好了,大概我的理解就是这些。额,这么晚了,该洗洗睡了<( ̄ˇ ̄)/