[譯]JavaScript:函數的範圍鏈

來源:互聯網
上載者:User

原文:http://blogs.msdn.com/b/jscript/archive/2007/07/26/scope-chain-of-jscript-functions.aspx

在JavaScript中,函數的範圍鏈是一個很難理解的東西.這是因為,JavaScript中函數的範圍鏈和其他語言比如C, C++中函數的範圍鏈相差甚遠.本文詳細解釋了JavaScript中與函數的範圍鏈相關的知識,理解這些知識可以協助你在處理閉包的時候避免一些可能出現的問題.

在JavaScript中,函數可以讓你在一次調用中執行一系列的操作.有多種方式來定義一個函數,如下:

函式宣告:

function maximum(x, y) {    if (x > y) return x;    else return y;}maximum(5, 6) //返回6;

這種文法通常用來定義全域範圍下的函數(全域函數).

函數運算式:

var obj = new Object();obj.maximum = function (x, y) {    if (x > y) return x;    else return y;};
obj.maximum(5, 6) //返回6;

這種文法通常用來定義一個作為對象方法的函數.

Function建構函式:

var maximum = new Function("x", "y", "if(x > y) return x; else return y;");maximum(5, 6); //返回6;

以這種形式定義函數通常沒有很好的可讀性(沒有縮排),只在特定情況下使用.

函數定義:

函數定義指的是在JavaScript引擎內部建立一個函數對象的過程.如果是全域函數的話,這個函數對象會作為屬性添加到全域對象上,如果是內建函式(嵌套函數)的話,該函數對象會作為屬性添加到上層函數的使用中的物件上,屬性名稱就是函數名.需要指出的是,如果函數是以函式宣告的方式定義的,則函數的定義操作會發生在指令碼解析的時候.如下例中,當JavaScript引擎完成指令碼解析時,就已經建立了一個函數對象func,該函數對象作為屬性添加到了全域對象中,屬性名稱為"func".

/*func函數可以被訪問到,因為在指令碼開始執行前func函數就已經存在了.*/alert(func(2)); //返回8
//執行該語句會覆蓋func的值為true.var func = true;
alert(func); //返回"true";
/*在指令碼開始執行前,解析下面的語句就會定義一個函數對象func.*/function func(x) { return x * x * x;}

在下面的例子中,存在內建函式的情況.內建函式innerFn的定義操作發生在外部函數outerFn執行的時候(其實也是發生在執行前的解析階段),同時,內建函式會作為屬性添加到外部函數的使用中的物件上.

function outerFn() {    function innerFn() {}}outerFn(); //執行outerFn函數的時候會定義一個函數innerFn

:  對於使用Function建構函式定義的函數來說,函數定義操作就發生在執行Function建構函式的時候.

範圍鏈:

函數的範圍鏈是由一系列對象(函數的使用中的物件+0個到多個的上層函數的使用中的物件+最後的全域對象)組成的,在函數執行的時候,會按照先後順序從這些對象的屬性中尋找函數體中用到的標識符的值(標識符解析).函數會在定義時將它們各自所處環境(全域上下文或者函數上下文)的範圍鏈儲存到自身的[[scope]]內部屬性中. 首先看一個內建函式的例子:

function outerFn(i) {    return function innerFn() {        return i;    }}var innerFn = outerFn(4);innerFn(); //返回4

當innerFn函數執行時,成功返回了變數i的值4,但變數i既不存在於innerFn函數自身的局部變數中,也不存在於全域範圍中.那麼變數i的值是從哪兒得到的? 你也許認為內建函式innerFn的範圍鏈是由innerFn函數的使用中的物件+全域對象組成的.但這是不對的,只有全域函數的範圍鏈包含兩個對象,這並不適用於內建函式.讓我們先分析全域函數,然後再分析內建函式.

全域函數:

全域函數的範圍鏈很好理解.

var x = 10;var y = 0;function testFn(i) {    var x = true;    y = y + 1;    alert(i);}testFn(10);

全域對象: JavaScript引擎在指令碼開始執行之前就會建立全域對象,並添加到一些預定義的屬性"Infinity", "Math"等.在指令碼中定義的全域變數也會成為全域對象的屬性.

使用中的物件: 當JavaScript引擎調用一些函數時,該函數會建立一個新的使用中的物件,所有在函數內部定義的局部變數以及傳入函數的具名引數和arguments對象都會作為這個使用中的物件的屬性.這個使用中的物件加上該函數的[[scope]]內部屬性中儲存的範圍鏈就組成了本次函數調用的範圍鏈.

內建函式:

讓我們分析一下下面的JavaScript代碼.

function outerFn(i, j) {    var x = i + j;    return function innerFn(x) {        return i + x;    }}var func1 = outerFn(5, 6);var func2 = outerFn(10, 20);alert(func1(10)); //返回15alert(func2(10)); //返回20

在調用func1(10)和func2(10)時,你引用到了兩個不同的i .這是怎麼回事?首先看下面的語句,

var func1 = outerFn(5,6);

調用outerFn (5, 6)的時候定義了一個新的函數對象innerFn,然後該函數對象成為了outerFn函數的使用中的物件的一個屬性.這時innerFn的範圍鏈是由outerFn的使用中的物件和全域對象組成的. 這個範圍鏈儲存在了innerFn函數的內部屬性[[scope]]中,然後返回了該函數,變數func1就指向了這個innerFn函數.

alert(func1(10));//返回15

在func1被調用時,它自身的使用中的物件被建立,然後添加到了[[scope]]中儲存著的範圍鏈的最前方(新的範圍鏈,並不會改變[[scope]]中儲存著的那個範圍鏈).這時的範圍鏈才是func1函數執行時用到的範圍鏈.從這個範圍鏈中,你可以看到變數‘i’的值實際上就是在執行outerFn(5,6)時產生的使用中的物件的屬性i的值.顯示了整個流程. 

                                                                                          

現在讓我們回到問題,"在調用func1(10)和func2(10)時,你引用到了兩個不同的i .這是怎麼回事?".讓我們從中看一下func2執行時的情況,答案就是在定義func1和func2時,函數outerFn中產生過兩個不同的使用中的物件.

現在又出現了一個問題, 一個使用中的物件在函數執行的時候建立,但在函數執行完畢返回的時候不會被銷毀嗎? 我用下面的三個例子來講解這個問題.

i) 沒有內建函式的函數                

function outerFn(x) {    return x * x;}var y = outerFn(2);

如果函數沒有內建函式,則在該函數執行時,當前使用中的物件會被添加到該函數的範圍鏈的最前端.範圍鏈是唯一引用這個使用中的物件的地方.當函數退出時,使用中的物件會被從範圍鏈上刪除,由於再沒有任何地方引用這個使用中的物件,則它隨後會被記憶體回收行程銷毀.

ii) 包含內建函式的函數,但這個內建函式沒有被外部函數之外的變數所引用

function outerFn(x) {    //在outerFn外部沒有指向square的引用     function square(x) {        return x * x;    }    //在outerFn外部沒有指向cube的引用    function cube(x) {        return x * x * x;    }    var temp = square(x);    return temp / 2;}var y = outerFn(5);

在這種情況下,函數執行時建立的使用中的物件不僅添加到了當前函數的範圍鏈的前端,而且還添加到了內建函式的範圍鏈中.當該函數退出時,使用中的物件會從當前函數的範圍鏈中刪除,使用中的物件和內建函式互相引用著對方,outerFn函數的使用中的物件引用著嵌套的函數對象square和cube,內建函式對象square和cube的範圍鏈中引用了outerFn函數的使用中的物件.但由於它們都沒有外部參考,所以都將會被記憶體回收行程回收.

iii)  包含內建函式的函數,但外部函數之外存在指向這個內建函式的引用

例1:

function outerFn(x) {    //內建函式作為outerFn的傳回值被引用到了外部    return function innerFn() {        return x * x;    }}//引用著返回的內建函式var square = outerFn(5);square();

例2:

var square;function outerFn(x) {    //通過全域變數引用到了內建函式    square = function innerFn() {        return x * x;    }}outerFn(5);square();

在這種情況下,outerFn函數執行時建立的使用中的物件不僅添加到了當前函數的範圍鏈的前端,而且還添加到了內建函式innerFn的範圍鏈中(innerFn的[[scope]]內部屬性).當外部函數outerFn退出時,雖然它的使用中的物件從當前範圍鏈中刪除了,但內建函式innerFn的範圍鏈仍然引用著它. 由於內建函式innerFn存在一個外部參考square,且內建函式innerFn的範圍鏈仍然引用著外部函數outerFn的使用中的物件,所以在調用innerFn時,仍然可以訪問到outerFn的使用中的物件上儲存著的變數x的值.

多個內建函式:

更有趣的情境是有不止一個的內建函式,多個內建函式的範圍鏈引用著同一個外部函數的使用中的物件.該使用中的物件的改變會反應到三個內建函式上.

function createCounter(i) {    function increment() {        ++i;    }    function decrement() {        --i;    }    function getValue() {        return i;    }    function Counter(increment, decrement, getValue) {        this.increment = increment;        this.decrement = decrement;        this.getValue = getValue;    }    return new Counter(increment, decrement, getValue);}var counter = createCounter(5);counter.increment();alert(counter.getValue()); //返回6

表示了createCounter函數的使用中的物件被三個內建函式的範圍鏈所共用.

閉包以及循環參考:

上面討論了JavaScript中函數的範圍鏈,下面談一下在閉包中可能出現因循環參考而產生記憶體流失的問題.閉包通常指得是能夠在外部函數外面被調用的內建函式.下面給出一個例子:

function outerFn(x) {    x.func = function innerFn() {}}var div = document.createElement("DIV");outerFn(div);

在上例中,一個DOM對象和一個JavaScript對象之間就存在著循環參考. DOM 對象div通過屬性‘func’引用著內建函式innerFn.內建函式innerFn的範圍鏈(儲存在內部屬性[[scope]]上)上的使用中的物件的屬性‘x’ 引用著DOM對象div. 這樣的循環參考就可能造成記憶體流失.

譯者注:猜測作者是為了使文章更易懂,故意不提及執行內容的概念,本文中出現[[scope]]內部屬性的地方也是我加的.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.