面試必問題,閉包是啥有啥子用,以爲本身以前回答的並很差,因此此次複習紅皮書的時候總結一下。前端
提到閉包,相關的知識點比較多,因此先羅列一下要講的內容。面試
1. 做用域鏈,活動對象數組
2. 關於this對象閉包
3. 垃圾回收機制,內存泄漏dom
4. 模仿塊級做用域,私有變量函數
涉及的內容這麼多,也難怪面試官喜歡問這個問題啊,就像niko大神說的,應該是根據回答的深淺瞭解你的思惟模式吧。廢話很少說,開始步入正題。this
1. 做用域鏈,活動對象操作系統
活動對象:活動對象就是在函數第一次調用時,建立一個對象,在函數運行期是可變的,它包含了this,arguments,以及局部變量和命名參數。prototype
執行環境:執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有與之相關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。某個執行環境中的代碼被執行完後,該環境被銷燬,保存其中的全部變量和函數也會被銷燬。啊說的文縐縐的,我按本身的理解白話文一下好了。簡單來說,每一個函數開始調用執行都會建立一個執行環境,這個環境就跟c裏面的做用域一個道理,就是你在函數執行期間能夠訪問的變量啊數據啊那些都會放到一個環境棧中,裏面有的數據你能夠訪問,沒有的就固然不能訪問啦,當你執行完以後,就把你從環境棧中彈出來,執行環境就被銷燬了,就訪問不到啦~~~指針
做用域鏈:就是一個保存對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行代碼所在環境的變量對象,全局變量在最後。這個也很好理解的嘛,在函數執行時訪問一個變量,一開始會搜索在當前環境裏有沒有該變量,沒有的話在尋找這個函數 外部的變量有沒有,就這樣層層往上找,最後再回訪問全局變量。因此原型鏈若是很長,查到原型的屬性值就顯得灰常漫長了~~
閉包:有權訪問另外一個函數做用域中變量的函數。建立閉包常見方式是便是在一個函數內部建立另外一個函數。
前面介紹了這麼多名詞,那咱們串一下當函數第一次調用的時候究竟會發生啥子?
在函數第一次調用時,建立一個執行環境及相應的做用域鏈,並吧主做用域鏈值付給一個特殊的內部屬性([[scope]])。而後,利用this,arguments和其餘名命名參數 的值來初始化函數的活動對象。在做用域鏈中,當前函數的活動對象處於第一位,外部函數的活動對象處於第二位,外部的外部的活動對象處於第三位......做用域鏈重點爲全局執行環境。
建立一個函數時i,建立一個包含全局變量對象的做用域鏈,這個做用域鏈被保存在內部的[[scope]]屬性中。當調用該函數時,會爲該函數建立一個執行環境,經過複製函數的[[scope]]屬性中的對象構建起執行環境的做用域鏈。此後,又有一個活動對象(變量對象)被建立並推入執行環境做用域鏈的前端。做用域鏈包含兩個變量對象:本地活動對象和全局變量對象。做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不包含實際變量對象。
閉包跟普通函數相比又有哪裏不一樣呢?
普通函數執行完以後,局部活動被銷燬,內存中僅保存全局做用域。可是閉包有所不一樣,閉包的外部函數在執行完畢後,其活動對象不會被銷燬,由於其匿名函數的做用域鏈扔在引用這個活動對象。因此即便外部函數執行環境的做用域鏈會被銷燬,但它的活動對象仍在內存中,直至匿名函數被銷燬。
閉包與變量:因爲做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不包含實際變量對象,因此閉包只能取得包含函數中最後一個值。閉包所保存的是整個變量對象,而不是某個特殊變量的值。下面貼個小例子。
function createFunctions(){
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function(){
return i;
};
}
return result;
}
這個函數返回一個函數數組。指望是這個函數每一個函數執行以後返回[0~9],而現實老是那麼的殘酷,返回的是[10......10]。由於數組中每一個函數的做用域鏈中保存着createFunctions()的活動對象,它們引用同一個變量i。當createFunctions()函數返回後,變量i的值爲10,每一個函數引用的同一個變量i = 10,因此函數數組的每一個函數返回10。
改良以後的2.0版以下:
function createFuncs(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
}
}(i);
}
return result;
}
改動以後的函數,定義了一個自執行的匿名函數,吧這個匿名函數賦值給result數組。這個匿名函數傳了一個參數num,就是函數的正確索引值i。在調用函數數組中每一個函數時,傳入了變量i,由於函數是按值傳遞的,因此把當前值複製給參數num。在這個匿名函數內部,建立一個返回num的閉包。這樣就能返回正確的索引值了。
呼哧呼哧的喘着氣吧第一個部分講完了,如今研究研究this對象。
2. this對象
this對象是在運行時基於函數的執行環境綁定的,在全局函數中,this等於window,當函數被做爲某個對象的方法調用時,this等於那個對象。匿名函數的執行環境居有全局性,所以this指向window。那麼在閉包中想訪問外部做用域的活動對象,不能直接用this,在外部做用域中的this對象保存在一個閉包能訪問的變量裏,而後訪問那個變量就好。
var name = 'window';
var object = {
name :'object',
getNameFunc:function(){
var that = this;
return function(){
return that.name;
}
}
}
console.log(obejct.getNameFunc());//'object'
忙完這陣趕忙把博客搭起來===否則每次貼代碼都想捅本身一刀好心累=.=
3. 垃圾回收機制,內存泄漏
js有自動垃圾回收的機制,不用手動管理,不過了解一下回收機制,謹防內存泄漏也是一個蠻好的習慣啊。
紅皮書上提到的回收方式有兩種,及標記清除和引用計數。標記清除是如今最經常使用的方式,IE8及其更早的版本還用着引用計數的方式,下面就稍微講一下概念。
標記清除:這種方式就是當變量進入環境時,將變量標記爲」進入環境「,不能釋放進入環境的變量佔用的內存。當變量離開環境時,將其標記爲「離開環境」。垃圾收集器在運行的時候會給內存中的全部變量加上標記,它去掉環境中的變量壞人被環境中的變量引用的變量的標記。作完這個工做,還有標記的變量被視爲準備刪除的變量 ,就能夠清除內存啦~
引用計數:跟蹤每一個引用類型值被引用次數。聲明一個變量並對其賦引用類型值時,計數爲1.同一個值被賦給別的變量,計數加1,。包含對這個值引用的變量去了另外一個值,則計數減1。引用計數等於0時,能夠回收。這樣有個棘手的問題那就是循環引用。若是a引用b,b引用a。則兩個引用計數都爲2,並不能被清除。
內存泄漏:回收機制用的引用計數方式時,若是出現了循環引用,那麼有些內存中的垃圾沒法回收,致使內存泄漏。解決循環引用的辦法就是,在不使用的時候,手動斷開js對象與dom元素間的連接,解除引用。閉包在引用計數的方法下也會有問題,好比在閉包的外部函數中的某個引用型值,閉包引用了這個變量那麼這個引用計數至少爲1,沒法釋放內存。以下面的例子。
function assignHandler(){
var element = document.getElementById('someElement');
element.onclick = function(){
console.log(element.id);
}
}
解除循環引用,代碼修改成:
function assignHandler(){
var element = document.getElementById('someElement');
var id = element.id;
element.onclick = function(){
console.log(id);
}
element = null;
}
吧element.id 賦給一個變量,能夠解除循環引用。並把 element = null解除對dom對象的引用。
4. 模仿塊級做用域,私有變量
模仿塊級做用域
function outputNumbers(){
for(var i = 0; i <10; i++){
console.log(i);
}
console.log(i);
}
Java等語言是有塊級做用域的,及上代碼中的i 在for語句塊內能夠訪問 ,出了for語句塊是訪問不了的。Js沒有塊級做用域的概念。i是定義在函數outputNumbers函數的活動對象中,所以在這個函數內均可以訪問i。 因此第二個打印語句訪問獲得i的值 。除此以外,js語句不會提示時候屢次聲明同一個變量。它只會忽略後續的聲明。可是它會執行後續的初始化。
用閉包能夠模仿塊級做用域(私有做用域)。
語法以下(function(){
//塊級做用域
})();
因爲在匿名函數中定一點的任何變量都在執行結束時被銷燬。根據這個原理,能夠吧語句塊的代碼放進自執行的匿名函數裏,模仿塊級做用域。
這個原理更棒的用法就是在全局做用域中被用在函數外部,限制在全局做用域中添加過多的變量和函數。多個開發人員共同開發,也能夠用私有做用域,防止命名衝突。若是在多個閉包間相互通訊,可定義一個全局變量或者直接在this上定義相應的方法。
私有變量
任何在函數內定義的變量均可以認爲是私有變量。吧有權訪問私有變量和函數的公有方法稱爲特權方法。
在函數內部建立一個閉包,閉包經過本身的做用域鏈能夠訪問私有變量。特權方法有構造函數方法,靜態私有變量。
構造函數方法:每一個實例都會建立一樣的方法。
function MyObject(){
var privateVariable = 10;
function privateFunc(){
return false;
}
this.publicMethod = function(){
privateVariable++;
return privateFunc();
}
}
靜態私有變量:運用的選型模式,複用同一個共有函數。缺點是多個實例對象共用一個私有變量。都共享上了,怎麼還算是私有呢。。。yy到了操做系統的臨界資源....
(function(){
var privateVariable = 10;
function privateFunc(){
return false;
}
MyObject = function(){
}; MyObject.prototype.publicMethod = function(){ privateVariable++; return privateVariable(); } })();