JavaScript 閉包

JavaScript 閉包

原文連接javascript

什麼是閉包(Closure)

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

MDN 上面這麼說閉包是一種特殊的對象。它由兩部分構成:函數,以及建立該函數的環境。環境由閉包建立時在做用域中的任何局部變量組成。java

可是,網上找了好多資料,它們對閉包的定義也各有各樣,搞得我也不知道怎麼去定義閉包了,因此乾脆不去定義了,意會吧。面試

道可道,很是道;名可名,很是名。segmentfault

產生一個閉包

建立閉包最多見方式,就是在一個函數內部建立另外一個函數。下面例子中的 closure 就是一個閉包:安全

function func(){
  var a = 1,b = 2;
  
  function closure(){
    return a+b;
  }
  return closure;
}

閉包的做用域鏈包含着它本身的做用域,以及包含它的函數的做用域和全局做用域。閉包

閉包的注意事項

  • 一般,函數的做用域及其全部變量都會在函數執行結束後被銷燬。可是,在建立了一個閉包之後,這個函數的做用域就會一直保存到閉包不存在爲止。模塊化

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

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

// 釋放對閉包的引用
add5 = null;
add10 = null;

add5 和 add10 都是閉包。它們共享相同的函數定義,可是保存了不一樣的環境。在 add5 的環境中,x 爲 5。而在 add10 中,x 則爲 10。最後經過 null 釋放了 add5 和 add10 對閉包的引用。函數

在javascript中,若是一個對象再也不被引用,那麼這個對象就會被垃圾回收機制回收;
若是兩個對象互相引用,而再也不被第3者所引用,那麼這兩個互相引用的對象也會被回收。性能

  • 閉包只能取得包含函數中任何變量的最後一個值,這是由於閉包所保存的是整個變量對象,而不是某個特殊的變量。

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 個 10

對於上面的狀況,若是咱們改變代碼以下:

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 到 9

對於上面兩種代碼的解釋,請看我在 segmentfault 上面的提問:連接

  • 閉包中的 this 對象

var name = "The Window";

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

console.log(obj.getName()());  // The Window

obj.getName()()其實是在全局做用域中調用了匿名函數,this指向了window。這裏要理解函數名與函數功能(或者稱函數值)是分割開的,不要認爲函數在哪裏,其內部的this就指向哪裏。匿名函數的執行環境具備全局性,所以其 this 對象一般指向 window。

var name = "The Window";

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

console.log(obj.getName()());  // My Object

閉包的應用

  • 應用閉包的主要場合是:設計私有的方法和變量。

任何在函數中定義的變量,均可以認爲是私有變量,由於不能在函數外部訪問這些變量。私有變量包括函數的參數、局部變量和函數內定義的其餘函數。

把有權訪問私有變量的公有方法稱爲特權方法(privileged method)

function Animal(){
  
  // 私有變量
  var series = "哺乳動物";
  function run(){
    console.log("Run!!!");
  }
  
  // 特權方法
  this.getSeries = function(){
    return series;
  };
}

模塊模式(The Module Pattern):爲單例建立私有變量和方法。

單例(singleton):指的是隻有一個實例的對象。JavaScript 通常以對象字面量的方式來建立一個單例對象。

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

上面是普通模式建立的單例,下面使用模塊模式建立單例:

var singleton = (function(){
  
  // 私有變量
  var age = 22;
  var speak = function(){
    console.log("speaking!!!");
  };
  
  // 特權(或公有)屬性和方法
  return {
    name: "percy",
    getAge: function(){
      return age;
    }
  };
})();
  • 匿名函數最大的用途是建立閉包,而且還能夠構建命名空間,以減小全局變量的使用。從而使用閉包模塊化代碼,減小全局變量的污染。

var objEvent = objEvent || {};
(function(){ 
    var addEvent = function(){ 
      // some code
    };
    function removeEvent(){
      // some code
    }

    objEvent.addEvent = addEvent;
    objEvent.removeEvent = removeEvent;
})();

在這段代碼中函數 addEvent 和 removeEvent 都是局部變量,但咱們能夠經過全局變量 objEvent 使用它,這就大大減小了全局變量的使用,加強了網頁的安全性。

  • 一個閉包計數器

var countNumber = (function(){
  var num = 0;
  return function(){
    return ++num;
  };
})();

閉包的缺陷

  • 閉包的缺點就是常駐內存會增大內存使用量,而且使用不當很容易形成內存泄露。

  • 若是不是由於某些特殊任務而須要閉包,在沒有必要的狀況下,在其它函數中建立函數是不明智的,由於閉包對腳本性能具備負面影響,包括處理速度和內存消耗。

最後再來一些有關閉包的面試題

  • 下面代碼中,標記 ? 的地方輸出分別是什麼?

function fun(n,o){
  console.log(o);
  return {
    fun: function(m){
      return fun(m,n);
    }
  };
}

var a = fun(0);  // ?
a.fun(1);        // ?        
a.fun(2);        // ?
a.fun(3);        // ?

var b = fun(0).fun(1).fun(2).fun(3);  // ?

var c = fun(0).fun(1);  // ?
c.fun(2);        // ?
c.fun(3);        // ?

undefined
0
0
0
undefined, 0, 1, 2
undefined, 0
1
1

參考資料

相關文章
相關標籤/搜索