原文連接javascript
簡單講,閉包就是指有權訪問另外一個函數做用域中的變量的函數。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
【書】《JavaScript 高級程序設計(第三版)》