在ES6之前的版本中,用于聲明變量的關鍵字只有var,并且沒有塊級作用域,只有函數作用域和全局作用域,但在ES6中已改變這種狀況。ES6引入了let和const兩個關鍵字,它們既可以用于聲明變量,還能夠將變量綁定到當前所處的任意作用域中,換句話說,就是把變量的作用域封閉在所處的代碼塊(即花括號字符“{”和“}”之間的區域,例如if條件語句中的代碼)中,如此一來就形成了塊級作用域。let和const兩個關鍵字與var之間的不同,簡單的說有以下三點:
  (1)不允許聲明提升。
  (2)不允許重復聲明。
  (3)不覆蓋全局變量。
  而在let與const之間也有不同之處,在下面的內容中會重點講解。
## 一、let
  let可以理解為var的升級版本,摒棄或糾正了var的一些會導致代碼混亂的特性,從而使得代碼的邏輯更清晰,可維護性更高。
**1)提升**
  首先要介紹的是用let聲明的變量,其聲明語句不會再被提升。下面將兩個變量分別用var和let聲明,再輸出它們的結果,具體如下所示。
~~~
console.log(outer); //undefined
console.log(inner); //拋出未定義的引用錯誤
{
console.log(outer); //undefined
console.log(inner); //拋出未定義的引用錯誤
var outer = true;
let inner = true;
console.log(outer); //true
console.log(inner); //true
}
console.log(outer); //true
console.log(inner); //拋出未定義的引用錯誤
~~~
  兩個變量都在代碼塊中執行賦值操作。第一個outer變量由于是用var聲明的,因此它的聲明語句能夠被提升,而在第一次和第二次輸出時,因為還沒被賦值,所以輸出的結果都為undefined,在賦完值后,輸出的結果就都是true。第二個inner變量是用let聲明的,由于聲明語句不能被提升到代碼塊的外部,因此它的作用域只限于代碼塊內,在代碼塊外就無法訪問到它,并且在聲明它之前也無法被訪問(因為聲明語句也不會被提升至作用域頂部),此時變量正處于臨時死區中。如果強行訪問,那么就會拋出未定義的引用錯誤。
  臨時死區(Temporal Dead Zone,簡稱TDZ)也叫暫時性死區,用let或const聲明的變量,在聲明之前都會被放到TDZ中,而在此時訪問這些變量就會觸發運行時錯誤。這種語法設計能促進工程師們在平時養成良好的編碼習慣,減少或杜絕一些由于始料未及的原因而產生的程序BUG。有一點要注意,TDZ并不是由ECMAScript標準命名的,它是JavaScript社區的一種約定俗成的叫法。
**2)重復聲明**
  接下來介紹let的第二個特性:不允許重復聲明。注意,這里有一個前置條件,那就是只有在相同作用域時,才不允許同一個變量重復聲明。ES6引入的這個重復聲明的檢查機制,可以避免在多人協作開發的時候,因多聲明一個或多個同名的變量,而影響別人代碼邏輯的情況出現。下面的示例就分了兩個作用域分別說明,并且對比了var和let對待重復聲明的處理結果。
~~~
var duplicate;
let repeat;
var duplicate; //var聲明的變量可重復聲明
let duplicate; //拋出重復聲明的語法錯誤
let repeat; //拋出重復聲明的語法錯誤
{
let repeat; //不同作用域,可正常聲明
}
~~~
**3)全局作用域**
  最后介紹的是let在全局作用域中的特性。當用var在全局作用域中聲明變量的時候,該變量不但會成為全局變量,而且還會成為全局對象(例如瀏覽器中的window對象)的一個屬性。全局對象以及它的屬性可以在任何位置被訪問,這樣就影響了函數的封裝,不利于代碼的模塊化,并且新聲明的全局變量有可能會覆蓋全局對象中已存在的屬性。上述是兩個比較有代表性的弊端,為了解決這些問題,ES6規定用let可將全局變量和全局對象斷開聯系。下面用兩組代碼分別演示斷開聯系(第一組)和覆蓋已有屬性(第二組),注意,在第二組代碼中為了方便對比,忽略了重復聲明的錯誤。
~~~
//第一組
var global = true;
console.log(window.global); //true
let whole = true;
console.log(window.whole); //undefined
//第二組
var Math = true;
console.log(window.Math); //true
let Math = true;
console.log(window.Math); //Math對象
~~~
## 二、const
  const不但擁有上面所述的let的三個特性,并且還能聲明一個常量。常量是指一個定義了初始值后固定不變的只讀變量。在過去,常量都是通過命名規范(例如用大寫字母、下畫線等字符)定義的,雖然實現起來很便捷,但由于缺少約束,因此這種常量很容易被修改。而在ES6引入了const關鍵字后,就能像其它編程語言那樣聲明常量了。有一點要注意,const與let不同,在聲明時必須初始化(即賦值),并且在設定后,其值無法再更改,如下代碼所示。
~~~
const number; //拋出未初始化的語法錯誤
const digit = 10;
digit = 20; //拋出賦值給常量的類型錯誤
~~~
  此處要強調一點,const限制的其實是變量與內存地址之間的綁定,也就是說,const讓變量無法更改所對應的內存地址。如果是基本類型(例如布爾值、數字等)的變量,那么對應的內存地址中保存的就是值;如果是引用類型(例如對象)的變量,那么對應的內存地址中保存的是指向實際數據的一個指針。由此可知,當用const聲明的變量,其初始化的值是對象時,可以修改對象中的屬性或方法,具體可參考下面的代碼。
~~~
const obj = {};
obj.name = "strick";
obj.age = function() {
return 29;
};
~~~
## 三、循環中的let和const
  ES6規定了let聲明在循環內部的行為,以for循環為例,如下所示。
~~~
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
~~~
  在控制臺輸出的結果依次為0、1、2,沒有出現循環中的異步回調問題。這是因為在每次循環的時候,都會重新創建一個叫做i的同名變量,并將其初始化為計算后的值,而循環體內調用的i變量不會受其它同名變量的影響,所以能夠在定時器的回調函數中正確顯示該變量的值。在ES5及之前的版本中,如果要解決異步回調問題,就需要像下面這樣借助立即執行函數表達式(IIFE)才能得到預期的效果。
~~~
for (var i = 0; i < 3; i++) {
(function(n) {
setTimeout(function() {
console.log(n);
}, 0);
})(i);
}
~~~
  const聲明在循環內部的行為與let聲明類似,不過,由于其值無法修改,因此在for循環中重新賦值(例如執行增量操作)將會拋出異常。但只要避開重新賦值,就能正常循環迭代,例如用for-in執行const聲明的循環,如下所示。
~~~
var author = {
name: "strick",
age: 29
};
for (const key in author) {
console.log(key);
}
~~~
*****
> 原文出處:
[博客園-ES6躬行記](https://www.cnblogs.com/strick/category/1372951.html)
[知乎專欄-ES6躬行記](https://zhuanlan.zhihu.com/pwes6)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020