JavaScript中變量提升是語言設計缺陷


首先糾正下,文章標題里的 “變量提升” 名詞是隨大流叫法,“變量提升” 改為 “標識符提升” 更准確。因為變量一般指使用 var 聲明的標識符,JS 里使用 function 聲明的標識符也存在提升(Hoisting)。

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

 

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

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

報錯是自然的

 

二. 變量聲明在末尾

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

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

 

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

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

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

 

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

變量提升: 在指定作用域里,從代碼順序上看是變量先使用后聲明,但運行時變量的 “可訪問性 提升到當前作用域的頂部,其值為 undefined ,沒有 “可用性

 

通常為了讓大家理解起來容易些,把 三 拆成如下

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

即把聲明和賦值分為兩句。

 

這里強調 “代碼順序” 和 “運行順序”,是因為多數時候我們寫的代碼都是順序執行的,即 “代碼順序” 和 “運行順序” 是一致的。這也符合人的大腦的思維過程。比如有過 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里可以反過來寫,見二、三。

 

因此,有類 C語言 經驗的程序員,都很清楚變量需要 先聲明后使用,不然會報錯。而到了JS里,有 變量提升 現象,可以 先使用后聲明,C 的經驗用到 JS 里迷惑便出現了。

 

四、 函數表達式也存在變量提升

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,表示無可用性。

 

二、三、四 都是使用 var 聲明的變量,JS 里函數聲明也會存在提升,只是這個 “變量” 比較特殊,它是一個 function 類型(可以作為函數、方法或構造器)。它的名字(標識符)也會提升到當前作用域的頂部。

 

五、函數聲明的名也會提升到當前作用域頂部

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兩種方式差異


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM