JavaScript備忘錄(2)——閉包

語句

JavaScript是解釋型語言,解釋器是按照順序逐句執行的(除了進行一些少許預處理,如將函數聲明提早)。瀏覽器

順序是由流程控制語句來控制的,經常使用的流程控制語句包括:閉包

  • 條件控制語句:if...else和switch...case語句
  • 循環控制語句:while...、do...while、for和for-in語句
  • 其餘:break、continue和try...catch語句

代碼中除去流程控制語句,剩下的部分只作兩件事:函數

  • 爲變量賦值(聲明新變量並複製如var obj = {word: "hello"},或爲已有變量賦值x=y)
  • 調用函數,好比console.log("hello world")

實際上爲了完成以上兩件事,代碼還須要編寫值或對象的字面量,以及訪問現有變量,可是單獨這樣作沒有意義。spa

函數執行過程

上面說到,除去流程控制,代碼只作兩件事,爲變量賦值和調用函數。前者不難理解,如今來看調用函數後發生了什麼:code

當解釋器調用一個函數以後,就會建立與之對應的變量對象(注意,一個變量對象與一次函數調用對應,而不是與一個函數對應),函數內聲明的全部局部變量都綁定在這個變量對象上,函數執行完畢後,不出意外,該變量對象就會銷燬。對象

咱們將函數執行過程當中全部能夠訪問到的變量的集合稱爲函數的執行上下文。對C++來講,函數的執行上下文只有函數內部定義的變量、函數實參和全局變量,而JavaScript的最大特色在於,函數的執行上下文包括了函數調用者的執行上下文,也就是說在函數內部能夠毫無障礙地訪問函數外部的數據。在咱們什麼都沒作的時候,其實已經處在了全局的執行上下文之中了。blog

var names = [];
function sayhi(name){
if(!names[name]){
names.push(name);
} console.log("hello "+name);
} sayhi("Ross");
sayhi("Rachel"
);
console.log(names); //-> ["Ross", "Rachel"]
console.log(name); //-> undefined

上面函數sayhi執行過程當中,代碼試圖訪問names變量,可是在函數內部並未定義names(GCC這時就要報錯了),因此函數就去sayhi的調用者(window)的變量對象上找到了names。ip

當函數內部調用其餘函數(或本身)時,變量對象就變成了三個或者更多,當在函數內試圖訪問某個變量時,就會從內向外依次在變量對象中查找變量,直到找到第一個變量爲止。get

閉包

上文說到,函數執行完畢後,不出意外,該變量對象就會銷燬。實際上,常常會出現意外。若是函數執行完畢後,仍然有可能訪問到函數內部的變量,此時雖然執行流程已經在函數外部了,但函數的變量對象不會被銷燬it

var last;
function sayhi(name){
    last = function(){
        return name;
    }
    console.log("hello "+name);
}
sayhi("Ross");
console.log(last()); //-> "Ross"

 因爲在sayhi函數內部,咱們將外部變量last賦值爲了一個新的函數,該函數返回sayhi函數的實參name,因此sayhi("Ross")以後,雖然sayhi已經執行完成了,可是last仍然能夠訪問到sayhi內部的變量name,因此由sayhi("Ross")此次調用帶來的變量對象不會被銷燬。並且,只要接下來的代碼(解釋器永遠不知道下一行代碼是什麼)有可能訪問到該變量對象中的元素,該變量對象就不會銷燬。

變量對象未銷燬,其包含的全部元素(包括那些不可能再被訪問到的元素)也會保留。因此,若是使用不當,閉包會超出預期限度地加劇瀏覽器的負擔。(如今已有一些瀏覽器如Chrome開始嘗試回收閉包中的那些不可能再被訪問到的元素)。

閉包能夠用來模擬對象的私有變量。下面這個對象對象chandler模擬了C++中的類的實例,兩個屬性name和hobby只能經過方法訪問,不能被改變。

var chandler = (function(){
    var name = "CHANDLER";var hobby = "joke";return {
        getName : function(){return name;},
        getHobby : function(){return hobby;}
    }
})();
相關文章
相關標籤/搜索