閉包總結(2018.03.19)

weixin_33830216發表於2018-03-19

什麼是閉包?

簡單講,閉包就是指有權訪問另一個函式作用域中的變數的函式。

不好理解?看下面一個簡單的例子:

function func() {
    var a = 1, b = 2;
    function closure() {
         return a + b
    }
    return closure
}
複製程式碼

上述程式碼中的closure就是一個閉包,是不是有點感覺了?

接下來再看下下面這句話:

通常,函式的作用域及其所有變數都會在函式執行結束後被銷燬。但是,在建立了一個閉包以後,這個函式的作用域就會一直儲存到閉包不存在為止。

有點暈?我們一點點來看:

function addNum() {
    var a = 1, b = 2;
    console.log("addNum->a", a);
    console.log("addNum->b", b);
    return (a + b);
}

addNum();
console.log("handle->a", a);
console.log("handle->b", b);
複製程式碼

執行結果如上圖,在函式執行結束後,作用域和變數立即被銷燬。

如果有閉包存在:

function addNum(x) {
    return function(y) {
        return x + y;
    }
}

var handleAddNum = addNum(5); 
console.log(handleAddNum(2));  // 7
console.log(handleAddNum(3));  // 8
複製程式碼

結果和上個例子是不是有所不同,按理說,addNum被執行結束後,x就應該別銷燬,但是因為有閉包的存在,x的作用域被繼續保留了,想要銷燬x,就要銷燬閉包。

handleAddNum = null;
複製程式碼

下面在看下一個比較經典的閉包問題:

function test(){
  var arr = [];
  for(var i = 0;i < 10;i++){
    arr[i] = function(){
      return i;
    };
  }
  for(var a = 0;a < 10;a++){
    console.log(arr[a]());
  }
}
test(); //結果連打十個10
複製程式碼

分析下執行過程:

首先,在這個作用域內,都是至上而下執行的,var i定義了一個整個作用域上的變數,第一個for語句執行結束後,i為10, 然後是第二個for語句,執行arr[i] = function(){ return i};,因為在函式中沒有找到i,會往上一層作用域去找,而上一層的i已經為10,所以會連續輸出十個十。

var改動成let後,看下執行結果:

function test(){
  var arr = [];
  for(let i = 0;i < 10;i++){
    arr[i] = function(){
      return i;
    };
  }
  for(var a = 0;a < 10;a++){
    console.log(arr[a]());
  }
}
test(); //輸出0,1,2,3,4,5,6,7,8,9
複製程式碼

分析下執行過程:

let 在定義變數時會開闢新的塊級作用域,即每次for迴圈都會開闢出一塊的新的作用域,所以當執行arr[i] = function(){ return i};時,沒有找到i時,向上找,在let開闢的新的塊級作用域中找到了i,即對應的0,1,2,3,4,5,6,7,8,9

閉包中的this物件:

有如下程式碼:

var obj = {
    name: 'yy',
    getThis: function() {
        console.log('this', this);
    } 
}

obj.getThis();
複製程式碼

執行結果:this Object {name: "yy"}...就是我們普遍認為的函式在哪裡定義就在哪裡執行。

那再看下面這個例子:

var obj = {
    name: 'yy',
    getThis: function() {
        //匿名函式
        return function() {
             console.log('this', this);
        }
    } 
}
obj.getThis()();
複製程式碼

執行結果:

this Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}

全域性作用域中呼叫了匿名函式,所以this指向了window。

不要認為函式在哪裡,其內部的this就指向哪裡。匿名函式的執行環境具有全域性性,因此其 this 物件通常指向 window。

鞏固下,想想下面的結果是啥:

var name = "The Window";

var obj = {
  name: "My Object",
  
  getName: function(){
    var that = this;
    console.log('getName:', this);
    return function(){
      return that.name;
    };
  }
};

console.log(obj.getName()()); 
複製程式碼

閉包的應用:設計私有的方法和變數

  • 任何在函式中定義的變數,都可以認為是私有變數,因為不能在函式外部訪問這些變數。私有變數包括函式的引數、區域性變數和函式內定義的其他函式。
  • 把有權訪問私有變數的公有方法稱為特權方法(privileged method)。
function Animal(){
  
  // 私有變數
  var series = "哺乳動物";
  function run(){
    console.log("Run!!!");
  }
  
  // 特權方法
  this.getSeries = function(){
    return series;
  };
}
複製程式碼

單例(singleton):指的是隻有一個例項的物件。JavaScript 一般以物件字面量的方式來建立一個單例物件。

var singleton = {
  name: "percy",
  speak:function(){
    console.log("speaking!!!");
  },
  getName: function(){
    return this.name;
  }
};
複製程式碼

模組模式(The Module Pattern):為單例建立私有變數和方法。

var singleton = (function(){
  
  // 私有變數
  var age = 22;
  var speak = function(){
    console.log("speaking!!!");
  };
  
  // 特權(或公有)屬性和方法
  return {
    name: "percy",
    getAge: function(){
      return age;
    }
  };
})();
複製程式碼

閉包的缺陷:

  1. 閉包的缺點就是常駐記憶體會增大記憶體使用量,並且使用不當很容易造成記憶體洩露。
  2. 如果不是因為某些特殊任務而需要閉包,在沒有必要的情況下,在其它函式中建立函式是不明智的,因為閉包對指令碼效能具有負面影響,包括處理速度和記憶體消耗。

相關文章