面試必問題,閉包是啥有啥子用,覺得自己之前回答的并不好,是以這次複習紅皮書的時候總結一下。
提到閉包,相關的知識點比較多,是以先羅列一下要講的内容。
1. 作用域鍊,活動對象
2. 關于this對象
3. 垃圾回收機制,記憶體洩漏
4. 模仿塊級作用域,私有變量
涉及的内容這麼多,也難怪面試官喜歡問這個問題啊,就像niko大神說的,應該是根據回答的深淺了解你的思維模式吧。廢話不多說,開始步入正題。
1. 作用域鍊,活動對象
活動對象:活動對象就是在函數第一次調用時,建立一個對象,在函數運作期是可變的,它包含了this,arguments,以及局部變量和命名參數。
執行環境:執行環境定義了變量或函數有權通路的其他資料,決定了它們各自的行為。每個執行環境都有與之相關聯的變量對象,環境中定義的所有變量和函數都儲存在這個對象中。某個執行環境中的代碼被執行完後,該環境被銷毀,儲存其中的所有變量和函數也會被銷毀。啊說的文绉绉的,我按自己的了解白話文一下好了。簡單來講,每個函數開始調用執行都會建立一個執行環境,這個環境就跟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();
}
})();
轉載于:https://www.cnblogs.com/echo-yaonie/p/4643732.html