JavaScript 閉包(Closure)
阿新 • • 發佈:2018-11-16
Closure 閉包
閉包是函式和宣告該函式的詞法環境的組合.類似於c#中的委託,或者說是一個棧幀,在函式開始是被分配到堆,當函式返回後仍然不會被釋放.
Profile
- 在function中使用另一個function, 就會使用閉包.但是構造器函式New Function()不會使用閉包
- 閉包可以視作一個函式的入口以及與此函式的相關的區域性變數(函式退出時的區域性變數的副本)的組合
- 每次呼叫有閉包的函式, 都會保留一組新的區域性變數(如果函式包含一個內部函式, 並且內部函式的引用被返回或以某種方式保留)
- 因為隱祕的閉包, 兩個函式看起來可能有相同的原始碼, 但實際的作用完全不同
- 閉包在處理速度和記憶體消耗方面對指令碼有負面影響
- 閉包可用來構建JavaScript私有成員
用途
- 訪問變數(在變數的作用域內,多在外部函式中宣告),被賦值給一個變數,
- 作為函式的實參被傳遞,
- 作為函式結果被返回.
function sayHello(name) {
var text = 'Hello ' + name; // Local variable
var say = function() { console.log(text); }
say();
}
sayHello('Joe');//Hello Joe
函式作為引用被返回
function sayHello2(name) {
var text = 'Hello2 ' + name; // Local variable
return function() { console.log(text); }
}
sayHello2('Joe')();//Hello2 Joe 等價於 var say2 = sayHello2('Joe');say2();
閉包是什麼:與指標的對比
- C指標不同的是,JavaScript中函式引用變數不僅指向函式,還包括隱藏的指標指向閉包,一塊兒分配在堆上的函式所處上下文環境的記憶體
- C等其他語言中在函式返回後,因為棧幀被銷燬,其區域性變數就不再可訪問.相對的,JavaScript中在函式中宣告一個函式,即使呼叫的函式被返回,外部函式中的區域性變數仍舊可用.
sayHello2('Joe')()中的text是區域性變數,匿名方法可以訪問text,就是因為sayHello2()仍舊儲存在一個閉包中.JavaScript中函式引用有一個對它的閉包的祕密引用,類似於委託,方法指標加上一個對物件的引用.
它們按引用儲存.外部函式退出時,棧幀仍儲存在記憶體中
function sayHello3() {
// Local variable that ends up within closure
var num = 42;
var say = function() { console.log(num); }
num++;
return say;
}
var sayNumber = sayHello3();
sayNumber(); // logs 43
閉包按引用儲存
- 同一個閉包可以同時被多個函式所訪問, setupSomeGlobals()中的區域性變數可以同時被3個方法呼叫
- 但是, setupSomeGlobals一旦被重新呼叫, 新的閉包就會被建立, 堆上一塊新的記憶體被分配.
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 42;
// Store some references to functions as global variables
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();gIncreaseNumber();
gLogNumber(); //43
gSetNumber(6);
gLogNumber();//6
var oldLog = gLogNumber; setupSomeGlobals();
gLogNumber(); //42
oldLog();//6
進階: 共享一個閉包的函式陣列
buildList返回的是根據list的個數生成的方法陣列, 共享一個閉包, 也包括其區域性變數i
當fnlist[j]()
呼叫匿名函式(function(){console.log(item + '_' + i +'_'+ list[i])})
時, 都指向同一閉包
此時var宣告的索引變數i作用域是整個for迴圈體, 因為for迴圈已經遍歷完成, 匿名函式對應的區域性變數都變成了3
而let宣告的變數k的作用域是匿名函式, 不同的遍歷階段, 匿名函式中的k值是不一致的
//var
function buildList(list){
var result = [];
for(var i=0; i<list.length; i++){
var item = 'item' + i;
result.push(function(){console.log(item + '_' + i +'_'+ list[i])});
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() // 列印3次 item2_3_undefined
//let
function buildList(list){
var result = [];
for(let k=0; k<list.length; k++){
let item = 'item' + k;
result.push(function(){console.log(item + '_' + k +'_'+ list[k])});
}
return result;
}
testList(); //item0_0_1 \n item1_1_2 \n item2_2_3