一何恶、概念
1捌议、詞法作用域就是定義在詞法階段的作用域哼拔。換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的瓣颅。是一套關(guān)于引擎如何尋找變量以及會在何處找到變量的規(guī)則倦逐。
2、遮蔽效應(yīng):在多層的嵌套作用域中可以定義同名的
標(biāo)識符宫补,即內(nèi)部的標(biāo)識符“遮蔽”了外部的標(biāo)識符
3檬姥、動態(tài)作用域:在運行時確定的,關(guān)注函數(shù)從何處調(diào)用(js沒有動態(tài)作用域)粉怕。(詞法作用域是寫代碼或者說定義時確定的健民,關(guān)注函數(shù)在何處聲明)
二、查找
1贫贝、作用域查找始終從運行時所處的最內(nèi)部作用域開始秉犹,逐級向外或者說向上進(jìn)行蛉谜,直到遇見第一個匹配的標(biāo)識符為止。
2崇堵、全局變量會自動成為全局對象(比如瀏覽器中的 window 對象)的屬性型诚,因此可以不直接通過全局對象的詞法名稱,而是間接地通過對全局對象屬性的引用來對其進(jìn)行訪問鸳劳。如 window.a 狰贯,通過這種技術(shù)可以訪問那些被同名變量所遮蔽的全局變量。但非全局的變量如果被遮蔽了赏廓,無論如何都無法被訪問到暮现。
三、欺騙詞法
1楚昭、:欺騙詞法作用域會導(dǎo)致性能下降栖袋。
2、實現(xiàn)欺騙詞法的兩種機(jī)制:
①抚太、eval(..):對一段包含一個或多個聲明的“代碼”字符串進(jìn)行演算塘幅,并借此來修改已經(jīng)存在的詞法作用域(在運行時)
function foo(str, a) {
eval( str ); // 欺騙!相當(dāng)于 var b = 3;
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
- eval(..) 調(diào)用中的 "var b = 3;" 這段代碼會被當(dāng)作本來就在那里一樣來處理
- 這段代碼實際上在 foo(..) 內(nèi)部創(chuàng)建了一個變量 b尿贫,并遮蔽
了外部(全局)作用域中的同名變量电媳。 console.log(..) 被執(zhí)行時,會在 foo(..) 的內(nèi)部同時找到 a 和 b庆亡,但是永遠(yuǎn)也無法找到外部的 b匾乓。因此會輸出“1, 3” - 實際上,eval(..) 通常被用來執(zhí)行動態(tài)創(chuàng)建的代碼
- 嚴(yán)格模式下,eval(..) 在運行時有其自己的詞法作用域又谋,意味著其中的聲明無法修改所在的作用域
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );
②拼缝、with:本質(zhì)上是通過將一個對象的引用當(dāng)作作用域來處理,將對象的屬性當(dāng)作作用域中的標(biāo)識符來處理彰亥,從而創(chuàng)建了一個新的詞法作用域(同樣是在運行時)咧七。
- with 通常被當(dāng)作重復(fù)引用同一個對象中的多個屬性的快捷方式,可以不需要重復(fù)引用對象本身
var obj = {
a: 1,
b: 2,
c: 3
};
// 單調(diào)乏味的重復(fù) "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 簡單的快捷方式
with (obj) {
a = 3;
b = 4;
c = 5;
}
- 盡管 with 塊可以將一個對象處理為詞法作用域任斋,但是這個塊內(nèi)部正常的 var聲明并不會被限制在這個塊的作用域中继阻,而是被添加到 with 所處的函數(shù)作用域中。
注:理解下面這個例子:
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 被泄漏到全局作用域上了瘟檩!
- 當(dāng)我們傳遞 o1 給 with 時,with 所聲明的作用域是 o1澈蟆,而這個作用域中含有一個同 o1.a 屬性相符的標(biāo)識符墨辛。
- 但當(dāng)我們將 o2 作為作用域時,其中并沒有 a 標(biāo)識符丰介,因此進(jìn)行了正常的 LHS 標(biāo)識符查找背蟆。 o2 的作用域、foo(..) 的作用域和全局作用域中都沒有找到標(biāo)識符 a哮幢,因此當(dāng) a=2 執(zhí)行時带膀,自動創(chuàng)建了一個全局變量(因為是非嚴(yán)格模式)
三、性能
JavaScript 引擎會在編譯階段進(jìn)行數(shù)項的性能優(yōu)化橙垢。其中有些優(yōu)化依賴于能夠根據(jù)代碼的詞法進(jìn)行靜態(tài)分析垛叨,并預(yù)先確定所有變量和函數(shù)的定義位置,才能在執(zhí)行過程中快速找到標(biāo)識符柜某。
如果引擎在代碼中發(fā)現(xiàn)了 eval(..) 或 with嗽元,無法在詞法分析階段明確知道 eval(..) 會接收到什么代碼,這些代碼會如何對作用域進(jìn)行修改喂击,也無法知道傳遞給 with 用來創(chuàng)建新詞法作用域的對象的內(nèi)容到底是什么剂癌。
引擎無法在編譯時對作用域查找進(jìn)行優(yōu)化,因為引擎只能謹(jǐn)慎地認(rèn)為這樣的優(yōu)化是無效的翰绊。使用這其中任何一個機(jī)制都將導(dǎo)致代碼運行變慢佩谷。
最悲觀的情況是如果出現(xiàn)了 eval(..) 或 with,所有的優(yōu)化可能都是無意義的监嗜,因此最簡單的做法就是完全不做任何優(yōu)化谐檀。