let 和 const 是 ES6 新增的用於聲明變量和常量的關鍵字,他們的做用是什麼?它們又有什麼特性?它們與 var 定義的變量又有何區別?javascript
定義之後可改變的量就是變量,定義後不可改變的量就是常量。前端
在 ES6
中,使用 let
命令定義變量,使用 const
命令定義常量,也就是說 let
定義後變量是可修改的,const
定義後的常量不能被修改。這一特性目前被大多數瀏覽器原生支持,可是針對少部分不能支持的瀏覽器,咱們可使用 babel
將它編譯成 ES5
語法,下面能夠看下二者用 babel
編譯後的代碼有何區別:java
// ES6 let a = 1; const b = 2; 複製代碼
// babel 編譯後 "use strict"; var a = 1; var b = 2; 複製代碼
感受沒區別啊??不要急,再加一段代碼:webpack
// ES6 let a = 1; const b = 2; b = 3; 複製代碼
// babel 編譯後 "use strict"; function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); } var a = 1; var b = 2; b = (_readOnlyError("b"), 3); 複製代碼
這樣應該很容易看出區別了,當咱們對一個常量進行變值操做,就會拋出一個錯誤告訴你:這個值是隻讀的。git
b = (_readOnlyError("b"), 3)
這種操做我也沒見過,不過猜想應該相似於b = _readOnlyError("b") && 3
es6
注意:因爲const
定義的值是不可變的,這點在用 const
定義引用類型的時候要特別注意!若是是引用類型的 const
值,改變其中的屬性是可行的,可是一般不建議這麼作。web
const obj = {}; obj.a = 1; obj.b = 2; obj; // => {a: 1, b: 2} obj = {}; // => 報錯 複製代碼
因爲 const
定義的常量,定義後就不能修改的特性,決定了它定義的時候必須就初始化,不然就報錯;而 let
則沒有這種限制,它定義的變量徹底能夠在後面再初始化:面試
let a; a = 1; const b; // Uncaught SyntaxError: Missing initializer in const declaration b = 2; 複製代碼
上面這段代碼因爲直接違反了
const
的語法特性,所以在babel
編譯階段就沒法經過express
let
和 var
是兩種聲明變量的方式,兩者主要有如下區別:瀏覽器
let
所聲明的變量會建立本身的塊級做用域,建立的做用域是定義它的塊級代碼及其中包括的子塊中,且沒法自動往全局變量 window
上綁定屬性。var
定義的變量,做用域爲定義它的函數,或者全局,而且是能自動往全局對象 window
上綁定屬性的。關於可否建立本身的塊級做用域這一差異,就會涉及到一道被問爛的面試題:
var result = []; (function () { for (var i = 0; i < 5; i++) { result.push(function () { console.log(i); }); } })(); result.forEach(function (item) { item() }); // => 打印出五個 5 複製代碼
爲何打印出了五個5,而不是預期的 0,1,2,3,4 ?由於 var
不會建立本身的做用域,而 js
自己又是沒有塊級做用域這個概念的,for
循環中定義的變量就等因而直接定義在匿名函數中的變量,因而當這5個函數被掏出來執行的時候,循環早已完成,而函數讀取的上面一層做用域中存儲的變量 i
,也早已經被累加成了5。
然而,這個問題只要將 var
關鍵字換成 let
就迎刃而解:
var result = []; (function () { for (let i = 0; i < 5; i++) { result.push(function () { console.log(i); }); } })(); result.forEach(function (item) { item() }); // => 0,1,2,3,4 複製代碼
咱們能夠看看這段使用 let
的代碼,最後被 babel
轉譯成什麼樣:
"use strict"; var result = []; (function () { var _loop = function _loop(i) { result.push(function () { console.log(i); }); }; for (var i = 0; i < 5; i++) { _loop(i); } })(); result.forEach(function (item) { item(); }); 複製代碼
從上面的代碼咱們就能夠看出,let
建立做用域的方式,其實就是建立了一個函數,在函數內定義一個同名變量並於外部將這個變量傳入其中,以此達到建立做用域的目的。
let
定義的變量不會進行變量聲明提高操做,也就是說在訪問該變量以前必需要先定義。var
定義的變量存在變量聲明提高,所以在變量定義前就能訪問到變量,值是 undefined
。console.log(a); // undefined var a = 1; console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization let b = 2; 複製代碼
一樣咱們來看下上面這段代碼被編譯後的樣子:
"use strict"; console.log(a); var a = 1; console.log(b); var b = 2; 複製代碼
看起來好像 Babel
沒法編譯這種阻止變量聲明提高的語法,let
聲明的變量沒法提高的特性應該是瀏覽器內部的 JS 執行引擎支持和實現的。
只要塊級做用域內存在
let / const
命令,它所聲明的變量 / 常量就「綁定」(binding)這個區域,再也不受外部的影響。ES6 明確規定,若是區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域,凡是在聲明以前就使用這些變量,就會報錯。這種特性也被成爲暫時性死區。
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError: tmp is not defined let tmp; } 複製代碼
一樣,這個特性也是被瀏覽器內部的 JS 執行引擎支持和實現的,babel
沒法支持這種特性的編譯,只能簡單的將 let
編譯成 var
。可是有意思的是,因爲 let
在 if
塊中是能夠構建本身的獨立做用域的,babel
將 tmp
這個變量換了個名字來模擬實現塊級做用域的建立:
"use strict"; var tmp = 123; if (true) { _tmp = 'abc'; var _tmp; } 複製代碼
let
定義的變量,一旦定義便不容許被從新定義。var
定義的變量,能夠被從新定義。var a = 1; var a = 2; let b = 3; let b = 4; // Uncaught SyntaxError: Identifier 'b' has already been declared 複製代碼
上面這段代碼因爲直接違反了
let
定義的變量沒法從新定義的語法特性,所以一樣在babel
編譯階段就沒法經過。
這塊內容並不複雜,咱們能夠作一個簡單的總結:
let
特性:
建立塊級做用域。
定義後不能從新定義。
不存在變量提高。
存在暫時性死區。
全局做用域下定義時不會被掛載到頂層對象上(window對象 / global 對象
)
// 瀏覽器環境 var a = 1; window.a; // => 1 let b = 2; window.b; // => undefined 複製代碼
const
特性:
let
本篇文章已收錄入 前端面試指南專欄