理解 JavaScript 做用域

上一篇文章中分析了 JS 中的數據類型和變量。這一篇文章將分析做用域,以及回答上一篇文章中變量提高的緣由。前端

什麼是做用域

做用域是一套規則,保存着變量,等待被引擎所查找。segmentfault

var a = 1;
console.log(a);  // => 1
console.log(b);  // => ReferenceError

當打印 a 時,引擎就去做用域中查找 a,找到把結果返回。若是查找失敗,那麼就會報錯。閉包

詞法做用域

JS 採用的詞法做用域,也能夠說是靜態做用域。簡單來講,詞法做用域是由寫代碼時將變量寫在哪裏決定的。函數

先看一段代碼:學習

var a = 1;

function fn() {
    var a = 2;
    return a;
}

fn();  // => 2

當執行函數 fn 時,會返回 2,而不是 1。spa

做用域查找

JS 引擎會進行兩種查找,LHS 和 RHS。怎麼理解?L 和 R 能夠說表明左和右。什麼的左和右?賦值操做的。code

這裏的賦值操做不必定出現 =,好比參數傳遞也是一個賦值操做。ip

當變量出如今賦值操做的左邊時,引擎就會對這個變量進行 LHS 查找;當出如今右邊時(這個還能夠理解爲取得變量的源值),就會進行 RHS 查找。作用域

function foo(a) {
    console.log(a);
}

foo(2);

對於變量 a 來講,引擎會進行兩次查找,1 次 LHS,1 次 RHS。rem

調用 foo(),並傳入參數 2,這時存在着一個賦值操做即 a = 2,進行一次 LHS 查找。打印 a 時,須要獲取 a 的源值,因此進行一次 RHS 查找。

若是查詢失敗呢?

對於 LHS 來講,給未聲明的賦值就會查詢失敗。

a = 2;

可是咱們知道,上面的代碼在非嚴格模式下並不會報錯,而變量 a 會被自動建立。

而對於 RHS 來講,直接使用未聲明的變量就會報 ReferenceError。

console.log(a); // => ReferenceError

另外,RHS 雖然查詢成功,可是卻對查詢結果進行非法操做,就會報 TypeError。

var foo = 1;
foo(); // => TypeError

做用域鏈

前面說,做用域是根據名稱查找變量的一套規則。而在實際狀況中,常常出現多個做用域嵌套的狀況。

function foo(a) {
    console.log(a + b);
}
var b = 2;
foo(2); // => 4

當引擎對 b 進行 RHS 查找時,在當前做用域沒法找到,引擎就會在外層做用域中查找,直到找到這個變量,或者直到抵達最外層做用域(全局做用域)爲止。

LHS 查找也是如此。

把這樣一層一層嵌套的做用域,叫作做用域鏈。

函數做用域

函數做用域是指,屬於這個函數的所有變量均可以在這個函數的範圍內使用及複用。

function foo() {
    var a = 1;
}

console.log(a); // => ReferenceError

也就是說,函數外部將沒法訪問函數內部的變量。

可是這倒是很是有用的。咱們能夠利用函數隱藏內部實現,使其外部沒法訪問、修改等。

當即執行函數表達式

利用函數做用域,能夠將外部做用域沒法訪問的內容包裝起來。可是,帶來了額外的一個問題,函數名自己「污染」了所在的做用域。

這時,就提出了 IIFE(當即執行函數表達式)。

(function foo() {
    // ...
}());

即包裝了內部函數,又避免了引入函數名。由於這個函數名沒法被外部做用域所訪問。

IIFE 的進階用法是給其傳入參數:

(function fn(global) {
    // ...
})(window);

這樣的好處是能夠縮短查詢時的做用域鏈。

塊做用域

ES6,經過 let 和 const 引入了塊做用域。

if (true) {
    let a = 1;
}
console.log(a); // => ReferenceError

變量提高

上一篇文章中中提到了變量提高。

在 JS 中,var a = 1; 這行代碼其實會被當作 var aa = 2,並在兩個階段去執行。

在編譯階段,執行聲明操做;在執行階段,執行賦值操做。

全部的變量聲明都會被提高到做用域的頂部,這個過程叫作「提高」。

函數聲明也會發生提高,而且函數聲明會先於變量提高:

var foo = 1;
function foo () {}

typeof foo; // => 'number'

注意,只有函數聲明會被提高,而函數表達式不會被提高。

var foo = 1;
var foo = function () {}

typeof foo; // => 'function'

小結

這篇文章梳理了 JavaScript 中做用域的基本知識。

接下來會介紹執行上下文和閉包這兩個概念,它們與做用域息息相關。

關於

這是個人公衆號,記錄着個人前端博客,沒事兒也分享一些電影、書籍。

歡迎一塊兒交流學習。

相關文章
相關標籤/搜索