这篇文章,是之前在 javascriptissexy 这个网站看到的。通读下来觉得没有遇到太多的障碍,作者介绍的闭包,非常容易让人理解。这里做了翻译,有些地方并不会直接翻译,要加入我自己的一点理解,翻译不当的地方,还请支出~ 下面我们进入正文
闭包能够让JavaScript开发人员写出更优雅的代码。它既有创造力,生动却又简洁。我们经常在JavaScript中使用闭包。抛开你的JavaScript经验不谈,你肯定遇到它们很多次了。可以肯定的是,闭包可能看起来比较复杂,甚至超出你的理解范围。但是读完了这篇文章,你会发现闭包会变得更加易于理解,甚至使得你在日常的JavaScript编程中,更加乐于使用闭包。
这是一篇相对较短的文章,介绍了JavaScript中闭包的一些细节。在你深入阅读之前,你应该对 JavaScript变量作用域非常熟悉,因为要理解闭包,你必须理解JavaScript的变量作用域。
什么是闭包
闭包是一个内部的函数,因为作用域链的原因,它能够访问到外部函数(一个独立的函数)的变量。闭包通常具有三种作用域链,分别是:它能够访问自己的作用域(比如定义在它自己函数体内部的变量),它还能访问外部函数的变量,最后它也能访问到全局变量。
内部的函数不仅能访问到外层函数的变量,还能访问到外层函数的参数。需要注意的是,尽管内部函数能够直接访问外层函数的参数,但它不能调用外层函数的 arguments 对象。
你可以通过在一个函数的内部添加另一个函数的方式来创造一个闭包。
JavaScript中闭包的一个基本的例子
function showName( firstName, lastName ) {
var nameIntro = "Your name is ";
// 这个内部函数能够访问外层函数变量,包括参数
function makeFullName() {
return nameIntro + firstName + " " + lastName;
}
return makeFullName();
}
// Your name is Michael Jackson
showName( "Michael", "Jackson" );
闭包在Node.js 中使用的非常广泛;它们在Node.js那种异步,非阻塞的机制中,到处可见。闭包在JQuery中被频繁的使用,甚至关乎到你阅读到的每一行JavaScript代码。(这里明白作者的意思,但翻译过来有点表达不清。读到最后关于Jquery这里,觉得作者说的有点过了,不过想想,jquery中,常见的事件绑定,ajax 用法,又觉得比较合理了。)
一个经典的jQuery闭包的例子
$( function() {
var selections = [];
$(".niners").click( function() { //这个函数可以访问变量 selections
// 函数内部还可以对selections变量进行更新
selections.push( this.prop( "name" ) );
} );
} );
闭包的规则以及一些副作用
-
及时外层函数返回了(return),闭包也能访问到外层函数的变量:
闭包的一个比较棘手而且重要的特征就是及时外层函数已经返回了,它仍然嫩巩固访问到外层函数的变量。是的,你没看错。在JavaScript中,当函数执行的时候,内部和外层函数使用的是相同的作用域链。这个作用域链当函数建立的时候,就已经生效了(这句翻译可能有点问题)。这就意味着,内部函数能在外层函数返回的情况下,依旧访问到它的变量。最终,你可以在后面编程的过程中,调用到这个内部的函数。这个例子可以这样来看:
function celebrityName( firstName ) { var nameIntro = "This celebrity is "; // 这个内部函数能够访问外层函数变量,包括参数 function lastName( theLastName ) { return nameIntro + firstName + " " + the LastName; } return lastName; } // 到这一步的时候,外层函数celebrity 以及返回了 var mjName = celebrity( "Michael" ); // 上面的外层函数返回之后,内部闭包函数这里被调用了 // 这里,闭包仍然能够访问到外层函数的变量和参数 mjName("Jackson");// This celebrity is Michael Jackson
-
闭包保存了外层函数中变量的引用;
它并没有保存真实的值。当外层函数的变量在闭包被调用之前改变的时候,闭包会变得很有意思。这个强大的特征可以被多种创造性的方式所使用,比如曾经被 Douglas Crockford (大神的名字,感觉直译就失去了一点神的意味,我就不翻译了~ 膜拜大神)首先提出的私有变量的例子
// 我们这里返回了一个包含一些内部函数的对象
// 所有内部函数都能访问到外层的函数
function celebrityID() {
var celebrityID = 999;
return {
getID: function() {
// 这个函数会返回原始的celebrityID
return celebrityID;
},
setID: function( theNewID ) {
// 这个内部函数能够在任何时候改变外部函数变量
celebrityID = theNewID;
}
}
}
var mjID = celebrityID(); // 这一步celebrityID 外层函数被返回
mjID.getID(); // 999
mjID.setID( 567 ); // 改变了外层函数变量
mjID.getID(); // 567 返回了更新的 celebrityID值
- 闭包出错
因为闭包能够访问到外层函数更新之后的值,所以它也会导致一些外层函数内部变量因为for 循环改变导致的一些 bug。比如:
function celebrityIDCreator( theCelebrities ) {
var i;
var uniqueID = 100;
for( i = 0; i < theCelebrities.length; i++ ) {
theCelebrities[i]["id"] = function() {
return uniqueID + i;
}
}
return theCelebrities;
}
var actionCelebs = [
{
name:"Stallone",
id:0
},{
name:"Cruise",
id:0
},{
name:"Willis",
id:0
}
];
var createIdForActionCelebs = celebrityIDCreator( actionCelebs );
var stalloneID = createIdForActionCelebs[0];
console.log(stalloneID.id());//103
在这个例子中,当匿名函数被调用的时候,i 的值是3(数组的长度,这个值在增加)。3这个值就被加到uniqueID
得到了 103, 针对所有的 celebrityID。所以每一个位置的而数组返回值都是103,而不是100,101,102。
发生这种情况,原因在于,正如我们上个例子中论述过的,闭包(上个例子中的匿名函数)能够通过引用而不是值,访问到外层
函数的变量。所以正如这个例子,我们能够得到闭包更新的变量值。这个例子同样当i 改变的时候访问到 i 变量,当外层函
数运行完了整个的for 循环,并返回了最终的 i,就得到了 103。
为了修复这个闭包带来的副作用,我们可以使用一个立即执行的函数(Immediately Invoked Function Expression),如下
function celebrityIDCreator( theCelebrities ) {
var i;
var uniqueID = 100;
for( i = 0; i < theCelebrities.length; i++ ) {
theCelebrities[i]["id"] = function( j ) {
return function() {
return uniqueID + j;
}()
}( i );
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id); // 100
var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id); // 101