原文:JavaScript闭包的一些理解
简单一点的说:闭包就是能够读取其他函数内部变量的函数。那如何实现读取其它函数内部变量呢,大家都知道在JavaScript中内部函数可以访问其父函数中的变量,那如果将内部函数返回是不是代表能够通过它访问其父函数中的变量了呢,闭包的原理事实上就是这样。
摘录
闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
闭包的主要作用:
- 可以读取函数内部的变量
- 让函数内部的变量值始终保持在内存中
使用闭包应该注意的地方:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包的一些例子
下面来看闭包相关的一些例子,希望从demo中能够深入理解一下JavaScript中的闭包。
function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc();
在上面的这个demo中,displayName事实上就是一个闭包,我们知道在全局范围内是根本没法访问makeFunc函数中的局部变量name,但是通过displayName这个闭包通过访问makeFunc函数中的局部变量name,这也就是为什么执行myFunc函数时弹出的是Mozilla
下面来看一个有趣的例子。
function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12
下面的这个例子可能大家一看就知道答案了,因为闭包函数increment、decrement、value可以访问privateCounter,所以答案不言而喻了。
var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); alert(counter.value()); /* Alerts 0 */ counter.increment(); counter.increment(); alert(counter.value()); /* Alerts 2 */ counter.decrement(); alert(counter.value()); /* Alerts 1 */
下面的这个例子也不难,因为makeCounter返回的是一个对象,所以每个对象内部变量privateCounter不同,所以下面的例子中对象counter1 和对象counter2能够访问到的privateCounter各占不同的内存空间。
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(); var counter2 = makeCounter(); alert(counter1.value()); /* Alerts 0 */ counter1.increment(); counter1.increment(); alert(counter1.value()); /* Alerts 2 */ counter1.decrement(); alert(counter1.value()); /* Alerts 1 */ alert(counter2.value()); /* Alerts 0 */
再来看一个典型的闭包的例子
假设页面上的html元素如下代码所示:
<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>
然后我们的JS代码如下所示:
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();
本来我们希望
- 在email文本框中focus的时候,help元素中显示的是Your e-mail address
- 在name文本框中focus的时候,help元素中显示的是Your full name
- 在age文本框中focus的时候,help元素中显示的是Your age (you must be over 16)
结果却发现不管哪个文本框focus,help元素始终显示的是Your age (you must be over 16) 这事实上就是闭包的一个特性,它始终访问的是局部变量的最终值。
所以你应该用下方的代码来实现你预期的效果
function showHelp(help) { document.getElementById('help').innerHTML = help; } function makeHelpCallback(help) { return function () { showHelp(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 = makeHelpCallback(item.help); } } setupHelp();
上面的这个示例使用了一个闭包来保存函数局部变量的值,相信你呆会看了下方的示例,肯定会马上明白的。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());//The Window this为全局对象,所以alert处理的name为The window
this的指向是由它所在函数调用的上下文决定的,而不是由它所在函数定义的上下文决定的。因为闭包最后的返回值是一个函数,注意紧紧是一个函数而已 并没有执行,等到alert调用时才执行,而这时执行调用的方法this所指向的变量为window.
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this;//注意这里用局部变量that保存this的值 that 为object对象,所以alert 处理的name为My object return function(){ return that.name; }; } }; alert(object.getNameFunc()());//注意这里是有两个() //弹出My Object
如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下);(上述弹出The window的示例中getNameFunc就是作为函数调用)
如果嵌套函数作为方法调用,其this值指向调用它的对象。(这个例子中弹出My Object变是作为方法调用)
关于Prototype的小示例
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }
在上述这个例子中,大家都明白如果你声明了100个MyObject对象的话就会用100个name、message、getName、getMessage在内存中,100个name、message属性这个无可厚非,但是100个getName、getMessage方法未免就有些浪费资源的,因为事实上所有对象是可以共享方法来实现节省资源。这样你可能会想到用如下代码来声明MyObject对象
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype = { getName: function() { return this.name; }, getMessage: function() { return this.message; } };
但我只能说这样的代码我实在是无法恭维,原因是重写的prototype,大家都知道重写的prototype带来的坏处,最简洁的一句话来形容坏处就是如果你之前给MyObject所作的一切建设都得重新开始。
所以推荐使用下面的示例,但是下面的这个示例可能习惯于写服务器端编程语言的人会很看不惯其风格(哈哈,这个你想其它办法......)
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };
参考文献为阮大师的 学习Javascript闭包(Closure)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
人都是有惰性的,其实自己的英文水平还可以就是懒得去提升,现在才发现原来看英文博客其实挺好的。
建议大家有空提升一下自己的英文水平,事实上国外一些技术大牛们写的博客确实挺不错的。如果大家有前端好的英文博文可以推荐一下给我。