1. 程式人生 > >讀書筆記《你不知道的JavaScript上卷》1.2詞法作用域

讀書筆記《你不知道的JavaScript上卷》1.2詞法作用域

詞法作用域

今天的讀書筆記是JavaScript中的詞法作用域,希望對大家有所幫助。


2.1 定義

詞法作用域: 詞法作用域就是定義在詞法階段的作用域,它由寫程式碼時將變數和塊作用域寫在哪裡來決定的。

坦白說這個定義第一句話是廢話!重點是後一句話,它說作用域是由變數和塊作用域寫在哪裡來決定的,也就是說它的作用域不是由(通常為函式)呼叫的地方來決定,而是由作用域宣告的地方來決定,即“無論函式在哪裡被呼叫,也無論它如何被呼叫,它的詞法作用域都只是由函式被宣告時所處的位置決定”,這裡給出下面的例子:

function foo(){
    console.log(a);//列印1
} function bar(){ var a = 2; foo(); } var a = 1; bar();

上述程式碼很簡單,函式bar中定義了一個變數a其值為2並且呼叫了另一個函式foo,函式foo做的事情就是把a列印了一下。那麼最後列印的a的值是多少呢?上述程式碼已給出了結果,是1。當函式bar呼叫函式foo的時候,會列印a,結果foo這個函式的作用域中並沒有a這個變數,那麼根據作用域冒泡原則它會在上一作用域中查詢,上面詞法作用域定義中說到”詞法作用域由作用域定義的地方來決定”,foo定義在全域性作用域中,也就是它冒泡會冒到全域性作用域中,全域性作用域中有a這麼個變數,並且值是1,所以它列印1。從本例中可以看出JavaScript中的作用域並不是基於程式碼中作用域的巢狀。

2.2 瀏覽器中呼叫某個全域性變數

如果在某個作用域中想要呼叫全域性的某個變數那麼該怎麼辦呢?瀏覽器中給出了一個window物件,所有的全域性變數都是屬於該物件的屬性。

var a = 3;
console.log(a);//列印3
console.log(window.a);//列印3

2.3 詞法欺騙

JavaScript中作用域由其宣告的位置來決定,那麼有沒有不符合這種情況的時候呢?答案是有的,主要有兩種情況:一個是eval函式,另一個是with語句。

2.3.1 eval函式

eval函式可以傳遞一個字串的引數,然後呼叫該函式時會把函式的引數當做程式碼來執行:

eval
("alert(123);");//瀏覽器會彈出123的對話方塊

現在考慮如下程式碼:

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

上述程式碼中按照正常的詞法作用域的情況,foo函式中並沒有宣告變數b它會去上一作用域中查詢也就是全域性作用域,裡面有變數b值為2,但是由於eval相當於動態插入了一行程式碼“var b = 3;”它把b的值變為了3,所以列印了3,這跟正常情況不符。

在嚴格模式下eval有自己的作用域,它無法修改該函式所在作用域的值:

"use strict"//嚴格模式
eval("var a = 1;");
console.log(a);//Uncaught ReferenceError: a is not defined

最後八卦一下這個eval函式,它還有一個用途就是解析json報文,將字串的json報文轉換為json物件,現在應該知道它為什麼會是這樣了吧,因為它會把該字串的json按照JavaScript程式碼來處理,而程式碼中它就是一個json物件。

var a = eval("("+"{name:'javascript'}" + ")");
console.log(a.name);

不過上述程式碼為什麼要加括號呢?如果不加的話JavaScript會把裡面的花括號當做語句中的花括號而不能解析json。不過最好不要用這種方法解析,好一點的做法是用JSON物件或JQuery提供的方法。

2.3.2 with語句

with語句通常被當做重複引用同一個物件中的多個屬性的快捷方式,可以不需要重複定義引用的物件,具體如下:

var obj = {
    a:1,
    b:2
};
//重複呼叫obj的賦值
obj.a = 2;
obj.b = 3;
//使用with語句的賦值
with(obj){
    a = 3;
    b = 4;
}

with語句同樣會產生詞法欺騙現象:

function foo(obj){
    with(obj){
        a = 2;
    }
}
var o1 = {
    a:3
};
var o2 = {
    b:3
};
foo(o1);
console.log(o1.a);//列印2

foo(o2);//注意這裡的o2並沒有a這麼個屬性
console.log(o2.a);//列印undefined
console.log(a);//列印2        

上述結果很讓人費解,為什麼是這樣呢?其實with實際上是根據你傳遞給它的物件憑空建立一個新的作用域。對於上述o2物件來說,它沒有a這個屬性,也就是這個新的作用域中沒有這個變數,那麼將會向上一作用域中查詢該變數,如果找到了那麼就使用改變數的值,如果沒有找到的話那麼繼續向上找。上述程式碼中很顯然直到全域性作用域都沒有找到,那麼它就在最外層的作用域建立了一個變數a並賦值為2,於是就有了這種不安全的現象。

由於with的不安全性,在嚴格模式下with被禁用了

2.3 效能

像eval和with這樣的詞法欺騙會使得效能下降,而且使用起來也是相當不安全的,所以我建議大家最好不要使用它們!