var、let、const 有什麼區別
引言
本文主要介紹 var
、 let
、 const
關鍵字的含義,並從
-
作用域規則
-
重複聲明 / 重複賦值
-
變量提升(hoisted)
-
暫時死區(TDZ)
四個方面對比 var
、 let
、 const
聲明的變量差異
var
在 ES6 之前我們都是通過 var
關鍵字定義 JavaScript 變量。ES6 才新增了 let
和 const
關鍵字
var num = 1
在全局作用域下使用 var
聲明一個變量,默認它是掛載在頂層對象 window
對象下(Node 是 global
)
var num = 1
console.log(window.num) // 1
用 var
聲明的變量的作用域是它當前的執行上下文,可以是函數也可以是全局
var x = 1 // 聲明在全局作用域下
function foo() {
var x = 2 // 聲明在 foo 函數作用域下
console.log(x) // 2
}
foo()
console.log(x) // 1
如果在 foo
沒有聲明 x
,而是賦值,則賦值的是 foo
外層作用域下的 x
var x = 1 // 聲明在全局作用域下
function foo() {
x = 2 // 賦值
console.log(x) // 2
}
foo()
console.log(x) // 2
如果賦值給未聲明的變量,該變量會被隱式地創建爲全局變量(它將成爲頂層對象的屬性)
a = 2
console.log(window.a) // 2
function foo(){
b = 3
}
foo()
console.log(window.b) // 3
var 缺陷一:所有未聲明直接賦值的變量都會自動掛在頂層對象下,造成全局環境變量不可控、混亂
變量提升(hoisted)
使用var
聲明的變量存在變量提升的情況
console.log(b) // undefined
var b = 3
注意,提升僅僅是變量聲明,不會影響其值的初始化,可以與隱式的理解爲:
var b
console.log(b) // undefined
b = 3
作用域規則
var
聲明可以在包含它的函數,模塊,命名空間或全局作用域內部任何位置被訪問,包含它的代碼塊對此沒有什麼影響,所以多次聲明同一個變量並不會報錯:
var x = 1
var x = 2
這種作用域規則可能會引發一些錯誤
function sumArr(arrList) {
var sum = 0;
for (var i = 0; i < arrList.length; i++) {
var arr = arrList[i];
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
return sum;
}
這裏很容易看出一些問題,裏層的 for
循環會覆蓋變量 i
,因爲所有 i
都引用相同的函數作用域內的變量。有經驗的開發者們很清楚,這些問題可能在代碼審查時漏掉,引發無窮的麻煩。
var 缺陷二:允許多次聲明同一變量而不報錯,造成代碼不容易維護
捕獲變量怪異之處
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
i
是全局變量,全局只有一個變量i
, for
循環結束時, i=10
,所以 a[6]()
也爲 10 ,並且 a
的所有元素裏面的 i
都爲 10
而我們期望的是 a[6]()
輸出 6,所以我們有了下面的塊級作用域
let
let
與 var
的寫法一致,不同的是它使用的是塊作用域
let a = 1
塊作用域變量在包含它們的塊或 for
循環之外是不能訪問的
{
let x = 1
}
console.log(x) // Uncaught ReferenceError: x is not defined
所以:
var a = [];
for (let i = 0; i < 10; i++) { // 每一次循環的 i 其實都是一個新的變量
a[i] = function () {
console.log(i);
};
} // JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算
a[6](); // 6
同時, let
解決了 var
的兩個缺陷:
使用 let 在全局作用域下聲明的變量也不是頂層對象的屬性
let b = 2
window.b // undefined
那它在哪裏喃?
var a = 1
let b = 2
debugger
通過上圖也可以看到,在全局作用域中,用 let 和 const 聲明的全局變量沒有在全局對象中,只是一個塊級作用域(Script)中
不允許同一塊中重複聲明
let x = 1
let x = 2
// Uncaught SyntaxError: Identifier 'x' has already been declared
如果在不同塊中是可以聲明的
{
let x = 1
{
let x = 2
}
}
這種在一個嵌套作用域中聲明同一個變量名稱的行爲稱做 屏蔽 ,它可以完美解決上面的 sumArr
問題:
function sumArr(arrList) {
let sum = 0;
for (let i = 0; i < arrList.length; i++) {
var arr = arrList[i];
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
return sum;
}
此時將得到正確的結果,因爲內層循環的 i
可以屏蔽掉外層循環的 i
通常來講應該避免使用屏蔽,因爲我們需要寫出清晰的代碼。同時也有些場景適合利用它,你需要好好打算一下
暫時性死區(TDZ)
指 let
聲明的變量在被聲明之前不能被訪問
console.log(x) // Uncaught ReferenceError: x is not defined
let x = 1
如果你在塊中聲明 let
,它會報以下錯誤:
// let
{
console.log(x) // Uncaught ReferenceError: Cannot access 'x' before initialization
let x = 2
}
即,在塊級作用域下報錯內容是未初始化,那 let
在塊級作用域下有沒有被提升喃?
TC39 的成員 Rick Waldron 在 hoisting-vs-tdz.md 中這麼說:
In JavaScript, all binding declarations are instantiated when control flow enters the scope in which they appear. Legacy var and function declarations allow access to those bindings before the actual declaration, with a "value" of
undefined
. That legacy behavior is known as "hoisting". let and const binding declarations are also instantiated when control flow enters the scope in which they appear, with access prevented until the actual declaration is reached; this is called the Temporal Dead Zone. The TDZ exists to prevent the sort of bugs that legacy hoisting can create.
翻譯:
在 JavaScript 中,當控制流進入它們出現的範圍時,所有綁定聲明都會被實例化。傳統的
var
和function
聲明允許在實際聲明之前訪問那些綁定,並且其值(value)爲undefined
。這種遺留行爲被稱爲變量提升(hoisting)。當控制流進入它們出現的範圍時,let
和const
聲明也會被實例化,但在運行到實際聲明之前禁止訪問。這稱爲暫時性死區( Temporal Dead Zone)。TDZ 的存在是爲了防止傳統提升可能造成的那種錯誤。
即,通過 let
、 const
變量始終 “存在” 於它們的作用域裏,但不能在 let
、 const
語句之前訪問它們,所以不能稱爲變量提升,只能稱爲暫時性死區
const
const
聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
const a = 1
a = 2 // Uncaught TypeError: Assignment to constant variable.
因此, const
聲明的變量不得改變值,這意味着,const
一旦聲明變量,就必須立即初始化,不能留到以後賦值。
const s // 聲明未賦值
// Uncaught SyntaxError: Missing initializer in const declaration
注意,這裏 const
保證的不是變量的值不得改動,而是變量指向的那個內存地址不得改動,如果是基本類型的話,變量的值就保存在那個內存地址上,也就是常亮,如果是引用類型,它內部的值是可以變更的
const num = 1
const user = {
name: "sisterAn",
age: num,
}
user = {
name: "pingzi",
age: num
} // Uncaught TypeError: Assignment to constant variable.
// 下面這些都是運行成功的
user.name = "Hello"
user.name = "Kitty"
user.name = "Cat"
user.age--
其它 const
與 let
相同,例如:
-
作用域相同,只在聲明所在的塊級作用域內有效
-
常量也是不提升,同樣存在暫時性死區
這裏不再贅述
var vs let vs const
var
、 let
、 const
的不同主要有以下幾個方面:
-
作用域規則
-
重複聲明 / 重複賦值
-
變量提升(hoisted)
-
暫時死區(TDZ)
作用域規則
let/const
聲明的變量屬於塊作用域,只能在其塊或子塊中可用。而 var
聲明的變量的作用域是是全局或者整個封閉函數
重複聲明 / 重複賦值
-
var
可以重複聲明和重複賦值 -
let
僅允許重複賦值,但不能重複聲明 -
const
既不可以重複賦值,但不能重複聲明
變量提升(hoisted)
var
聲明的變量存在變量提升,即可以在變量聲明前訪問變量,值爲undefined
let
和 const
不存在變量提升,即它們所聲明的變量一定要在聲明後使用,否則報錯 ReferenceError
var:
console.log(a) // undefined
var a = 1
let:
console.log(b) // Uncaught ReferenceError: b is not defined
let b = 2
const:
console.log(c) // Uncaught ReferenceError: c is not defined
let c = 3
暫時死區(TDZ)
var
不存在暫時性死區, let
和const
存在暫時性死區,只有變量聲明後,才能被訪問或使用
編程風格
ES6 提出了兩個新的聲明變量的命令:let
和 const
。其中,let
完全可以取代 var
,因爲兩者語義相同,而且 let
沒有副作用。所以,我們在開發中建議使用 let
、 const
,不使用 var
參考
-
MDN
-
《阮一峯:ECMAScript 6 入門》
來自:https://github.com/sisterAn/blog
最後
歡迎關注「三分鐘學前端」,回覆「交流」自動加入前端三分鐘進階羣,每日一道編程算法面試題(含解答),助力你成爲更優秀的前端開發!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ZuiLh0jrQfJMnvd8c5FYQQ