1. 程式人生 > >讀書筆記《你不知道的JavaScript上卷》1.1作用域是什麼

讀書筆記《你不知道的JavaScript上卷》1.1作用域是什麼

作用域是什麼

JavaScript是一門非常神奇的程式語言,以至於你沒有掌握它的精髓都可以寫出完整的專案,本套讀書筆記記錄的就是《你不知道的JavaScript上卷》中的內容,其中會夾雜這我個人對某些知識點的感悟與見解。


1.1 編譯器與引擎

在討論作用域是什麼的時候不得不講講另外兩個概念,一個是編譯器另一個是引擎

  • 編譯器 負責語法分析及程式碼生成等工作
  • 引擎 負責整個JavaScript程式的編譯及執行的過程

看了這兩個概念後相信大家跟我一樣也是一頭霧水,這兩個東西到底是幹什麼的?簡單的講就是對於一行程式碼:

var a = 2;

這行程式碼在解析與執行的時候並不是按一次來處理,而是分兩次來處理。

  • 首先,編譯器會判斷當前作用域中有沒有宣告過該變數,如果宣告過了則忽略該語句,如果沒有生成過則宣告該變數
  • 其次,在執行的時候引擎會在作用域中查詢該變數,如果找到了就賦值

所以上述程式碼可以看成是兩行程式碼:

var a;//這部分由編譯器來處理,先宣告一個變數
a=2;//這部分由引擎來處理,給變數賦值

通常情況下我們程式猿不需要知道什麼是編譯器什麼是引擎,用一個高大上的詞來說就是他們對我們來說是透明的,但是我們一定要知道在處理宣告和賦值的時候是分為兩個過程,這在就是JavaScript中變數提升

(後續筆記中會講到)的原因。

1.2 作用域

上一小節談到了是JavaScript宣告和賦值是兩個過程,本章節就是圍繞這兩個過程來展開,首先讓我們談談什麼是作用域。

作用域: 通常來說,一段程式程式碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的程式碼範圍就是這個名字的作用域。 —— [ 百度百科 ]

簡單的說作用域就是變數的有效範圍。JavaScript最常見的作用域就是函式作用域。就比如:

function foo(){
    var a = 3;
}
console.log(a);//這裡找不到變數a因為它在自己的作用域中,即函式foo所形成的作用域

變數a所在函式foo的作用域中,而外面列印a的時候,在他所處的作用域中並沒有a這麼一個變數,所以會報ReferenceError這樣的錯誤。

1.3 作用域鏈

在JavaScript中有兩大鏈是我們必須要掌握的,其中一個就是作用域鏈,另一個就是原型鏈。我們這裡首先講的是作用域鏈,它是一個非常重要的概念。

作用域鏈:引擎從當前的執行作用域開始查詢變數,如果找不到,就向上一級繼續查詢,再找不到就繼續向上一級中找,直到找到該變數或者到達最外層的作用域(即全域性作用域)。

這句話,什麼意思呢?上程式碼:

var a = 3;
function bar(){
    var a = 2;//如果這一行註釋掉那麼將會列印3
    function foo(){
        console.log(a);//列印2    
    }
    foo();
}
bar();

在講這段程式碼之前大家首先要知道JavaScript中函式是可以巢狀的,像上面的bar函式中嵌套了一個foo的函式一樣,這一點不同於Java等某些程式語言。
上述程式碼呼叫了bar函式,該函式內部又會呼叫函式foo,而foo內部列印了a變數,但是函式foo內部(即函式foo作用域中)並沒有宣告過a這個變數,那麼它會去上一作用域中查詢這個變數,即他外層的函式bar的函式作用域,這個作用域中有a的宣告,並且值是2,那麼foo函式列印的值就是2了。如果bar中的語句”var a = 2;”是不存在的那麼在bar作用域中還是沒有找到,那麼它就會繼續去上一作用域也就是最外層的全域性作用域中查詢,則會找到a=3,那麼這種情況下將會列印3了。

1.4 異常

相信上小節討論作用域鏈的時候很多人會問那如果到了最外層的全域性作用域還沒有找到那麼會發生什麼事。本節就討論一下這種情況。

如果查詢完所有巢狀的作用域中都沒有找到所需要的變數的宣告和賦值語句,引擎就會丟擲出ReferenceError。

什麼意思呢?如下:

function bar(){
    function foo(){
        console.log(a);//沒有找到a,丟擲ReferenceError 
    }
    foo();
}
bar();

上述程式碼中沒有找到a這個變數的宣告和賦值語句就會丟擲ReferenceError。

如果查詢完所有巢狀的作用域中找到了所需要的變數賦值語句但沒有宣告語句,全域性作用域就會建立一個該變數,並返回給引擎。

這句話又是什麼意思呢?如下:

function bar(){
    a = 1;//找到賦值了 但全域性都沒有宣告,所以最外層的全域性作用域會建立一個該變數,故列印1
    function foo(){
        console.log(a);//有a的賦值返回1
    }
    foo();
}
bar();

上述程式碼中沒有找到a這個變數賦值語句了,但沒有宣告所以最外層的全域性作用域會“友好“地建立一個該變數a,故列印1。
但是有賦值語句這種情況還有另外一種情況。
ES5中引入了一種嚴格模式,顧名思義嚴格模式是因為它對語法的檢查更為嚴格,這種模式下禁止自動或隱式地建立全域性變數。對於這種情況如果查詢完所有巢狀的作用域中只要沒有找到所需要變數的宣告語句無論是否找到賦值語句,引擎都會丟擲ReferenceError。我們給上面程式碼加上嚴格模式後效果如下:

"use strict";//有了這行神奇的程式碼編譯器就知道你使用的是嚴格模式了
function bar(){
    a = 1;//找遍所有的作用域都沒有找到a的宣告語句,只有賦值語句
    function foo(){
        console.log(a);//同樣丟擲ReferenceError
    }
    foo();
}
bar();

說到這裡這節的筆記就接近尾聲了,最後簡紹一下另外一個與ReferenceError同樣高頻的一個錯誤叫做TypeError。

TypeError: 當給一個變數做違反該型別的操作時就會丟擲該異常。

這個異常太常見了就比如給一個非函式型別的值進行函式呼叫,或者呼叫undefined的某個屬性呀(umdefined沒有object型別中的屬性)等等都會發生這個異常,如下,程式碼都會報TypeError錯誤:

var a = 1;
a();//a是number型別的不能進行函式引用
var b
console.log(b.name);//b沒有賦值,是undefined型別,該型別沒有name這麼個屬性