var、let、const 有什么區(qū)別
點(diǎn)擊上方 三分鐘學(xué)前端,關(guān)注公眾號(hào)
回復(fù)交流,加入前端編程面試算法每日一題群
引言
本文主要介紹 var 、 let 、 const 關(guān)鍵字的含義,并從
-
作用域規(guī)則 -
重復(fù)聲明/重復(fù)賦值 -
變量提升(hoisted) -
暫時(shí)死區(qū)(TDZ)
四個(gè)方面對(duì)比 var 、 let 、 const 聲明的變量差異
var
在 ES6 之前我們都是通過(guò) var 關(guān)鍵字定義 JavaScript 變量。ES6 才新增了 let 和 const 關(guān)鍵字
var num = 1
在全局作用域下使用 var 聲明一個(gè)變量,默認(rèn)它是掛載在頂層對(duì)象 window 對(duì)象下(Node 是 global)
var num = 1
console.log(window.num) // 1
用 var 聲明的變量的作用域是它當(dāng)前的執(zhí)行上下文,可以是函數(shù)也可以是全局
var x = 1 // 聲明在全局作用域下
function foo() {
var x = 2 // 聲明在 foo 函數(shù)作用域下
console.log(x) // 2
}
foo()
console.log(x) // 1
如果在 foo 沒(méi)有聲明 x ,而是賦值,則賦值的是 foo 外層作用域下的 x
var x = 1 // 聲明在全局作用域下
function foo() {
x = 2 // 賦值
console.log(x) // 2
}
foo()
console.log(x) // 2
如果賦值給未聲明的變量,該變量會(huì)被隱式地創(chuàng)建為全局變量(它將成為頂層對(duì)象的屬性)
a = 2
console.log(window.a) // 2
function foo(){
b = 3
}
foo()
console.log(window.b) // 3
var 缺陷一:所有未聲明直接賦值的變量都會(huì)自動(dòng)掛在頂層對(duì)象下,造成全局環(huán)境變量不可控、混亂
變量提升(hoisted)
使用var聲明的變量存在變量提升的情況
console.log(b) // undefined
var b = 3
注意,提升僅僅是變量聲明,不會(huì)影響其值的初始化,可以與隱式的理解為:
var b
console.log(b) // undefined
b = 3
作用域規(guī)則
var 聲明可以在包含它的函數(shù),模塊,命名空間或全局作用域內(nèi)部任何位置被訪問(wèn),包含它的代碼塊對(duì)此沒(méi)有什么影響,所以多次聲明同一個(gè)變量并不會(huì)報(bào)錯(cuò):
var x = 1
var x = 2
這種作用域規(guī)則可能會(huì)引發(fā)一些錯(cuò)誤
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;
}
這里很容易看出一些問(wèn)題,里層的 for 循環(huán)會(huì)覆蓋變量 i,因?yàn)樗?nbsp;i 都引用相同的函數(shù)作用域內(nèi)的變量。有經(jīng)驗(yàn)的開(kāi)發(fā)者們很清楚,這些問(wèn)題可能在代碼審查時(shí)漏掉,引發(fā)無(wú)窮的麻煩。
var 缺陷二:允許多次聲明同一變量而不報(bào)錯(cuò),造成代碼不容易維護(hù)
捕獲變量怪異之處
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
i 是全局變量,全局只有一個(gè)變量i , for 循環(huán)結(jié)束時(shí), i=10 ,所以 a[6]() 也為 10 ,并且 a 的所有元素里面的 i 都為 10
而我們期望的是 a[6]() 輸出 6,所以我們有了下面的塊級(jí)作用域
let
let 與 var 的寫法一致,不同的是它使用的是塊作用域
let a = 1
塊作用域變量在包含它們的塊或 for 循環(huán)之外是不能訪問(wèn)的
{
let x = 1
}
console.log(x) // Uncaught ReferenceError: x is not defined
所以:
var a = [];
for (let i = 0; i < 10; i++) { // 每一次循環(huán)的 i 其實(shí)都是一個(gè)新的變量
a[i] = function () {
console.log(i);
};
} // JavaScript 引擎內(nèi)部會(huì)記住上一輪循環(huán)的值,初始化本輪的變量i時(shí),就在上一輪循環(huán)的基礎(chǔ)上進(jìn)行計(jì)算
a[6](); // 6
同時(shí), let 解決了 var 的兩個(gè)缺陷:
使用 let 在全局作用域下聲明的變量也不是頂層對(duì)象的屬性
let b = 2
window.b // undefined
那它在哪里喃?
var a = 1
let b = 2
debugger
通過(guò)上圖也可以看到,在全局作用域中,用 let 和 const 聲明的全局變量沒(méi)有在全局對(duì)象中,只是一個(gè)塊級(jí)作用域(Script)中
不允許同一塊中重復(fù)聲明
let x = 1
let x = 2
// Uncaught SyntaxError: Identifier 'x' has already been declared
如果在不同塊中是可以聲明的
{
let x = 1
{
let x = 2
}
}
這種在一個(gè)嵌套作用域中聲明同一個(gè)變量名稱的行為稱做 屏蔽 ,它可以完美解決上面的 sumArr 問(wèn)題:
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;
}
此時(shí)將得到正確的結(jié)果,因?yàn)閮?nèi)層循環(huán)的 i 可以屏蔽掉外層循環(huán)的 i
通常來(lái)講應(yīng)該避免使用屏蔽,因?yàn)槲覀冃枰獙懗銮逦拇a。同時(shí)也有些場(chǎng)景適合利用它,你需要好好打算一下
暫時(shí)性死區(qū)(TDZ)
指 let 聲明的變量在被聲明之前不能被訪問(wèn)
console.log(x) // Uncaught ReferenceError: x is not defined
let x = 1
如果你在塊中聲明 let ,它會(huì)報(bào)以下錯(cuò)誤:
// let
{
console.log(x) // Uncaught ReferenceError: Cannot access 'x' before initialization
let x = 2
}
即,在塊級(jí)作用域下報(bào)錯(cuò)內(nèi)容是未初始化,那 let 在塊級(jí)作用域下有沒(méi)有被提升喃?
TC39 的成員 Rick Waldron 在 hoisting-vs-tdz.md 中這么說(shuō):
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 中,當(dāng)控制流進(jìn)入它們出現(xiàn)的范圍時(shí),所有綁定聲明都會(huì)被實(shí)例化。傳統(tǒng)的
var和function聲明允許在實(shí)際聲明之前訪問(wèn)那些綁定,并且其值(value)為undefined。這種遺留行為被稱為變量提升(hoisting)。當(dāng)控制流進(jìn)入它們出現(xiàn)的范圍時(shí),let和const聲明也會(huì)被實(shí)例化,但在運(yùn)行到實(shí)際聲明之前禁止訪問(wèn)。這稱為暫時(shí)性死區(qū)( Temporal Dead Zone)。TDZ 的存在是為了防止傳統(tǒng)提升可能造成的那種錯(cuò)誤。
即,通過(guò) let 、 const 變量始終“存在”于它們的作用域里,但不能在 let 、 const 語(yǔ)句之前訪問(wèn)它們,所以不能稱為變量提升,只能稱為暫時(shí)性死區(qū)
const
const 聲明一個(gè)只讀的常量。一旦聲明,常量的值就不能改變。
const a = 1
a = 2 // Uncaught TypeError: Assignment to constant variable.
因此, const聲明的變量不得改變值,這意味著,const一旦聲明變量,就必須立即初始化,不能留到以后賦值。
const s // 聲明未賦值
// Uncaught SyntaxError: Missing initializer in const declaration
注意,這里 const 保證的不是變量的值不得改動(dòng),而是變量指向的那個(gè)內(nèi)存地址不得改動(dòng),如果是基本類型的話,變量的值就保存在那個(gè)內(nèi)存地址上,也就是常亮,如果是引用類型,它內(nèi)部的值是可以變更的
const num = 1
const user = {
name: "sisterAn",
age: num,
}
user = {
name: "pingzi",
age: num
} // Uncaught TypeError: Assignment to constant variable.
// 下面這些都是運(yùn)行成功的
user.name = "Hello"
user.name = "Kitty"
user.name = "Cat"
user.age--
其它 const 與 let 相同,例如:
-
作用域相同,只在聲明所在的塊級(jí)作用域內(nèi)有效 -
常量也是不提升,同樣存在暫時(shí)性死區(qū)
這里不再贅述
var vs let vs const
var 、 let 、 const 的不同主要有以下幾個(gè)方面:
-
作用域規(guī)則 -
重復(fù)聲明/重復(fù)賦值 -
變量提升(hoisted) -
暫時(shí)死區(qū)(TDZ)
作用域規(guī)則
let/const 聲明的變量屬于塊作用域,只能在其塊或子塊中可用。而 var 聲明的變量的作用域是是全局或者整個(gè)封閉函數(shù)
重復(fù)聲明/重復(fù)賦值
-
var可以重復(fù)聲明和重復(fù)賦值 -
let僅允許重復(fù)賦值,但不能重復(fù)聲明 -
const既不可以重復(fù)賦值,但不能重復(fù)聲明
變量提升(hoisted)
var 聲明的變量存在變量提升,即可以在變量聲明前訪問(wèn)變量,值為undefined
let 和 const 不存在變量提升,即它們所聲明的變量一定要在聲明后使用,否則報(bào)錯(cuò) 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
暫時(shí)死區(qū)(TDZ)
var不存在暫時(shí)性死區(qū), let和const存在暫時(shí)性死區(qū),只有變量聲明后,才能被訪問(wèn)或使用
編程風(fēng)格
ES6 提出了兩個(gè)新的聲明變量的命令:let 和 const 。其中,let 完全可以取代 var ,因?yàn)閮烧哒Z(yǔ)義相同,而且 let 沒(méi)有副作用。所以,我們?cè)陂_(kāi)發(fā)中建議使用 let 、 const ,不使用 var
參考
-
MDN -
《阮一峰:ECMAScript 6入門》
來(lái)自:https://github.com/sisterAn/blog
