在JavaScript中,在全部函數體以外聲明的變量爲全局變量,而在函數體內聲明的變量(經過var關鍵字)爲局部變量。事實上,全局變量是全局對象的屬性而已,好比在客戶端的JavaScript中,咱們聲明的變量實際上是window對象的屬性,如此而已。
javascript
那麼,局部變量又隸屬於什麼對象呢?就是咱們要討論的調用對象。在執行一個函數時,函數的參數和其局部變量會做爲調用對象的屬性進行存儲。同時,解釋器會爲函數建立一個執行器上下文(Execution Context),與上下文對應起來的是一個做用域鏈。顧名思義,做用域鏈是關於做用域的鏈,一般實現爲一個鏈表,鏈表的每一個項都是一個對象,在全局做用域中,該鏈中有且只有一個對象,即全局對象。對應的,在一個函數中,做用域鏈上會有兩個對象,第一個(首先被訪問到的)爲調用對象,第二個爲全局對象。 java
若是函數須要用到某個變量,則解釋器會遍歷做用域鏈,好比: 程序員
var str = "global"; function scopeTest(){ print(str); var str = "local"; print(str); }
當解釋器進入scopeTest函數的時候,一個調用對象就被建立了,其中包含了str變量做爲其中的一個屬性並被初始化爲undefined,當執行到第一個print(str)時,解釋器會在做用域鏈中查找str,找到以後,打印其值爲undefined,而後執行賦值語句,此時調用對象的屬性 str會被賦值爲local,所以第二個print(str)語句會打印local。 設計模式
應該注意的是,做用域鏈隨着嵌套函數的層次會變的很長,可是查找變量的過程依舊是遍歷做用域鏈(鏈表),一直向上查找,直到找出該值,若是遍歷完做用域鏈仍然沒有找到對應的屬性,則變量未定義。(定義未賦值返回undefined,未定義會引發運行時的錯誤not defined)。 閉包
嘗試讀一個未聲明的變量的值,Javascript會生成一個錯誤。若是嘗試給一個味用var聲明的變量賦值,Javascript會隱式聲明該變量,注意,隱式聲明的變量老是被建立爲全局變量,即便該變量只是在一個函數體內使用。防止在建立局部變量時建立全局變量,必須在函數體內部使用var語句。 函數
一個函數的調用對象是動態的,它是在這個函數被調用時才被實例化的。咱們已經知道,當一個函數被定義的時候,已經肯定了它的做用域鏈。當 Javascript 解釋器調用一個函數的時候,它會添加一個新的對象(調用對象)到這個做用域鏈的前面。這個調用對象的一個屬性被初始化成一個名叫 arguments 的屬性,它引用了這個函數的 Arguments 對象,Arguments 對象是函數的實際參數。全部用 var 語句聲明的本地變量也被定義在這個調用對象裏。這個時候,調用對象處在做用域鏈的頭部,本地變量、函數形式參數和 Arguments 對象所有都在這個函數的範圍裏了。固然,這個時候本地變量、函數形式參數和 Arguments 對象就覆蓋了做用域鏈裏同名的屬性。 spa
在函數被定義的時候,實際上也是它外層函數執行的時候,它肯定的做用域鏈其實是它外層函數的調用對象鏈;當函數被調用時,它的做用域鏈是根據定義的時候 肯定的做用域鏈(它外層函數的調用對象鏈)加上一個實例化的調用對象。因此函數的做用域鏈其實是調用對象鏈。在一個函數被調用的時候,它的做用域鏈(或 者稱調用對象鏈)其實是它在被定義的時候肯定的做用域鏈的一個超集。它們之間的關係能夠表示成:做用域⊃做用域鏈⊇調用對象。 設計
function f(x) { var g = function () { return x; } return g; } var g1 = f(1); alert(g1()); //輸出 1假設咱們把全局當作相似如下這樣的一個大匿名函數:
(function() { //這裏是全局範圍 })();那麼例子就能夠當作是:
(function() { function f(x) { var g = function () { return x; } return g; } var g1 = f(1); alert(g1()); //輸出 1 })();
閉包的一個簡單的說法是,當嵌套函數在被嵌套函數以外調用的時候,就造成了閉包。 code
以前的那個例子其實就是一個閉包。g1 是在 f(1) 內部定義的,卻在 f(1) 返回後才被執行。能夠看出,閉包的一個效果就是被嵌套函數 f 返回後,它內部的資源不會被釋放。在外部調用 g 函數時,g 能夠訪問 f 的內部變量。根據這個特性,能夠寫出不少優雅的代碼。 對象
例如要在一個頁面上做一個統一的計數器,若是用閉包的寫法,能夠這麼寫:
var counter = (function() { var i = 0; var fns = {"get": function() {return i;}, "inc": function() {return ++i;}}; return fns; })(); //do something counter.inc(); //do something else counter.inc(); var c_value = counter.get(); //now c_value is 2
這樣,在內存中就維持了一個變量 i,整個程序中的其它地方都沒法直接操做 i 的值,只能經過 counter 的兩個操做。
在 setTimeout(fn, delay) 的時候,咱們不能給 fn 這個函數句柄傳參數,但能夠經過閉包的方法把須要的參數綁定到 fn 內部。
for(var i=0,delay=1000; i< 5; i++, delay +=1000) { setTimeout(function() { console.log('i:' + i + " delay:" + delay); }, delay); }
這樣,打印出來的值都是
i:5 delay:6000 i:5 delay:6000 i:5 delay:6000 i:5 delay:6000 i:5 delay:6000
改用閉包的方式能夠很容易綁定要傳進去的參數:
for(var i=0, delay=1000; i < 5; i++, delay += 1000) { (function(a, _delay) { setTimeout(function() { console.log('i:'+a+" delay:"+_delay); }, _delay); })(i, delay); }
輸出:
i:0 delay:1000 i:1 delay:2000 i:2 delay:3000 i:3 delay:4000 i:4 delay:5000
閉包還有一個很經常使用的地方,就是在綁定事件的回調函數的時候。也是一樣的道理,綁定的函數句柄不能作參數,但能夠經過閉包的形式把參數綁定進去。