前端 let、const和var你真的瞭解麼?

zhaoeq發表於2022-02-07

1.var 關鍵字

要定義變數,可以使用 var 操作符(注意 var 是一個關鍵字),後跟變數名(即識別符號,如前所述):

var message;

這行程式碼定義了一個名為 message 的變數,可以用它儲存任何型別的值。(不初始化的情況下,變

量會儲存一個特殊值 undefined)ECMAScript 實現變數初始化,因此可以同時定義變數並設定它的

值:


var message = "hi";

這裡,message 被定義為一個儲存字串值 hi 的變數。像這樣初始化變數不會將它標識為字串

型別,只是一個簡單的賦值而已。隨後,不僅可以改變儲存的值,也可以改變值的型別:


var message = "hi";

message = 100; // 合法,但不推薦

在這個例子中,變數 message 首先被定義為一個儲存字串值 hi 的變數,然後又被重寫為儲存了

數值 100。雖然不推薦改變變數儲存值的型別,但這在 ECMAScript 中是完全有效的。

1. var 宣告作用域

關鍵的問題在於,使用 var 操作符定義的變數會成為包含它的函式的區域性變數。比如,使用 var

在一個函式內部定義一個變數,就意味著該變數將在函式退出時被銷燬:

function test() {

var message = "hi"; // 區域性變數

}

test();

console.log(message); // 出錯!

這裡,message 變數是在函式內部使用 var 定義的。函式叫 test(),呼叫它會建立這個變數並給

它賦值。呼叫之後變數隨即被銷燬,因此示例中的最後一行會導致錯誤。不過,在函式內定義變數時省

略 var 操作符,可以建立一個全域性變數:

function test() {

message = "hi"; // 全域性變數

}

test();

console.log(message); // "hi"

去掉之前的 var 操作符之後,message 就變成了全域性變數。只要呼叫一次函式 test(),就會定義

這個變數,並且可以在函式外部訪問到。

2. var 宣告提升

使用 var 時,下面的程式碼不會報錯。這是因為使用這個關鍵字宣告的變數會自動提升到函式作用域

頂部:

function foo() {

console.log(age);

var age = 26;

}

foo(); // undefined

之所以不會報錯,是因為 ECMAScript 執行時把它看成等價於如下程式碼:

function foo() {

var age;

console.log(age);

age = 26;

}

foo(); // undefined

這就是所謂的“提升”(hoist),也就是把所有變數宣告都拉到函式作用域的頂部。此外,反覆多次

使用 var 宣告同一個變數也沒有問題:

function foo() {

var age = 16;

var age = 26;

var age = 36;

console.log(age);

}

foo(); // 36

2.let 宣告

let 跟 var 的作用差不多,但有著非常重要的區別。最明顯的區別是,let 宣告的範圍是塊作用域,而 var 宣告的範圍是函式作用域而 var 宣告的範圍是函式作用域。

if (true) {

var name = 'Matt';

console.log(name); // Matt

}

console.log(name); // Matt
let age = 26;

console.log(age); // 26

}

console.log(age); // ReferenceError: age 沒有定義

在這裡,age 變數之所以不能在 if 塊外部被引用,是因為它的作用域僅限於該塊內部。塊作用域

是函式作用域的子集,因此適用於 var 的作用域限制同樣也適用於 let。

let 也不允許同一個塊作用域中出現冗餘宣告。這樣會導致報錯:

var name;

var name;

let age;

let age; // SyntaxError;識別符號 age 已經宣告過了

當然,JavaScript 引擎會記錄用於變數宣告的識別符號及其所在的塊作用域,因此巢狀使用相同的標

識符不會報錯,而這是因為同一個塊中沒有重複宣告:

var name = 'Nicholas';

console.log(name); // 'Nicholas'

if (true) {

var name = 'Matt';

console.log(name); // 'Matt'

}

let age = 30;

console.log(age); // 30

if (true) {

let age = 26;

console.log(age); // 26

}

對宣告冗餘報錯不會因混用 let 和 var 而受影響。這兩個關鍵字宣告的並不是不同型別的變數,

它們只是指出變數在相關作用域如何存在。

var name;

let name; // SyntaxError

let age;

var age; // SyntaxError

1. 暫時性死區

let 與 var 的另一個重要的區別,就是 let 宣告的變數不會在作用域中被提升。
// name 會被提升

console.log(name); // undefined 
var name = 'Matt'; 

// age 不會被提升

console.log(age); // ReferenceError:age 沒有定義
let age = 26; 

在解析程式碼時,JavaScript 引擎也會注意出現在塊後面的 let 宣告,只不過在此之前不能以任何方
式來引用未宣告的變數。在 let 宣告之前的執行瞬間被稱為“暫時性死區”(temporal dead zone),在此
階段引用任何後面才宣告的變數都會丟擲 ReferenceError。

2. 全域性宣告

與 var 關鍵字不同,使用 let 在全域性作用域中宣告的變數不會成為 window 物件的屬性(var 聲

明的變數則會)。

var name = 'Matt';

console.log(window.name); // 'Matt'

let age = 26;

console.log(window.age); // undefined

不過,let 宣告仍然是在全域性作用域中發生的,相應變數會在頁面的生命週期記憶體續。因此,為了

避免 SyntaxError,必須確保頁面不會重複宣告同一個變數。

3. 條件宣告

在使用 var 宣告變數時,由於宣告會被提升,JavaScript 引擎會自動將多餘的宣告在作用域頂部合

併為一個宣告。因為 let 的作用域是塊,所以不可能檢查前面是否已經使用 let 宣告過同名變數,同

時也就不可能在沒有宣告的情況下宣告它。

使用 try/catch 語句或 typeof 操作符也不能解決,因為條件塊中 let 宣告的作用域僅限於該塊。

為此,對於 let 這個新的 ES6 宣告關鍵字,不能依賴條件宣告模式。

注意 不能使用 let 進行條件式宣告是件好事,因為條件宣告是一種反模式,它讓程式得更難理解。如果你發現自己在使用這個模式,那一定有更好的替代方式。

4. for 迴圈中的 let 宣告

在 let 出現之前,for 迴圈定義的迭代變數會滲透到迴圈體外部:

for (var i = 0; i < 5; ++i) {

// 迴圈邏輯

}

console.log(i); // 5

改成使用 let 之後,這個問題就消失了,因為迭代變數的作用域僅限於 for 迴圈塊內部:

for (let i = 0; i < 5; ++i) {

// 迴圈邏輯

}

console.log(i); // ReferenceError: i 沒有定義

在使用 var 的時候,最常見的問題就是對迭代變數的奇特宣告和修改:

for (var i = 0; i < 5; ++i) {

setTimeout(() => console.log(i), 0)

}

// 你可能以為會輸出 0、1、2、3、4

// 實際上會輸出 5、5、5、5、5

之所以會這樣,是因為在退出迴圈時,迭代變數儲存的是導致迴圈退出的值:5。在之後執行超時

邏輯時,所有的 i 都是同一個變數,因而輸出的都是同一個最終值。

而在使用 let 宣告迭代變數時,JavaScript 引擎在後臺會為每個迭代迴圈宣告一個新的迭代變數。

每個 setTimeout 引用的都是不同的變數例項,所以 console.log 輸出的是我們期望的值,也就是循

環執行過程中每個迭代變數的值。

for (let i = 0; i < 5; ++i) {

setTimeout(() => console.log(i), 0)

}

// 會輸出 0、1、2、3、4

這種每次迭代宣告一個獨立變數例項的行為適用於所有風格的 for 迴圈,包括 for-in 和 for-of

迴圈。

3.const 宣告

const 的行為與 let 基本相同,唯一一個重要的區別是用它宣告變數時必須同時初始化變數,且

嘗試修改 const 宣告的變數會導致執行時錯誤。

const age = 26;

age = 36; // TypeError: 給常量賦值
// const 也不允許重複宣告

const name = 'Matt';

const name = 'Nicholas'; // SyntaxError

// const 宣告的作用域也是塊

const name = 'Matt';

if (true) {

const name = 'Nicholas';

}

console.log(name); // Matt

const 宣告的限制只適用於它指向的變數的引用。換句話說,如果 const 變數引用的是一個物件,

那麼修改這個物件內部的屬性並不違反 const 的限制。

const person = {};

person.name = 'Matt'; // ok

JavaScript 引擎會為 for 迴圈中的 let 宣告分別建立獨立的變數例項,雖然 const 變數跟 let 變

量很相似,但是不能用 const 來宣告迭代變數(因為迭代變數會自增):

for (const i = 0; i < 10; ++i) {} // TypeError:給常量賦值

不過,如果你只想用 const 宣告一個不會被修改的 for 迴圈變數,那也是可以的。也就是說,每

次迭代只是建立一個新變數。這對 for-of 和 for-in 迴圈特別有意義:

let i = 0;

for (const j = 7; i < 5; ++i) {

console.log(j);

}

// 7, 7, 7, 7, 7

for (const key in {a: 1, b: 2}) {

console.log(key);

}

// a, b

for (const value of [1,2,3,4,5]) {

console.log(value);

}

// 1, 2, 3, 4, 5

這篇文章事參考javascript高階教程裡得,多看書有益處,受益良多

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章