做用域
被定義爲一套規則,這套規則用來管理引擎
如何在當前做用域以及嵌套的子做用域中根據標識符名稱進行變量查找
。react
做用域共有兩種主要的工做模型。第一種是最爲廣泛的,被大多數編程語言所採用的詞法做用域,咱們會對這種做用域進行深刻討論。另一種叫做動態做用域。git
大部分標準語言編譯器的第一個工做階段叫做詞法化(也叫單詞化),詞法化的過程會對源代碼中的字符進行檢查,若是是有狀態的解析過程,還會賦予單詞語義。github
簡單地說,詞法做用域
就是定義在詞法階段的做用域
。換句話說,詞法做用域
是由你在寫代碼時將變量和塊做用域寫在哪裏來決定的
,所以當詞法分析器處理代碼時會保持做用域 不變(大部分狀況下是這樣的)。編程
看下面的例子性能優化
// (1) 包含着整個全局做用域,其中只有一個標識符:foo。
function foo (a) {
// (2) 包含着 foo 所建立的做用域,其中有三個標識符:a、bar 和 b。
var b = a * 2;
function bar (c) {
// (3) 包含着 bar 所建立的做用域,其中只有一個標識符:c。
console.log(a, b, c);
}
bar(b * 3)
}
foo(2);
複製代碼
做用域氣泡的結構和互相之間的位置關係給引擎提供了足夠的位置信息,引擎用這些信息來查找標識符的位置。bash
在上一個代碼片斷中,引擎執行 console.log(..) 聲明,並查找 a、b 和 c 三個變量的引 用。它首先從最內部的做用域,也就是 bar(..) 函數的做用域氣泡開始查找。引擎沒法在 這裏找到 a,所以會去上一級到所嵌套的 foo(..) 的做用域中繼續查找。在這裏找到了 a, 所以引擎使用了這個引用。對 b 來說也是同樣的。而對 c 來講,引擎在 bar(..) 中就找到 了它。編程語言
做用域查找會在找到第一個匹配的標識符時中止。在多層的嵌套做用域中能夠定義同名的 標識符,這叫做「遮蔽效應」
(內部的標識符「遮蔽」了外部的標識符)。拋開遮蔽效應, 做用域查找始終從運行時所處的最內部做用域開始,逐級向外或者說向上進行,直到碰見 第一個匹配的標識符爲止。函數
若是詞法做用域徹底由寫代碼期間函數所聲明的位置來定義,怎樣才能在運行時來「修 改」(也能夠說欺騙)詞法做用域呢?post
JavaScript 中有兩種機制來實現這個目的。社區廣泛認爲在代碼中使用這兩種機制並非 什麼好注意。可是關於它們的爭論一般會忽略掉最重要的點:欺騙詞法做用域會致使性能 降低。性能
在執行 eval(..) 以後的代碼時,引擎並不「知道」或「在乎」前面的代碼是以動態形式插 入進來,並對詞法做用域的環境進行修改的。引擎只會如往常地進行詞法做用域查找。
function foo (str, a) {
eval(str) // 詞法欺騙
console.log(a, b);
// 此處的 b 永遠也沒法找到外部的b
// 由於此處內部建立了一個 b 而且覆蓋了外部的b
}
var b = 2;
foo('var b = 3; ', a)
// 注意 eval 語法在嚴格模式下有本身的做用域,因此沒法修改做用域
複製代碼
JavaScript 中另外一個難以掌握(而且如今也不推薦使用)的用來欺騙詞法做用域的功能是 with 關鍵字。能夠有不少方法來解釋 with,在這裏我選擇從這個角度來解釋它:它如何同 被它所影響的詞法做用域進行交互。
with 一般被看成重複引用同一個對象中的多個屬性的快捷方式,能夠不須要重複引用對象自己
。
var obj = {
a: 1,
b: 2,
c: 3
};
// 重複編寫 "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 簡單的快捷方式
with (obj) {
a = 3;
b = 4;
c = 5;
}
console.log(obj) // 3, 4, 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 被泄漏到全局做用域上了!
複製代碼
此段代碼爲何 o2.a 爲 undefined 呢,咱們能夠這樣理解。
當咱們傳遞 o1 給 with 時,with 所聲明的做用域是 o1,而這個做用域中含 有一個同 o1.a 屬性相符的標識符。
但當咱們將 o2 做爲做用域時,其中並無 a 標識符,所以進行了正常的 LHS 標識符查找,在非嚴格模式下 LHS 引用在全局環境下建立了一個 a 而且賦值爲 2
eval(..) 和 with 會在運行時修改或建立新的做用域,以此來欺騙其餘在書寫時定義的詞法做用域。
可是,JavaScript 引擎本來會在編譯階段進行數項的性能優化。其中有些優化依賴於可以根據代碼的詞法進行靜態分析
,並預先肯定全部變量和函數的定義位置,才能在執行過程當中快速找到標識符
。
但若是引擎在代碼中發現了 eval(..) 或 with,它只能簡單地假設關於標識符位置的判斷 都是無效的,由於沒法在詞法分析階段明確知道 eval(..) 會接收到什麼代碼,這些代碼會 如何對做用域進行修改,也沒法知道傳遞給 with 用來建立新詞法做用域的對象的內容到底 是什麼。
最悲觀的狀況是若是出現了 eval(..) 或 with,全部的優化可能都是無心義的,所以最簡單的作法就是徹底不作任何優化。