闭包
- 概念
闭包是一种特殊的对象。它由两部分组成:执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。
当执行B时,如果访问了A中的变量对象中的值,那么闭包就会产生。
有时候以函数B的名字代指这里生成的闭包。而在Chrome中,则以执行上下文A的函数名代指闭包。
只需要知道一个闭包对象,由A、B共同组成即可。
// demo1
function foo() {
var a = 100;
var b = 200;
function bar() {
return a + b;
}
return bar;
}
var bar = foo();
bar();
上面例子中,首先执行上下文foo,在foo中定义了函数bar,而后通过对外返回bar的方式让bar得以执行。当bar执行时,访问了foo内部的变量a和b。因此这个时候闭包产生。
在Chrome中通过断点调试的方式可以逐步分析该过程,此时闭包产生,用foo代指,如下图:
上图中,箭头所指的正是闭包。其中Call Stack为当前的函数执行栈,Scope为当前正在被执行函数的作用域,Local为当前活动对象。
来看一个非常有意思的例子:
// demo2
function add(x) {
return function _add(y) {
return x + y;
}
}
add(2)(3); // 5
上面的例子有闭包产生吗?
当然有。当内部函数_add被调用执行时,访问了add函数变量对象中的x,这个时候,闭包就会产生,如下图,一定要记住,函数参数的变量传递给函数之后也会加到变量对象中。
下面代码会产生闭包吗?
// demo3
var name = "window";
var person = {
name: "perter",
getName: function() {
return function() {
return this.name;
};
}
};
var getName = person.getName();
var _name = getName();
console.log(_name);
getName在执行时,它的this其实指向的是window对象,而这个时候并没有形成闭包的环境,因此这个例子没有闭包。
如果按照下面的方式进行改动呢?
// demo4
// 改动一
var name = "window";
var person = {
name: "perter",
getName: function() {
return function() {
return this.name;
};
}
};
var getName = person.getName();
// 利用call的方式让this指向person对象
var _name = getName.call(person);
console.log(_name);
// demo5
// 改动二
var name = "window";
var person = {
name: "perter",
getName: function() {
// 利用变量保存的方式保证其访问的是person对象
var self = this;
return function() {
return self.name;
};
}
};
var getName = person.getName();
var _name = getName();
console.log(_name);
分别利用call与变量保存的方式保证this指向的都为person对象。所以demo4(由于Chrome已做优化,所以在Chrome调试工具中没有显示闭包)和demo5都产生了闭包。
- 闭包与垃圾回收机制
了解垃圾回收机制原理都知道当一个值失去引用之后就会被标记,然后被垃圾回收机制回收并释放空间。当一个函数的执行上下文运行完毕之后,内部的所有内容都会失去引用而被垃圾回收机制回收。
闭包的本质就是在函数的外部保持了内部变量的引用,因此闭包会阻止垃圾回收机制进行回收
下面用一个例子来证明这一点:
// demo6
function foo1() {
var n = 99;
nAdd = function() {
n += 1;
};
return foo2() {
console.log(n);
};
}
var result = foo1();
result(); // 99
nAdd();
result(); // 100
从上面的例子可以看出,因为nAdd都访问了foo1中的n,因此它们都与foo1形成了闭包。这个时候变量n的引用被保留了下来。因为foo2(result)与nAdd执行时都访问了n,aAdd每运行一次就会将n加1,所以上例的执行结果非常符合我们的认知。
认识到闭包中保存的内容不会被释放之后,我们在使用闭包时就要保持足够的警惕性。如果滥用闭包,很可能会因为内存的原因导致程序性能过差。
- 闭包与作用域链
结合下面的例子思考一下,闭包会导致函数的作用域链发生改变吗?
// demo7
var fn = null;
function foo() {
var a = 2;
function innerFoo() {
console.log(a);
}
fn = innerFoo; // 将innerFoo的引用赋值给全局变量中的fn
}
function bar() {
var a = 3;
fn(); // 此处保留innerFoo的引用
}
foo();
bar(); // 2
在上面的例子中,foo内部的innerFoo访问了foo的变量a。因此当innerFoo执行时会有闭包产生。全局变量fn在foo内部获取了innerFoo的引用,并在bar中执行。
innerFoo断点调试图如下:
在这里需要特别注意的地方是函数调用栈(Call Stack)与作用域链(Scope)的区别。因为函数调用栈其实是在代码执行时才确定的,而作用域规则在代码编译阶段就已经确定,虽然作用域链是在代码执行时才生成的,但是它的规则并不会在执行时发生改变。
所以,闭包的存在并不会导致作用域链发生变化。
参考资料:
JavaScript高级程序设计
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。