概念 函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内
Javascript允许嵌套内部函数—即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
实例:
1 2 3 4 5 6 7 8 9 10 var scope = 'global' ;function checkScope ( ) { var scope = 'local' ; function f ( ) { console .log(scope); } return f; } var func = checkScope();func();
JavaScript中的函数会形成闭包。这个环境包含了这个闭包创建时所能访问的所有局部变量。在我们的例子中,checkScope
返回的是内部函数f
本身,func
是f
函数的引用,而f
仍可访问其词法作用域中的变量,即可以访问到checkScope
作用域内定义的scope
。由此,当func
被调用时,checkScope
中的scope
仍可被访问
闭包可能带来的问题 在循环中创建闭包 在实际开发中可能会遇到一个问题,如何对相似的元素绑定事件,比如:
1 2 3 4 <p id ="help" > Helpful notes will appear here</p > <p > E-mail: <input type ="text" id ="email" name ="email" > </p > <p > Name: <input type ="text" id ="name" name ="name" > </p > <p > Age: <input type ="text" id ="age" name ="age" > </p >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function showHelp (help ) { document .getElementById('help' ).innerHTML = help; } function setupHelp ( ) { var helpText = [ {'id' : 'email' , 'help' : 'Your e-mail address' }, {'id' : 'name' , 'help' : 'Your full name' }, {'id' : 'age' , 'help' : 'Your age (you must be over 16)' } ]; for (var i = 0 ; i < helpText.length; i++) { var item = helpText[i]; document .getElementById(item.id).onfocus = function ( ) { showHelp(item.help); } } } setupHelp();
使用循环为三个相似的input
元素绑定onfoucs
时,会发现所有的提示均为"Your age (you must be over 16)"
,这就是闭包带来的问题——闭包只能取得包含函数中任何变量的最后一个值
将该问题做一个抽象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function f ( ) { var result = []; for (var i = 0 ; i < 5 ; i++) { result[i] = function ( ) { console .log(i); }; } return result; } f()[0 ](); f()[1 ](); f()[2 ](); f()[3 ](); f()[4 ]();
事实上,我们想要的结果应该是f()[i]() == i
。出现全部是5的原因是:当f()
返回后,变量i
的值是5,此时通过循环创建的每个函数result[i]()
都引用保存着同一个变量i
,所有每个函数内部的i
都是5
想要实现f()[i]() == i
有以下几个方法:
使用更多闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 function print (i ) { console .log(i); } function f ( ) { var result = []; for (var i = 0 ; i < 5 ; i++) { result[i] = print(i); } return result; } f();
使用匿名闭包
1 2 3 4 5 6 7 8 9 10 11 function f ( ) { var result = []; for (var i = 0 ; i < 5 ; i++) { result[i] = (function ( ) { console .log(i); })(i) } return result; } f();
ES6中可以使用let
声明i
1 2 3 4 5 6 7 8 9 10 11 function f ( ) { var result = []; for (let i = 0 ; i < 5 ; i++) { result[i] = function ( ) { console .log(i); }; } return result; } f()[2 ]();
性能问题 由于闭包会携带包含它的函数的作用域,会比其他函数占用更多内容,过度使用闭包,会导致内存占用过多,在处理速度和内存消耗方面对脚本性能具有负面影响。因此 如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的
this指向问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var scope = 'global' ;var obj = { scope: 'local' , getScope1: function ( ) { return this .scope; }, getScope2: function ( ) { return function ( ) { return this .scope; }; } } console .log(obj.getScope1()) console .log(obj.getScope2()())
匿名函数的执行环境具有全局性,因此其this
通常指向window
,可以通过把外部作用域中的this
保存在一个闭包能访问的变量中
1 2 3 4 5 6 7 8 9 10 11 12 13 var scope = 'global' ;var obj = { scope: 'local' , getScope2: function ( ) { var self = this ; return function ( ) { return self.scope; }; } } console .log(obj.getScope2()())
内存泄漏 IE9之前的版本对JScript
对象和COM
对象使用不同的垃圾收集例程,故闭包在IE的这些版本中会导致一些特殊的问题
具体来说,如果闭包的作用域链中保存着一个HTML元素,那么该元素无法销毁
1 2 3 4 5 6 function assignHandler ( ) { var element = document .getElementById('elementId' ); element.onclick = function ( ) { alert(element.id); }; }
可以改成:
1 2 3 4 5 6 7 8 9 10 function assignHandler ( ) { var element = document .getElementById('elementId' ); var id = element.id; element.onclick = function ( ) { alert(id); }; element = null ; }
闭包的用途 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
用闭包模拟私有方法 可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问,还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var makeCounter = function ( ) { var privateCounter = 0 ; function changeBy (val ) { privateCounter += val; } return { increment: function ( ) { changeBy(1 ); }, decrement: function ( ) { changeBy(-1 ); }, value: function ( ) { return privateCounter; } } }; var Counter1 = makeCounter(), Counter2 = makeCounter(); console .log(Counter1.value()); Counter1.increment(); Counter1.increment(); console .log(Counter1.value()); Counter1.decrement(); console .log(Counter1.value()); console .log(Counter2.value()); console .log(makeCounter.privateCounter)
两个计数器counter1
和counter2
各自独立性,每个闭包都是引用自己词法作用域内的变量privateCounte
,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量
以这种方式使用闭包,提供了许多与面向对象编程相关的好处——特别是数据隐藏和封装
实现类和继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function Person ( ) { var name; return { getName: function ( ) { return name; }, setName: function (newName ) { name = newName; } } }; var p1 = new Person();p1.setName("Tom" ); console .log(p1.getName()); var Person1 = function ( ) { };Person1.prototype = new Person(); Person1.prototype.Say = function ( ) { console .log('Hello,my name is ' + this .getName()); }; var p2 = new Person1();p2.setName('Eddy' ); p2.Say(); console .log(p2.getName());