1、let命令數組
一、基本語法數據結構
ES6新增了let命令,用於聲明變量。其語法與var相似,可是所聲明的變量只在let命令所在的代碼塊內有效。函數
{ let a = 1; var b = 2; } console.log(a); // ReferenceError: a is not defined console.log(b); // 2
上面的代碼在代碼塊中分別用let和var聲明瞭兩個變量,而後在代碼塊以外調用這兩個變量,結果let聲明的變量報錯,var聲明的輸出了正確的值。這代表,let聲明的變量只在其所在的代碼塊內有效。spa
下面來個經典的例子:指針
// var { var arr = []; for (var i = 0; i < 10; i++) { arr[i] = function() { console.log(i); }; } arr[3](); // 10 }
上面的代碼中,變量i是var聲明的,在全局範圍內都有效,因此全局只有一個變量i。每一次循環,變量i的值都會發生改變,而循環內,被賦給數組arr的函數內部的console.log(i)中的i指向全局中的i。也就是說,全部數組arr的成員中的i指向的都是同一個i,致使運行時輸出的是最後一輪的i值,也就是10。code
// let { var arr = []; for (let i = 0; i < 10; i++) { arr[i] = function() { console.log(i); }; } arr[3](); // 3 }
上面的代碼中,變量是let聲明的,當前的i只在本輪循環有效。因此每一次循環的i其實都是一個新的變量,因而最後輸出的是3。你們可能會問,若是每一輪循環變量i都是從新聲明的,那它怎麼知道上一輪循環的值從而計算出本輪循環的值呢?這是由於JavaScript引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。對象
另外,for循環還有一個特別之處,就是設置循環變量的那一部分是一個父做用域,而循環體內部是一個單獨的子做用域。blog
{ for (let i = 0; i < 2; i++) { let i = 'SophiaLee'; console.log(i); } // SophiaLee // SophiaLee }
二、不存在變量提高ip
var命令會發生「變量提高」的現象,即變量能夠在聲明以前使用,值爲undefined。這種現象多少是有些奇怪的,按照通常的邏輯,變量應該在聲明語句以後才能使用。內存
爲了糾正這種現象,let命令改變了語法行爲,它所聲明的變量必定要在聲明後使用,不然會報錯。
// var { console.log(a); // undefined var a = 5; }
// let { console.log(b); // ReferenceError: b is not defined let b = 1; }
在上面的代碼中,變量a用var命令聲明會發生變量提高,當腳本開始運行時,變量a就已經存在,可是沒有值,因此輸出undefined。變量b用let命令聲明則不會發生變量提高。這代表在聲明它以前,變量b是不存在的,這時若是用到它,就會拋出錯誤。
三、暫時性死區
只要塊級做用域內存在let命令,它所聲明的變量就「綁定」這個區域,再也不受外部的影響。
ES6明確規定,若是區塊中存在let和const命令,則這個區域對這些命令聲明的變量從一開始就造成封閉的做用域。只要在聲明以前使用這些變量,就會報錯。總之,在代碼塊內,使用let命令聲明變量以前,該變量都是不可用的。這在語法上成爲「暫時性死區(TDZ)」。
// 暫時性死區 { if (true) { // TDZ開始 a = 1; console.log(a); // ReferenceError: a is not defined let a; // TDZ結束 console.log(a); // undefined a = 2; console.log(a); // 2 } }
上面的代碼中,在let命令聲明變量a以前,都屬於變量a的「死區」。
另外,有些「死區」比較隱蔽,不太容易發現。
{ function a(x = y, y = 2) { return [x, y]; } a(); // ReferenceError: y is not defined }
上面的代碼中,調用a函數之因此報錯,是由於參數x的默認值等於另外一個參數y,而此時y尚未聲明,屬於「死區」。若是y的默認值是x,就不會報錯。
{ function a(x = 1, y = x) { return [x, y]; } a(); // [1, 1] } { // 不報錯 var x = x; // 報錯 let x = x; // ReferenceError: x is not defined }
總之,暫時性死區的本質就是,只要進入當前做用域,所要使用的變量已經存在,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。
四、不容許重複聲明
let不容許在同一做用域內重複聲明同一個變量。
{ // 報錯 let a = 1; // var a = 2; } { function a(arg) { let arg; } a(); // SyntaxError: Identifier 'arg' has already been declared } { function a(arg) { { let arg; // 不報錯 } } a(); }
2、const命令
一、基本用法
const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
{ const PI = 3.1415926; console.log(PI); // 3.1415926 PI = 4; // TypeError: Assignment to constant variable. }
上面的代碼說明改變常量的值會報錯。
const聲明的變量不得改變值,這意味着,const一旦聲明常量,就必須當即初始化,不能留到後面賦值。(只聲明不賦值會報錯)
const的做用域與let命令相同:只在聲明所在的塊級做用域內有效。
const命令聲明的變量也不會提高,一樣存在暫時性死區,只能在聲明以後使用。
二、本質
const實際上保證的並非變量的值不得改動,而是變量指向的那個內存地址不得改動。對於簡單類型的數據(Number、String、Boolean)而言,值就保存在變量指向的內存地址中,所以等同於常量。但對於複合類型的數據(主要是對象和數組)而言,變量指向的內存地址保存的只是一個指針,const只能保證這個指針是固定的,至於它指向的數據結構是不可變的,這徹底不能控制。所以,將一個對象聲明爲常量時必須很是當心。
{ const person = {}; // 爲person添加一個屬性,能夠成功 person.name = 'SophiaLee'; console.log(person.name); // SophiaLee // 將person指向另外一個對象,就會報錯 person = {}; // TypeError: Assignment to constant variable. 類型錯誤:賦值給常量變量 }
上面的代碼中,常量person儲存的是一個地址,這個地址指向的是一個對象。不可變的只是這個地址,即不能把person指向另外一個地址,但對象自己是可變的,因此依然能夠爲其添加新屬性。
再看另外一個例子:
{ const arr = []; arr.push('Hello'); // 可執行 arr.length = 0; // 可執行 arr = ['World']; // 報錯 TypeError: Assignment to constant variable. }
上面的代碼中,常量arr是一個數組,這個數組自己是可寫的,可是若是將另外一個數組賦值給arr,就會報錯。