1. 程式人生 > >JavaScript 閉包(Closure)

JavaScript 閉包(Closure)

Closure 閉包

閉包是函式和宣告該函式的詞法環境的組合.類似於c#中的委託,或者說是一個棧幀,在函式開始是被分配到堆,當函式返回後仍然不會被釋放.

Profile

  1. 在function中使用另一個function, 就會使用閉包.但是構造器函式New Function()不會使用閉包
  2. 閉包可以視作一個函式的入口以及與此函式的相關的區域性變數(函式退出時的區域性變數的副本)的組合
  3. 每次呼叫有閉包的函式, 都會保留一組新的區域性變數(如果函式包含一個內部函式, 並且內部函式的引用被返回或以某種方式保留)
  4. 因為隱祕的閉包, 兩個函式看起來可能有相同的原始碼, 但實際的作用完全不同
  5. 閉包在處理速度和記憶體消耗方面對指令碼有負面影響
  6. 閉包可用來構建JavaScript私有成員

用途

  1. 訪問變數(在變數的作用域內,多在外部函式中宣告),被賦值給一個變數,
  2. 作為函式的實參被傳遞,
  3. 作為函式結果被返回.
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();

閉包是什麼:與指標的對比

  1. C指標不同的是,JavaScript中函式引用變數不僅指向函式,還包括隱藏的指標指向閉包,一塊兒分配在堆上的函式所處上下文環境的記憶體
  2. 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

閉包按引用儲存

  1. 同一個閉包可以同時被多個函式所訪問, setupSomeGlobals()中的區域性變數可以同時被3個方法呼叫
  2. 但是, 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