一般來講JavaScript是一門「動態」或者「解釋執行」語言,但事實上它是一門編譯語言
,晦澀的編譯原理咱就不說了(我也不懂),直接說一下JavaScript的編譯狀況。對於JavaScript來講,大部分狀況下編譯發生在代碼執行前幾微秒的時間內
。
最簡單的一段JavaScript的代碼:segmentfault
var a = 2;
編譯器對於這行代碼會進行兩個步驟的處理:函數
//變量聲明 var a; //賦值操做 a = 2;
這個過程當中會涉及到變量提高的問題。性能
編譯器遇到var a,會檢查當前的做用域中是否有a的聲明。若是有,那麼就會忽略掉a的聲明,若是沒有就會在當前的做用域中聲明一個新的變量,並命名爲a。翻譯
接下來編譯器就把a = 2這條語句翻譯成機器代碼等待運行。引擎運行時會查找當前的做用域中是否有一個名字爲a的變量。若是有就使用;若是沒有,引擎會到上層的做用域中查找,直到全局做用域中。code
var a = 2; //這裏是一個LHS引用 console.log(a); //這裏是一個RHS引用
乍一看LHS就是‘=’的左邊,RHS就是‘=’的右邊。但我對LHS的理解是我把值放到哪裏
,RHS是我去哪裏找我要的值
。爲何要區分RHS和LHS,由於在變量尚未聲明的時候,這兩種查詢的行爲是不同的。對象
function foo(a){ console.log(a+b); b = a; } foo(2);
運行時,第一次對b
進行的是RHS的查詢,引擎在全部做用域中都找不到,最後會拋出一個ReferenceError
的錯誤。而對於b=a
而言,b
進行的是LHS查詢,若是在全局做用域中都找不到,那麼就會在全局做用域中建立一個變量b。(前提是代碼運行在非嚴格模式
)。
接下來,若是RHS查詢到了一個變量,但你嘗試對這個變量的值進行不合理的操做。好比,對一個非函數類型的值進行函數調用,那麼引擎會拋出TypeError
。ReferenceError
與做用域判別失敗相關,TypeError
則表明了做用域判別成功,但對結果的操做是非法或不合理的。ip
詞法做用域
就是定義在詞法階段的做用域。更通俗的說法是詞法做用域是你書寫代碼的順序決定的。例如以下代碼:作用域
function foo(a){ var b = a*2; function bar(c){ console.log(a,b,c); } bar(b*3); } foo(2);
在全局做用域中只有一個變量foo;在foo的做用域中有變量a,b和函數bar;在bar的做用域中有變量c。這種層層嵌套的關係是在書寫時就已經決定了。動態做用域
就是在程序運行的時候才能肯定的做用域。JavaScript中有兩種實現方式eval
和with
。但這兩種方式都會致使性能的降低,因此仍是少用。字符串
JavaScript中的eval函數能夠接受一個字符串的參數,並將其中的內容視爲好像在書寫的時候就存在於程序中這個位置同樣。例如以下代碼:get
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變量。
with表面上是一種引用同一個對象不一樣屬性的快捷方式
,但更重要的是它會建立一個新的做用域
,例如以下代碼:
//with體現了訪問對象的快捷 var obj = { a:1, b:2, c:3 }; //不使用with obj.a = 2; obj.b = 3; obj.c = 4; //使用with with(obj){ a = 2; b = 3; c = 4; }
建立新的做用域能夠看以下代碼:
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爲何會暴露在全局做用域中?
當執行foo(o1)時,由於使用了with,因此會建立一個全新的做用域,命名爲o1。o1對象裏的屬性會變成o1做用域裏變量。當執行a=2時,能夠在o1的做用域中找到a,因此就修改了a的值。當執行foo(o2)時,o2的做用域中沒有a變量,因此會執行LHS的查詢,最終會在全局做用局中聲明一個變量a,因此就致使了a暴露在全局做用域中。