一、let
1.定義
ES6新增了let命令,用來宣告變數,用法類似於var,但是和var有一定的區別
2.let只在塊級作用域內有效
首先來看一個比較簡單的例子,請告訴我,他們分別輸出什麼
//程式碼段1
for(var i = 0; i < 10 ;i++){
console.log('我是var宣告的')
}
console.log(i)//10
//程式碼段2
for(let i = 0; i < 10 ;i++){
console.log(‘我是let宣告的’)
}
console.log(i)// i is not defined
兩段程式碼的不同之處就在宣告變數i時,一個採用的var,一個採用的let。程式碼段1在外部列印i時的結果是 10,而程式碼段2的結果卻是 i is not defined。
這是因為let宣告的變數只在塊級作用域內有效,所以在外部環境無法使用這個變數,而var宣告的變數是一個全域性變數。
然後再看下面這個經典面試題,告訴我,他們分別輸出什麼
//程式碼段1 var宣告
for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i)//10次10
},100)
}
//程式碼段2 let宣告
for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i)//0-9
},100)
}
分析程式碼之前,你要知道setTimeout是一個非同步函式,非同步函式的執行順序是當主執行緒執行完成之後才會執行非同步佇列裡的函式。所以每一次for迴圈實際上是將與之對應的setTimeout放入非同步佇列裡面。當for迴圈執行完成之後,再執行這些setTimeout。
搞清楚了執行過程,那我們從結果來分析原因。用var宣告的for迴圈的結果是10次10,這是因為當for迴圈執行完成之時,全域性變數i的值已經變成了10,呼叫setTimeout函式,列印10次10。用let宣告的變數並不是一個全域性變數,是一個有塊級作用域的變數,那麼在每次迴圈的時候,這個i就繫結到了對應的setTimeout函式身上,實際上就是每一次迴圈的i都是一個新變數。所以他會輸出0-9。
園友:你說上面每一次迴圈的i都是一個新變數,那為什麼i的值會依次增加呢?
小爐:因為 javascript 引擎內部會記住上一輪迴圈的值,初始化本輪的變數i時,就在上一輪迴圈的基礎上進行計算。
3.不存在變數提升
var命令會發生“變數提升”現象,即變數可以在宣告之前使用,值為undefined。我也在之前的部落格裡面多次分析變數提升的程式碼執行順序,在這裡不再贅述,後續會專門出一篇變數提升和函式提升的解析部落格
let命令則不會出現這個現象,它要求所宣告的變數一定要在宣告後使用,否則報錯。
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;
上面的程式碼中,變數foo用var命令宣告,會發生變數提升,即指令碼開始執行時,變數foo已經存在了,但是沒有值,所以會輸出undefined。變數bar用let命令宣告,不會發生變數提升。這表示在宣告它之前,變數bar是不存在的,這時如果用到它,就會丟擲一個錯誤。
4.暫時性死區
只要塊級作用域記憶體在let命令,它所宣告的變數就“繫結”(binding)這個區域,不再受外部的影響。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面程式碼中,存在全域性變數tmp,但是塊級作用域內let又宣告瞭一個區域性變數tmp,導致後者繫結這個塊級作用域,所以在let宣告變數前,對tmp賦值會報錯。
ES6 明確規定,如果區塊中存在let和const命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉作用域。凡是在宣告之前就使用這些變數,就會報錯。
總之,在程式碼塊內,使用let命令宣告變數之前,該變數都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 tdz)。暫時性死區的本質就是,只要一進入當前作用域,所要使用的變數就已經存在了,但是不可獲取,只有等到宣告變數的那一行程式碼出現,才可以獲取和使用該變數。
5.不允許重複宣告
let不允許在相同作用域內,重複宣告同一個變數。
// 報錯
function func() {
let a = 10;
var a = 1;
}
// 報錯
function func() {
let a = 10;
let a = 1;
}
因此,不能在函式內部重新宣告引數。
function func(arg) {
let arg;
}
func() // 報錯
function func(arg) {
{
let arg;
}
}
func() // 不報錯
值得一提的是,一定要明確不允許重複宣告的概念,請看下面的程式碼:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
實際上這段程式碼並不是重複宣告(let不允許在相同作用域內,重複宣告同一個變數),for迴圈有一個特別之處,就是設定迴圈變數的那部分是一個父作用域,而迴圈體內部是一個單獨的子作用域。上面程式碼正確執行,輸出了 3 次abc。這表明函式內部的變數i與迴圈變數i不在同一個作用域,有各自單獨的作用域。
二、const
1.定義
const宣告一個只讀的常量。一旦宣告,常量的值就不能改變。
2.const特點
(1)const一旦宣告變數,就必須立即初始化,不能留到以後賦值。只宣告不賦值,就會報錯。
(2)const的作用域與let命令相同:只在宣告所在的塊級作用域內有效。
(3)const命令宣告的常量也是不提升,同樣存在暫時性死區,只能在宣告的位置後面使用。
(4)const宣告的常量,也與let一樣不可重複宣告。
(5)const宣告的變數不得改變值,改變常量的值就會報錯
3.const宣告真的不能改值嗎?
const實際上保證的,並不是變數的值不得改動,而是變數指向的那個記憶體地址所儲存的資料不得改動。對於簡單型別的資料(數值、字串、布林值),值就儲存在變數指向的那個記憶體地址,因此等同於常量。
但對於複合型別的資料(主要是物件和陣列),變數指向的記憶體地址,儲存的只是一個指向實際資料的指標,const只能保證這個指標是固定的(即總是指向另一個固定的地址),至於它指向的資料結構是不是可變的,就完全不能控制了。
接下來我們分別用基本資料型別和引用資料型別舉例
//const宣告基本資料型別
const i = 10
function show(){
i = 20
}
show()//Uncaught TypeError: Assignment to constant variable.
//const宣告 陣列 改變陣列內某個元素
const arr = [1,3,5,67,8,90]
arr[0] = 5
arr.push(4)
console.log(arr)//[5, 3, 5, 67, 8, 90, 4]
//const宣告 物件 改變物件某個鍵值
const obj = {
name:'傑西卡',
age:15,
time:true
}
obj[name] = '詹姆斯'
obj.aa = 'hold on'
console.log(obj)//{name: "傑西卡", age: 15, time: true, "": "詹姆斯", aa: "hold on"}
從上面程式碼的結果可以看到,const宣告基本資料型別的時候,該變數的值是不可以改變的。當宣告引用資料型別的時候,對陣列/物件內部的元素進行刪改,是可以改變的。那這是為什麼呢?我們再看一段程式碼,再來解釋
//const宣告的物件 重新賦值
const obj = {
name:'傑西卡',
age:15,
time:true
}
let a = {
name:'haha',
age:12,
time:false
}
obj = a
console.log(obj)//Uncaught TypeError: Assignment to constant variable.
//const宣告的陣列 重新賦值
const arr = [1,3,5,67,8,90]
let cc = [0]
arr = cc
console.log(arr)Uncaught TypeError: Assignment to constant variable.
上面兩段程式碼的結果都是報錯,且提示const宣告的是無法修改的。
原因:首先你應該知道引用資料型別的儲存方式,在棧中儲存了指標,該指標指向堆中具體的物件(也就是物件的值)。const所說的宣告變數不能改變值,這個值對於引用資料型別來說是棧中的指標,const只能保證這個指標是固定的。所以當你對物件內部的值進行修改時是被允許的。但是當你去修改這個指標時,就會報錯。
三、總結
let、const宣告的變數只能在塊級作用域中使用,沒有變數提升,不能重複宣告,必須先宣告再使用。const宣告變數必須伴隨初始化,且const宣告的變數值不可以修改。
var宣告的變數作用域是整個封閉函式,是全域性的(瀏覽器環境下會掛到window上,作為window的屬性,let、const不會),var宣告的變數存在變數提升,宣告會提升到作用域的最頂部。var可以重複宣告。