閉包是一種特殊的對象。它由兩部分組成:執行上下文(代號A),以及在該執行上下文中建立的函數(代號B)。閉包
當執行B時,若是訪問了A中的變量對象中的值,那麼閉包就會產生。函數
有時候以函數B的名字代指這裏生成的閉包。而在Chrome中,則以執行上下文A的函數名代指閉包。工具
只須要知道一個閉包對象,由A、B共同組成便可。性能
// demo1 function foo() { var a = 100; var b = 200; function bar() { return a + b; } return bar; } var bar = foo(); bar();
上面例子中,首先執行上下文foo,在foo中定義了函數bar,然後經過對外返回bar的方式讓bar得以執行。當bar執行時,訪問了foo內部的變量a和b。所以這個時候閉包產生。優化
在Chrome中經過斷點調試的方式能夠逐步分析該過程,此時閉包產生,用foo代指,以下圖:this
上圖中,箭頭所指的正是閉包。其中Call Stack爲當前的函數執行棧,Scope爲當前正在被執行函數的做用域,Local爲當前活動對象。spa
來看一個很是有意思的例子:設計
// demo2 function add(x) { return function _add(y) { return x + y; } } add(2)(3); // 5
上面的例子有閉包產生嗎?
固然有。當內部函數_add被調用執行時,訪問了add函數變量對象中的x,這個時候,閉包就會產生,以下圖,必定要記住,函數參數的變量傳遞給函數以後也會加到變量對象中。調試
下面代碼會產生閉包嗎?code
// demo3 var name = "window"; var person = { name: "perter", getName: function() { return function() { return this.name; }; } }; var getName = person.getName(); var _name = getName(); console.log(_name);
getName在執行時,它的this其實指向的是window對象,而這個時候並無造成閉包的環境,所以這個例子沒有閉包。
若是按照下面的方式進行改動呢?
// demo4 // 改動一 var name = "window"; var person = { name: "perter", getName: function() { return function() { return this.name; }; } }; var getName = person.getName(); // 利用call的方式讓this指向person對象 var _name = getName.call(person); console.log(_name);
// demo5 // 改動二 var name = "window"; var person = { name: "perter", getName: function() { // 利用變量保存的方式保證其訪問的是person對象 var self = this; return function() { return self.name; }; } }; var getName = person.getName(); var _name = getName(); console.log(_name);
分別利用call與變量保存的方式保證this指向的都爲person對象。因此demo4(因爲Chrome已作優化,因此在Chrome調試工具中沒有顯示閉包)和demo5都產生了閉包。
瞭解垃圾回收機制原理都知道當一個值失去引用以後就會被標記,而後被垃圾回收機制回收並釋放空間。當一個函數的執行上下文運行完畢以後,內部的全部內容都會失去引用而被垃圾回收機制回收。
閉包的本質就是在函數的外部保持了內部變量的引用,所以閉包會阻止垃圾回收機制進行回收
下面用一個例子來證實這一點:
// demo6 function foo1() { var n = 99; nAdd = function() { n += 1; }; return foo2() { console.log(n); }; } var result = foo1(); result(); // 99 nAdd(); result(); // 100
從上面的例子能夠看出,由於nAdd都訪問了foo1中的n,所以它們都與foo1造成了閉包。這個時候變量n的引用被保留了下來。由於foo2(result)與nAdd執行時都訪問了n,aAdd每運行一次就會將n加1,因此上例的執行結果很是符合咱們的認知。
認識到 閉包中保存的內容不會被釋放以後,咱們在使用 閉包時就要保持足夠的警戒性。若是濫用 閉包,極可能會由於內存的緣由致使程序性能過差。
結合下面的例子思考一下,閉包會致使函數的做用域鏈發生改變嗎?
// demo7 var fn = null; function foo() { var a = 2; function innerFoo() { console.log(a); } fn = innerFoo; // 將innerFoo的引用賦值給全局變量中的fn } function bar() { var a = 3; fn(); // 此處保留innerFoo的引用 } foo(); bar(); // 2
在上面的例子中,foo內部的innerFoo訪問了foo的變量a。所以當innerFoo執行時會有閉包產生。全局變量fn在foo內部獲取了innerFoo的引用,並在bar中執行。
innerFoo斷點調試圖以下:
在這裏須要特別注意的地方是函數調用棧(Call Stack)與做用域鏈(Scope)的區別。由於函數調用棧實際上是在代碼執行時才肯定的,而做用域規則在代碼編譯階段就已經肯定,雖然做用域鏈是在代碼執行時才生成的,可是它的規則並不會在執行時發生改變。
因此,閉包的存在並不會致使做用域鏈發生變化。
參考資料:
JavaScript高級程序設計