轉(zhuǎn)自知乎Rachel
什么是閉包
關(guān)于為什么在JavaScript中閉包得應(yīng)用都有關(guān)鍵詞"return",引用JavaScript秘密花園中的一段話:
閉包是JavaScript一個(gè)非常重要得特性辽剧,這意味著當(dāng)前作用域總是能夠訪問外部作用域中的變量霞怀。因?yàn)?函數(shù) 是JavaScript中唯一擁有自身作用域的結(jié)構(gòu),因此閉包的創(chuàng)建依賴于函數(shù)
閉包是一個(gè)內(nèi)部函數(shù),可以訪問外部(封閉)函數(shù)的變量范圍鏈劫樟。閉包有三個(gè)范圍鏈:它可以訪問自己的范圍(在大括號(hào)之間定義的變量),它可以訪問外部函數(shù)的變量织堂,并且它可以訪問全局變量
內(nèi)部函數(shù)不僅可以訪問外部函數(shù)的變量叠艳,還可以訪問外部函數(shù)的參數(shù),需要注意的是:即使可以直接調(diào)用外部函數(shù)的參數(shù)易阳,內(nèi)部函數(shù)仍然不能調(diào)用外部函數(shù)的參數(shù)對(duì)象附较。
通過在另一個(gè)函數(shù)中添加一個(gè)函數(shù)來創(chuàng)建一個(gè)閉包。
javaScript 閉包的基本示例:
function showName(firstName,lastName){
var nameIntro = "Your name is "
// 這個(gè)內(nèi)部函數(shù)可以訪問外部函數(shù)的變量潦俺,包括參數(shù)
function makeFullName(){
return nameIntro + firstName + " " + lastName
}
return makeFullName()
}
showName("Michael","Jackson")
閉包在 Node.js 中廣泛使用拒课;它們是 Node.js 的異步,非阻塞構(gòu)架中的主軸事示。閉包也經(jīng)常用在 jQuery 和 javaScript 代碼中早像。
閉包的經(jīng)典 jQuery 示例:
$(function(){
var selections = []
$(".niners").click(function(){
selections.push(this.prop("name"))
})
})
閉包規(guī)則和副作用
- 閉包可以訪問外部函數(shù)的變量:
閉包最顯著的特征就是閉包可以訪問到外部函數(shù)變量,即使外部函數(shù)變量已經(jīng)被返回很魂。
JavaScript 中的函數(shù)執(zhí)行都是相同的作用域鏈(函數(shù)可以從內(nèi)往外訪問)扎酷。因此,您可以稍后在程序中調(diào)用內(nèi)部函數(shù)遏匆。此示例演示:
function celebrityName(firstName) {
var nameIntro = "This celebrity is"
// 這個(gè)內(nèi)部函數(shù)可以訪問外部函數(shù)的變量法挨,包括參數(shù)
function lastName(theLastName) {
return nameIntro + firstName + " " + theLastName
}
return lastName
}
var mjName = celebrityName('Michael')
//此時(shí)celebrityName外部函數(shù)返回
//閉包(lastName)在外部函數(shù)返回后調(diào)用
//然而,閉包仍然可以訪問到外部函數(shù)的變量和參數(shù)mjName("Jackson")
2.閉包引用外部函數(shù)的變量存儲(chǔ)幅聘,不存儲(chǔ)實(shí)際值凡纳。當(dāng)外部函數(shù)的變量的值在調(diào)用閉包之前改變時(shí),閉包變得更有趣帝蒿。這個(gè)強(qiáng)大的功能可以創(chuàng)造性的利用荐糜,看如下代碼:
function celebrityID() {
var celebrityID = 999;
//用內(nèi)部函數(shù)返回一個(gè)對(duì)象
//所有的內(nèi)部函數(shù)可以訪問外部函數(shù)的變量
return {
getID: function () {
//這個(gè)內(nèi)部函數(shù)將返回更新celebrityID變量
return celebrityID;
//它將返回celebrityID的當(dāng)前值,即使changeTheID函數(shù)更改
},
setID: function (theNewID) {
//這個(gè)內(nèi)部函數(shù)會(huì)隨時(shí)改變外部函數(shù)的變量
celebrityID = theNewID;
}
}
}
var mjID = celebrityID();
// 此時(shí)葛超,該celebrityID外部函數(shù)返回暴氏。
mjID.getID();
// 999? mjID.setID(567);
// 改變外部函數(shù)變量
mjID.getID(); // 567
3.閉包緩存
由于閉包可以訪問外部函數(shù)的變量的更新值,當(dāng)用一個(gè)for循環(huán)改變外部函數(shù)的變量時(shí)绣张,也可能導(dǎo)致錯(cuò)誤答渔。從而:
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
在前面的例子中,在調(diào)用匿名函數(shù)時(shí)侥涵,i的值未3(數(shù)組的長(zhǎng)度沼撕,然后遞增)宋雏。數(shù)字3已添加到uniqueID以為所有名人ID創(chuàng)建103.因此返回的數(shù)組中的每個(gè)位置都得到id = 103,而不是預(yù)期的100务豺,101磨总,102
發(fā)生這種情況的原因是,正如我們之前例子中討論的笼沥,閉包(本例中的匿名函數(shù))可以通過參數(shù)訪問外部函數(shù)的變量蚪燕,而不是一個(gè)值。因此敬拓,正如前面的例子所示邻薯,我們可以使用閉包訪問更新的變量,這個(gè)例子類似地訪問i變量改變時(shí)乘凸,因?yàn)橥獠亢瘮?shù)運(yùn)行整個(gè)for循環(huán)并返回i的最后一個(gè)值厕诡,即103
要解決在閉包這個(gè)副作用(錯(cuò)誤),你可以使用一個(gè)立即調(diào)用函數(shù)表達(dá)式:
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function (j) { //參數(shù)j是i通過這個(gè)IIFE調(diào)用?
return function () {
return uniqueID + j;
//for循環(huán)的每個(gè)迭代通過i的當(dāng)前值到這個(gè)IIFE并且保存當(dāng)前值到數(shù)組?
} () //通過這個(gè)函數(shù)的末尾的()营勤,我們將立即執(zhí)行灵嫌,
并返回UNIQUEID + j的值,而不是返回一個(gè)函數(shù)葛作。?
} (i); //立即調(diào)用函數(shù)傳遞變量i作為參數(shù)?
}
? 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
或者使用ES6的內(nèi)容定義循環(huán)中的i
function celebrityIDCreator(theCelebrities) {
var uniqueID = 100;
for (let 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); // 100?
?
?var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id);// 101