1.閉包
先看一個簡單的例子
function a() { var i = 0; function b() { alert(++i); } return b; }
var c = a(); c();
這段代碼有兩個特點:
1、函數b嵌套在函數a內部;
2、函數a返回函數b。
這樣在執行完var c=a()后,變量c實際上是指向了函數b,再執行c()后就會彈出一個窗口顯示i的值(第一次為1)。這段代碼其實就創建了一個閉包,為什么?因為函數a外的變量c引用了函數a內的函數b,就是說:當函數a的內部函數b被函數a外的一個變量引用的時候,就創建了一個閉包。
閉包的作用就是在a執行完並返回后,閉包使得Javascript的垃圾回收機制GC不會收回a所占用的資源,因為a的內部函數b的執行需要依賴a中的變量
如果要更加深入的了解閉包以及函數a和嵌套函數b的關系,我們需要引入另外幾個概念:函數的執行環境(excution context)、活動對象(call object)、作用域(scope)、作用(scope chain)。以函數a從定義到執行的過程為例闡述這幾個概念。
當定義函數a的時候,js解釋器會將函數a的作用域鏈(scope chain)設置為定義a時a所在的“環境”,如果a是一個全局函數,則scope chain中只有window對象。
當執行函數a的時候,a會進入相應的執行環境(excution context)。
在創建執行環境的過程中,首先會為a添加一個scope屬性,即a的作用域,其值就為第1步中的scope chain。即a.scope=a的作用域鏈。然后執行環境會創建一個活動對象(call object)。活動對象也是一個擁有屬性的對象,但它不具有原型而且不能通過JavaScript代碼直接訪問。創建完活動對象后,把活動對象添加到a的作用域鏈的最頂端。此時a的作用域鏈包含了兩個對象:a的活動對象和window對象。下一步是在活動對象上添加一個arguments屬性,它保存着調用函數a時所傳遞的參數。最后把所有函數a的形參和內部的函數b的引用也添加到a的活動對象上。在這一步中,完成了函數b的的定義,因此如同第3步,函數b的作用域鏈被設置為b所被定義的環境,即a的作用域。
到此,整個函數a從定義到執行的步驟就完成了。此時a返回函數b的引用給c,又函數b的作用域鏈包含了對函數a的活動對象的引用,也就是說b可以訪問到a中定義的所有變量和函數。函數b被c引用,函數b又依賴函數a,因此函數a在返回后不會被GC回收。
當函數b執行的時候亦會像以上步驟一樣。因此,執行時b的作用域鏈包含了3個對象:b的活動對象、a的活動對象和window對象,當在函數b中訪問一個變量的時候,搜索順序是:
1.先搜索自身的活動對象,如果存在則返回,如果不存在將繼續搜索函數a的活動對象,依次查找,直到找到為止。
2.如果函數b存在prototype原型對象,則在查找完自身的活動對象后先查找自身的原型對象,再繼續查找。這就是Javascript中的變量查找機制。
3.如果整個作用域鏈上都無法找到,則返回undefined。
小結,本段中提到了兩個重要的詞語:函數的定義與執行。文中提到函數的作用域是在定義函數時候就已經確定,而不是在執行的時候確定(參看步驟1和3)。用一段代碼來說明這個問題:
function f(x) { var g = function () { return x; } return g;}var h = f(1);alert(h());
這段代碼中變量h指向了f中的那個匿名函數(由g返回)。
假設函數h的作用域是在執行alert(h())確定的,那么此時h的作用域鏈是:h的活動對象->alert的活動對象->window對象。
假設函數h的作用域是在定義時確定的,就是說h指向的那個匿名函數在定義的時候就已經確定了作用域。那么在執行的時候,h的作用域鏈為:h的活動對象->f的活動對象->window對象。
如果第一種假設成立,那輸出值就是undefined;如果第二種假設成立,輸出值則為1。
運行結果證明了第2個假設是正確的,說明函數的作用域確實是在定義這個函數的時候就已經確定了。
2.作用域
就是變量與函數的可訪問范圍
全局作用域:
1.最外層函數和在最外層函數外面定義的變量擁有全局作用域
2.所有末定義直接賦值的變量自動聲明為擁有全局作用域
3.所有window對象的屬性擁有全局作用域
window對象的內置屬性都擁有全局作用域,例如window.name、window.location、window.top
局部作用域:函數內部
3.作用域鏈
當一個函數創建后,它的作用域鏈會被創建此函數的作用域中可訪問的數據對象填充。
4.原型對象
集中保存同一類型的子對象共有成員的父對象,在定義構造函數時,自動創建。當使用構造函數創建子對象時,會自動設置子對象繼承構造函數的原型對象。構造函數負責創建指定類型的對象,原型對象負責保存該類型子對象共有的API。
5.原型鏈
保存了所有對象的成員(屬性和方法),定義了成員的使用順序:先用自有成員, 自己沒有,才延原型鏈向父對象查找
