我們活著的每一刻背后都隱藏著成千上萬個不一樣的瞬間。 ——《忽然七日》
大部分情況下,詞法作用域在編譯階段的第一步——詞法分析階段便已經被確定了。不過,也有少數的例外情況,比如說運用作用域欺騙的手法,便能夠改變這一現狀。
關于詞法作用域所需要注意的地方是:作用域查找會在找到第一個匹配的標識符時停止;無論函數在哪里被調用,也無論它如何被調用,它的詞法作用域只由函數被聲明時所處的位置決定;詞法作用域查找只會查找一級標識符,像foo.bar.baz,詞法作用域只會查找foo標識符,找到這個變量后,對象屬性訪問規則會分別接管對bar和baz屬性的訪問。
通過前段的介紹,我們得知常規情況下,詞法作用域完全由函數聲明時所處的位置決定,既然是這樣的話,那么有辦法使得詞法作用域所處的位置有運行時所處的狀態決定嗎?答案是有的,我們可以利用eval()和with()來進行詞法作用域欺騙達到在運行時修改詞法作用域所處的位置。下面將進行詳細介紹,不過丑話說在前頭,在程序中大量使用eval()和with()的話,那么將會使得程序的性能出現下滑。
1.關于eval()
JavaScript中的eval函數接收一個字符串,并且將其運行在eval語句所在的位置,這使得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,以至于輸出的是1,3而非2,3.
默認情況下,如果eval()中所執行的代碼包含有聲明時,那么他就會對eval()函數所處的詞法作用域進行修改。
注意,在嚴格模式下eval函數里面執行的函數在自己的作用域里面,意味著其中的聲明無法對eval函數所處的作用域進行影響,下面可以看一個例子:
function foo(str){
"use strict";
eval(str);
console.log(a);
}
foo("var a = 2");//ReferenceError
在JavaScript語言中,還有幾處地方接收字符串相當于產生函數的效果,他們分別為new Function(),setTimeout(),setInterval(),他們在立執行函數的多種寫法一文有略微介紹過。雖然他們可以這樣用,但是在實際編程中不推薦使用。
2.關于with
with通常用來被當作重復引用某一個對象的多個屬性的時候使用,這樣可以避免多次書寫這個對象名稱的繁瑣。但是使用with需要注意的一點時,它有可能會向全局作用域中泄露出變量。下面舉一個例子:
var obj = {"a": 0, "b":1, "c": 2};
var obj2 = {"b": 3, "c": 4};
function fun(obj){
with(obj){
a = 1;
b = 2;
c = 3;
}
}
fun(obj);
console.log(obj.a + ' ' + obj.b + ' ' + obj.c);//1 2 3
fun(obj2);
console.log(obj2.a + ' ' + obj2.b + ' ' + obj2.c);//undefined 2 3
console.log(window.a);//1此時a被泄露到了全局作用域中
在看一個例子:
function fun(obj){
with(obj){
var a = 9;
b=0;
}
console.log(a);
};
var obj = {"b":1};
fun(obj);//9
從第一個例子我們可以看出,with可能會泄露出變量到全局作用域里面去,對此我們的解釋是:符合作用域查找的規則,分析可得這是一個LHS作用域查找請求,所以根據作用域查找規則,如果在所有除全局作用域的地方都找不到這個變量的話,那么在非嚴格模式下將會在全局作用域中創建這個變量。所以當with查詢的那個對象沒有某個屬性的時候,那么它將會作為變量名泄露到全局作用域中。從第二個例子我們可以看出:在with代碼塊中利用var來定義一個變量的話,那么這個變量所屬的作用域將會是with()被調用處所在的作用域。很可能這個時候,你會問如果利用let和const定義變量的話還會是這樣的嗎,答案是當然不會了,利用let和const定義變量的話會強制把變量的作用域定位所在的函數塊中。
關于欺騙詞法作用域所引起的性能問題:我們知道,JavaScript引擎會在編譯階段進行數項的性能優化。而如果我們在程序中加入了大量的eval()和with(){}的話,那么將會影響引擎對代碼優化的一部分工作,使得程序運行起來慢許多。
END