1. 程式人生 > >你不知道的JavaScript-2.詞法作用域

你不知道的JavaScript-2.詞法作用域

ons 創建 script 局部變量 變量 性能 ole function 導致

考慮以下代碼:

function foo(a) {
    var b = a * 2;
    function bar(c) {
        console.log( a, b, c );
    }
    bar( b * 3 );
}
foo( 2 ); // 2, 4, 12

在這個例子中有三個逐級嵌套的作用域。

  1. 包含著整個全局作用域,其中只有一個標識符: foo 。
  2. 包含著 foo 所創建的作用域,其中有三個標識符: a 、 bar 和 b 。
  3. 包含著 bar 所創建的作用域,其中只有一個標識符: c 。


全局變量會自動成為全局對象(比如瀏覽器中的 window 對象)的屬性,因此可以不直接通過全局對象的詞法名稱,而是間接地通過對全局對象屬性的引用來對其進行訪問。
window.a
通過方式可以訪問那些被同名局部變量所遮蔽的全局變量。但非全局的變量如果被遮蔽了,無論如何都無法被訪問到。


eval

JavaScript 中的 eval(..) 函數可以接受一個字符串為參數,並將其中的內容視為好像在書寫時就存在於程序中這個位置的代碼。換句話說,可以在你寫的代碼中用程序生成代碼並運行,就好像代碼是寫在那個位置的一樣。

考慮以下代碼:

function foo(str, a) {
    eval( str ); // 欺騙!
    console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

eval(..) 調用中的 "var b = 3;" 這段代碼會被當作本來就在那裏一樣來處理。
由於那段代碼聲明了一個新的變量 b ,因此它對已經存在的 foo(..) 的詞法作用域進行了修改。事實上,和前面提到的原理一樣,這段代碼實際上在 foo(..) 內部創建了一個變量 b ,並遮蔽了外部(全局)作用域中的同名變量。


with

with 通常被當作重復引用同一個對象中的多個屬性的快捷方式,可以不需要重復引用對象本身。

// 單調乏味的重復 "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 簡單的快捷方式
with (obj) {
    a = 3;
    b = 4;
    c = 5;
}

但實際上這不僅僅是為了方便地訪問對象屬性。考慮如下代碼:

function foo(obj) {
    with (obj) {
      a = 2;
    }
}
var o1 = {
    a: 3
};
var o2 = {
    b: 3
};
foo( o1 );
console.log( o1.a ); 
// 2 foo( o2 ); console.log( o2.a ); // undefined console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!

可以註意到一個奇怪的副作用,實際上 a = 2 賦值操作創建了一個全局的變量 a 。這是怎麽回事?

可以這樣理解,當我們傳遞 o1 給 with 時, with 所聲明的作用域是 o1 ,而這個作用域中含有一個同 o1.a 屬性相符的標識符。但當我們將 o2 作為作用域時,其中並沒有 a 標識符,因此進行了正常的 LHS 標識符查找.

o2 的作用域、 foo(..) 的作用域和全局作用域中都沒有找到標識符 a ,因此當 a=2 執行時,自動創建了一個全局變量(因為是非嚴格模式)。


eval、with 會導致引擎無法在編譯時對作用域查找進行優化,從而影響性能。

你不知道的JavaScript-2.詞法作用域