概念
闭包是一种特殊的对象。
它由两部分组成:执行上下文(代号A),已经在该执行上下文中创建的函数(代码B)。
当B执行时,如果访问了A中变量对象中值,那么闭包就会产生。
我们只需要知道,一个闭包对象,由A,B共同组成,在以后的文章中,都会将以chrome的标准来称呼。
function foo() {
var a = 20;
var b = 30;
function bar() {
return a + b;
}
return bar;
}
var bar = foo();
bar();
在上面的例子中,首先执行上下文 foo,在foo中定义了函数bar,而后通过对外返回bar的方式让bar得以执行。当时执行时,访问了 foo 内部变量a和b。因此这个时候闭包产生,
在chrome中通过断点调试的方式可以逐步分析该过程,从图中可以看出,此时闭包产生,用foo代替
在图中,箭头所指的正是闭包,其中call stack 为当前的函数调用栈,scope 为当前正在被执行函数的作用域链,Local 为当前活动对象。
现在我们来思考一个小小的问题,把上面的代码调整成这样后,是否形成闭包?
function foo() {
var a = 20;
var b = 30;
function bar() {
return a + b;
}
bar();
}
foo();
还是形成了闭包。
再来看一个例子,非常有意思的例子
function add(x) {
return function _add(y){
return x+y;
}
}
add(2)(3); //5
这个例子当然是产生闭包的,内部函数_add(y)被调用执行的时候,访问了add函数变量对象的中x的值,这个时候,闭包就会产生,如图所示,一定要记住,函数参数中的变量传递给函数之后也会加到变量对象中
还有一个例子可以验证大家对于闭包的理解。
看看下面这个例子是否产生闭包
var name = "window";
var p = {
name: 'Perter',
getName: function() {
return function () {
return this.name;
}
}
}
var getName = p.getName();
var _name = getName();
console.log(_name);
getName在执行时,他的this其实是指向的是window对象,而这个时候并没有形成闭包的环境,因此并没有产生闭包。
那么这个例子改成如下这样呢?
var name = "window";
var p = {
name: 'Perter',
getName: function() {
return function () {
return this.name;
}
}
}
var getName = p.getName();
var _name = getName.call(p);
console.log(_name);
没有产生闭包,
如果再改成这样呢?
var name = "window";
var p = {
name: 'Perter',
getName: function() {
var self = this;
return function () {
return self.name;
}
}
}
var getName = p.getName();
var _name = getName();
console.log(_name);
这样就会产生闭包了
闭包与垃圾回收机制
垃圾回收机制,知道当一个值失去引用之后就会被标记,然后被垃圾回收机制回收并释放空间。
我们知道,当一个函数的执行上下文运行完毕之后,内部的所有内容都会失去引用而被垃圾回收机制回收。
我们还知道,闭包的本质就是在函数外面保持了内部变量的引用,因此闭包会阻止垃圾回收机制进行回收。
我们用一个例子来证明这一点
function f1() {
var n = 999;
nAdd = function() {
n += 1;
}
return function f2(){
console.log(n);
}
}
var result = f1();
result();
nAdd();
result();
从上面的例子可以看出,因为 nAdd ,f2 都可以访问f1中的n,因此他们都会与f1形成了闭包。这个时候变量n都被保留下来了,因为f2(result)与nAdd执行时间都访问了n,而nAdd每运行一次就会将n加1,所以上例的执行结果非常符合我们的认知。
闭包与作用域链
先花几秒钟时间来思考一个小问题,闭包会导致函数的作用域链的变化呢?
var fn=null
function foo() {
var a = 2;
function innerFoo() {
console.log(a);
}
fn = innerFoo; // 将innerFoo 的引用赋值给全局变量中的fn;
function bar() {
fn(); //此处保留innerFoo的引用
}
foo();
bar();
}
在这个例子中,foo内部的innerFoo访问了foo的变量a。因此当innerFoo执行时会有闭包产生,这是一个比较简单的例子,不一样的地方是全局变量fn。fn在foo内部获取了innerFoo的引用,并在bar中执行。
那么innerFoo的作用域链会怎么样呢?
在这里需要特别注意的地方是函数调用栈与作用域链的区别。
因为函数调用栈其实是在代码执行时才确定的,而作用域规则在代码编译阶段就已经确定了,虽然作用域链是在代码执行时才生出的,但是他的规则不会在执行期间发生变化。所以闭包的存在不会影响作用域链的变化。