面試必問題目,但總以爲理解得不深刻,索性寫一篇文章慢慢梳理吧。面試
紅寶書上給出的定義是:閉包是指有權訪問另外一個函數做用域中的變量的函數,看到另一個理解是:函數和函數內部能訪問到的變量(或者環境)的總合,就是一個閉包。建立一個閉包最多見的方式就是在一個函數內部建立另外一個函數。下面寫一個例子:閉包
function f1() { var a = 1; function closure() { console.log(++a); } return closure; }
上面例子中,f1
內部的匿名函數以及它可以訪問到的外部函數的變量 a
合在一塊兒,就造成了一個閉包。使用 return
將閉包返回的目的是讓它能夠被外部訪問。下面看看它怎麼使用:函數
var f2 = f1(); // 執行外部函數,返回閉包 f2(); // 2 f2(); // 3 f2(); // 4
第一句執行函數 f1()
後,閉包被返回並賦值給了一個全局變量 f2
,之後每次調用 f2()
,變量 a
的值就會加 1
。一般函數執行完畢後,其做用域鏈和活動對象都會被銷燬,爲何這裏 a
並無被銷燬而且每次執行 f2()
還會被遞增?緣由是閉包有權訪問外部函數的變量,進一步說,閉包的做用域鏈會引用外部函數的活動對象,因此 f2()
在執行時,其做用域鏈其實是:性能
f1()
的活動對象;因此 f1()
執行完後,其執行環境的做用域鏈會被銷燬,但活動對象仍然會留在內存中,由於閉包做用域鏈在引用這個活動對象(說白了就是閉包還須要使用外層函數的變量,不容許它們被銷燬),直到閉包被銷燬後,f1()
的活動對象纔會被銷燬。this
上面例子中,是將返回的閉包賦值給了一個全局變量 f2
,var f2 = f1();
,f2
是不會被銷燬的,每次執行完 f2()
,閉包的做用域鏈不會被銷燬,因此就會出現每次執行 f2()
,a
遞增。code
可是換一種閉包的調用方式,狀況會不一樣:對象
f1()(); // 2 f1()(); // 2
由於沒有把閉包賦值給一個全局變量,閉包執行完後,其執行域鏈與活動對象都銷燬了。接口
其實構造函數中定義的實例方法,就是閉包:內存
function Person(){ var name = 'Leon'; function sayHi() { alert('Hi!'); } this.publicMethod = function() { alert(name); return sayHi(); } }
構造函數 Person
中定義實例方法 publicMethod()
就是一個閉包,它能夠訪問外部函數的變量 name
和 函數 sayHi()
,爲何要這麼作呢?由於咱們想在構造函數中定義一些私有變量,讓外部不能直接訪問,只能經過定義好的公有方法訪問,從而達到保護變量,收斂外部權限的目的。作用域
而在普通函數中,把閉包 return
出去供外部使用,其實目的也就是:讓函數內部的變量始終保持在內存中,同時保護這些變量,讓它們不能被直接訪問。
function person(){ var name = 'Leon'; function sayHi() { alert('Hi!'); } function publicMethod() { alert(name); return sayHi(); } return publicMethod; }
所謂單例,就是隻有一個實例的對象。單例模式的好處在於:
保證一個類只有一個實例,避免了一個在全局範圍內使用的實例頻繁建立與銷燬。
劃分了命名空間,避免了與全局命名空間的衝突。
單例.方法
來使用,避免了在全局環境中定義函數,形成函數名衝突。下面逐步介紹下單例的建立方式,後兩種方式將用到閉包。
var singleton = { attr1: 1, attr2: 2, method: function () { return this.attr1 + this.attr2; } } var s1 = singleton; var s2 = singleton; console.log(s1 == s2) // true
上面用字面量形式建立了一個單例,能夠看到 s1
和 s2
是等同的。這種方式的問題在於外部能夠直接訪問單例的內部變量並加以修改,若是想讓單例擁有私有變量,就須要使用模塊模式,模塊模式就是用了閉包。
JS 中的模塊模式的做用是:爲單例添加私有變量和公有方法。它使用當即執行函數和閉包來達到目的。
var singleton = (function(){ // 建立私有變量 var privateNum = 1; // 建立私有函數 function privateFunc(){ console.log(++privateNum); } // 返回一個對象包含公有方法 return { publicMethod: function(){ console.log(privateNum) return privateFunc() } }; })(); singleton.publicMethod(); // 1 // 2
這裏首先定義了一個當即執行函數,它返回一個對象,該對象中有一個閉包 publicMethod()
, 它能夠訪問外部函數的私有變量。從而這個被返回的對象就成爲了單例的公共接口,外部能夠經過它的公有方法訪問私有變量而無權直接修改。總結一下就是兩點:
上面提到的對象字面是用來建立單例的方法之一,既然單例只能被實例化一次,不難想到,在使用構造函數新建實例時,先判斷實例是否已被新建,未被新建則新建實例,不然直接返回已被新建的實例。
var Singleton = function(name){ this.name = name; }; // 獲取實例對象 var getInstance = (function() { var instance = null; return function(name) { if(!instance) { instance = new Singleton(name); } return instance; } })(); var a = getInstance('1'); console.log(a); // {name: "1"} var b = getInstance('2'); console.log(b); // {name: "1"}
這裏將構造函數和實例化過程進行了分離, getInstance()
中存在一個閉包,它能夠訪問到外部變量 instance
,第一次 instance = null
,則經過 new Singleton(name)
新建實例,並將這個實例保存在instance
中,以後再想新建實例,由於閉包訪問到的instance
已經有值了,就會直接返回以前實例化的對象。