闭包是一种特殊的对象。它由两部分组成:执行上下文(代号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代指,以下图:this
上图中,箭头所指的正是闭包。其中Call Stack为当前的函数执行栈,Scope为当前正在被执行函数的做用域,Local为当前活动对象。spa
来看一个很是有意思的例子:设计
// demo2 function add(x) { return function _add(y) { return x + y; } } add(2)(3); // 5
上面的例子有闭包产生吗?
固然有。当内部函数_add被调用执行时,访问了add函数变量对象中的x,这个时候,闭包就会产生,以下图,必定要记住,函数参数的变量传递给函数以后也会加到变量对象中。调试
下面代码会产生闭包吗?code
// 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高级程序设计