JavaScript中的做用域與變量提高

如下來自[1]:javascript

在JavaScript中,變量有4種基本方式進入做用域:html

1 語言內置:全部的做用域裏都有this和arguments;java

2 形式參數:函數的形參會做爲函數體做用域的一部分;程序員

3 函數聲明:像這種形式:function foo(){};面試

4 變量聲明:像這樣:var foo;安全


ECMAScript參考文檔中關於做用域和變量提高的描述:
函數

若是變量在函數體類聲明,則它是函數做用域。不然,它是全局做用域(做爲global的屬性)。變量將會在執行進入做用域的時候被建立。塊不會定義新的做用域,只有函數聲明和程序(譯者覺得,就是全局性質的代碼執行)纔會創造新的做用域。變量在建立的時候會被初始化爲undefined。若是變量聲明語句裏面帶有賦值操做,則賦值操做只有被執行到的時候纔會發生,而不是建立的時候。this


如下來自[2]:
spa

首先糾正下,文章標題裏的 「變量提高」 名詞是隨大流叫法,「變量提高」 改成 「標識符提高」 更準確。由於變量通常指使用 var 聲明的標識符,JS 裏使用 function 聲明的標識符也存在提高(Hoisting)。.net

JS 存在變量提高,這個的設計實際上是低劣的,或者是語言實現時的一個反作用。它容許變量不聲明就能夠訪問或聲明在後使用在前。新手對於此則很迷惑,甚至許多使用JS多年老手也比較迷惑。但在 ES6 加入 let/const 後,變量Hoisting 就不存在了。

1、 變量未聲明,直接使用

function test() {
    alert(notDefined);
}
test(); // ?

報錯是天然的

JavaScript中變量提高是語言設計缺陷 - 第1張  | Joben

 二. 變量聲明在末尾

function test() {
    alert(declaredButNotAssigned); // undefined
    var declaredButNotAssigned;
}
test();

輸出 undefined, 結果比上例有所改善,沒有報錯,代碼能夠運行,但變量值可能不是程序員所指望的。

3、 變量聲明在末尾,同時給變量賦值

function test() {
    alert(declaredAndAssigned); // undefined
    var declaredAndAssigned = 1;
}
test();

結果和 二 相同, 很明顯,並不會由於賦值了就輸出 1。

2、三 都發生了變量提高(Hoisting),簡單定義

變量提高: 在指定做用域裏,從代碼順序上看是變量先使用後聲明,但運行時變量的 「可訪問性 提高到當前做用域的頂部,其值爲 undefined ,沒有 「可用性

 這裏強調 「代碼順序」 和 「運行順序」,是由於多數時候咱們寫的代碼都是順序執行的,即 「代碼順序」 和 「運行順序」 是一致的。這也符合人的大腦的思惟過程。好比有過 C語言 經驗的程序員

#include <stdio.h>
int main() {
	int x = 1;
	printf("%d, ", x); // 1
}

兩句代碼,先聲明整數型 x, 再輸出。代碼順序和運行順序是一致的,即正常運行。

 若是順序反過來

#include <stdio.h>
int main() {
	printf("%d, ", x); // error
	int x = 1;
}

此時,編譯都不能經過了。但JS裏能夠反過來寫,見2、三。

 所以,有類 C語言 經驗的程序員,都很清楚變量須要 先聲明後使用,否則會報錯。而到了JS裏,有 變量提高 現象,能夠 先使用後聲明,C 的經驗用到 JS 裏迷惑便出現了。

4、 函數表達式也存在變量提高

function test() {
    alert(func); // undefined
    var func = function() {};
}
test();

 但若是想使用這個 func,則無可能

function test() {
    alert(func); // undefined
    func(); // 報異常
    var func = function() {};
}
test();

結果func 是 undefined,調用 func 則會報異常。 在上面的定義中提到了 可訪問性 和 可用性 對應以下語句。

可訪問性:alert(func),輸出 undefined,能夠運行,能夠訪問 func。

可用性:   func(), 報異常,不能正常調用 func,表示無可用性。

 2、3、四 都是使用 var 聲明的變量,JS 裏函數聲明也會存在提高,只是這個 「變量」 比較特殊,它是一個 function 類型(能夠做爲函數、方法或構造器)。它的名字(標識符)也會提高到當前做用域的頂部。

5、函數聲明的名也會提高到當前做用域頂部

function test() {
    alert(f1); // function
    f1(); // "called"
    function f1() {
        alert('called');
    }
}
test();

咱們看到,聲明 f1 在代碼最末,f1 使用在前,alert(f1) 和 f1() 都正常執行,表示 可訪問性 和 可用性 都有了。

前面說了,變量提高(Hoisting)沒什麼用,屬於語言的低劣設計,好的習慣仍是 「先聲明後使用」。這個特性也會出如今很多大公司面試題裏

題1:

// 寫出如下代碼的運行結果
var a = 1;
function fn() {
	if (!a) {
		var a = 2;
	}
	alert(a); // ?
}
fn();

 題2:

// 寫出如下代碼的運行結果
var a = 1;
function fn() {
	a = 2;
	return;
	function a() {}
}
fn();
alert(a); // ?

 但這一切隨着 ES6 的 let/const 到來結束了,ES裏除全局變量外,其它都使用 let/const,var 替換成 let 後變量提高就不復存在了。

function test() {
    alert(declaredButNotAssigned1); // 報異常
    alert(declaredButNotAssigned2); // 報異常
    alert(func); // 報異常
 
    let declaredButNotAssigned1;
    let declaredButNotAssigned2 = true;
    let func = function() {};
}
test();

這強制程序員養成好的習慣,變量須要「先聲明再使用」,不然報錯。

如下摘自MDN的關於let不在發生變量提高的描述

In ECMAScript 6, let does not hoist the variable to the top of the block. If you reference a variable in a block before the let declaration for that variable is encountered, this results in a 

ReferenceError, because the variable is in a 「temporal dead zone」 from the start of the block until the declaration is processed. 

用 let 聲明變量後,typeof 也再也不安全

if (condition) {
    alert(typeof num); // Error!
    let num = 100;
}

 之前能夠用 typeof == ‘undefined’,來判斷是否引入了某lib,好比jQuery

// 判斷jQuery是否引入了
if (typeof $ !== 'undefined') {
    // do something
}...

jQuery沒有引入,$ 沒有聲明,這句也不會報錯而影響到下面的代碼執行,但若是是 let 聲明的就會報錯了。

相關:

Temporal_dead_zone_and_errors_with_let

why-typeof-is-no-longer-safe

JavaScript判斷變量是否爲undefined兩種方式差別

參考文檔:

[1] Javascript做用域和變量提高, http://blog.csdn.net/sunxing007/article/details/9034253

[2] JavaScript中變量提高是語言設計缺陷, http://www.cnblogs.com/snandy/p/4552078.html

相關文章
相關標籤/搜索