前言:
最近開始看阮一峰老師的《ECMAScript 6 入門》(以下簡稱原文)學習ECMAScript 6(下文簡稱ES6)的知識,整理出一些知識點加上我的理解來做成文章筆記。按照章節為單位一個章節一篇筆記。
文章代碼與目錄結構和原文不同。
這一章原文鏈接 let 和 const 命令 。
let
let
是用來聲明一個變量。
不同與var
會存在變量提升(下文有介紹),let
所聲明的變量值只在let
命令所在的代碼塊內有效。
同一個作用域(下文有介紹)不可使用 let
重復聲明同一個變量。
注意:
- 聲明變量
- 沒有變量提升
- 不可重復聲明
- 只在
let
命令所在代碼塊有效
let sample = 1;sample = 2;let sample = 2; // 將會報錯
{ let sample = 1; console.log(sample); // 正常輸出 1}console.log(sample); // 將會報錯,因為只在let命令所在代碼塊有效
const
const
是用來聲明一個只讀常量。
一旦聲明,常量的值就不能改變。如果試著改變常量的值會報錯。
并且const
在聲明的時候就必須對其賦值,只聲明不賦值,也會報錯。
同一個作用域不可使用 const
重復聲明同一個常量。const
與let
一樣,都因為作用域原因,只能在所在代碼塊中有效。
const實際上保證的,并不是變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。
注意:
- 聲明常量
- 聲明后不可以改變
- 聲明的時候必須對其賦值
- 不可重復聲明
- 在
const
命令所在代碼塊有效
const sample = 1;sample = 2; // 將會報錯,const 聲明的變量不可以重新賦值
const sample; // 直接報錯,const 聲明的時候必須對其賦值
let 與 const
引入let
后,已經可以代替var
了,在let
與const
之中能用const
就盡量用const
。
let 與 const 不同處
let
與 const
的區別就是一個聲明變量一個聲明常量,變量可以重新賦值,常量不能重新賦值。
let sampleLet = 2;const sampleConst = 1;sampleLet = 3; // 正常sampleConst = 3; // 報錯
let 與 const 相同處
- 都只能先聲明后使用,不能變量提升。
- 都不可以在同一個作用域中重復聲明
- 都只在命令所在代碼塊有效
{sampleLet; // 報錯sampleConst; // 報錯let sampleLet = 2;const sampleConst = 1;let sampleLet = 3; // 報錯const sampleConst = 3; // 報錯}sampleLet; // 報錯sampleConst; // 報錯
變量提升(Hoisting)
在ES6之前,使用var
聲明變量時會產生一種叫做變量提升的特性。
無論是在代碼的哪個地方聲明的,都會提升到當前作用域的最頂部,這種行為叫做變量提升。
為了糾正這種現象,let
命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報錯
上文
let
與const
表示變量不能提升,真的是這樣嗎?
其實在 JavaScript 中,所有表示var,?let,?const,?function,?function*, class
的聲明都會被提升。let
與const
聲明變量會在環境實例化時被創建,但是在變量的詞法綁定之前不允許以任何方式對其進行訪問,也就是說,當你在聲明前調用變量將會報錯但是報錯信息不是未定義而是無法在初始化之前訪問。這里也就引出了下一個概念,叫做暫時性死區。
// var 聲明會變量提升,不會報錯,但是值為 undefinedconsole.log(sampleVar); // undefinedvar sampleVar = 1;// let 聲明不會變量提升,但是報錯不是 not definedconsole.log(sampleLet); // Cannot access sampleLet before initializationlet sampleLet = 1;// const 聲明不會變量提升,但是報錯不是 not definedconsole.log(sampleConst); // Cannot access sampleConst before initializationconst sampleConst = 1;// 直接使用沒有聲明的變量報錯為 ” is not defined “console.log(sample); //sample is not defined
暫時性死區
ES6 規定,如果代碼區塊中存在 let
和 const
命令聲明的變量,這個區塊對這些變量從一開始就形成了封閉作用域,凡是在聲明之前就使用這些變量,就會報錯。直到聲明語句完成,這些變量才能被訪問(獲取或設置),
這在語法上稱為“暫時性死區”(英temporal dead zone,簡 TDZ),即代碼塊開始到變量聲明語句完成之間的區域。
var sample = 1;if (true) { sample = 1; // 報錯 let sample;}
簡單來說,就是let
和 const
命令聲明的變量,在進入這個聲明代碼所在的作用域時,就已經存在,但是不可以獲取或使用,直到聲明語句完成,才可以訪問。
塊級作用域
作用域(scope,或譯有效范圍)就是變量和函數的可訪問范圍,即作用域控制著變量和函數的可見性和生命周期。
let與const 塊級作用域
作用域并不是ES6的新東西,但是在ES5只有全局作用域和函數作用域,為了解決塊級作用域,ES6可以使用**let**
與**const**
聲明一個塊級作用域的變量。?var
聲明的變量具有變量提升特性,所以沒有塊的概念,可以跨塊訪問,但不能跨函數。
外層作用域無法讀取內層作用域的變量。
{ // 塊作用域 var sampleVar = 1; let sampleLet = 2; const sampleConst = 3; console.log(sampleVar); // 成功輸出 1 console.log(sampleLet); // 成功輸出 2 console.log(sampleConst); // 成功輸出 3}console.log(sampleVar); // 成功輸出 1console.log(sampleLet); // 報錯 not definedconsole.log(sampleConst); // 報錯 not defined
?
ES6 允許塊級作用域的任意嵌套。
同一個作用域不可使用let
或const
聲明同一個變量,內層作用域可以定義外層作用域的同名變量。
{ { { let sample = Hello World; // 外層作用域 { let sample = sample; } // 不報錯 { console.log(sample); } // 正常輸出 ‘Hello World’ } }}
塊級作用域與函數聲明
ES5 規定,函數只能在頂層作用域和函數作用域之中聲明,不能在塊級作用域聲明。
ES6 規定,塊級作用域之中,函數聲明語句的行為類似于**let**
,在塊級作用域之外不可引用。
/* ES5,這兩種情況都是不合法的,因為這兩個函數聲明都是在塊作用域中聲明。 但應為瀏覽器為了兼容以前的舊代碼,還是支持在塊級作用域之中聲明函數。所以不會報錯*/ if (true) { function sampleFn() {}}try { function sampleFn() {}} catch(e) { // ...}
/* ES6,函數聲明語句的行為類似于let,在塊級作用域之外不可引用*/ if (true) { sampleFn(); // 正常輸出,函數聲明語句的行為類似于let function sampleFn() { console.log(Hello World); }}// 但其實在塊級作用域之外也可以引用函數,只不過值為undefinedif (false) { function sampleFn() { console.log(Hello World); }}console.log(sampleFn); // 正常輸出 undefinedsampleFn(); // 報錯為sampleFffffdn is not defined
為什么塊級作用域之外也可以引用函數呢?
如果改變了塊級作用域內聲明的函數的處理規則,顯然會對老代碼產生很大影響。為了減輕因此產生的不兼容問題,ES6 在附錄 B里面規定,瀏覽器的實現可以不遵守上面的規定(指函數聲明語句的行為),有自己的行為方式。
- 允許在塊級作用域內聲明函數。
- 函數聲明類似于
**var**
,即會提升到全局作用域或函數作用域的頭部。 - 同時,函數聲明還會提升到所在的塊級作用域的頭部。
注意,上面三條規則只對 ES6 的瀏覽器實現有效,其他環境的實現不用遵守,還是將塊級作用域的函數聲明當作let
處理。
我們應該避免在塊級作用域內聲明函數。如果確實需要,也應該寫成函數表達式,而不是函數聲明語句。
// 函數聲明語句,不要在塊作用域中使用,因為會有變量提升{ function sampleFn() { console.log("Hello World"); }}// 函數表達式,在塊作用域中,函數不會有變量提升{ const sampleFn = function () { console.log("Hello World"); }}
頂層對象
頂層對象,在瀏覽器環境指的是window
對象。
ES5 之中,頂層對象的屬性與全局變量是等價的。
ES6 為了改變這一點,
一方面規定,為了保持兼容性,var
命令和function
命令聲明的全局變量,依舊是頂層對象的屬性;
另一方面規定,let
命令、const
命令、class
命令聲明的全局變量,不屬于頂層對象的屬性。
/* ES5 之中,頂層對象的屬性賦值與全局變量的賦值,是同一件事。*/window.sample = 1;console.log(window.sample); // 正常輸出 1sample = 2;console.log(window.sample);// 正常輸出 2/* ES6 之中,let命令、const命令、class命令聲明的全局變量,不屬于頂層對象的屬性。*/var sampleVar = 1;console.log(window.sampleVar) // 正常輸出 1let sampleLet = 1;console.log(window.sampleLet) // 正常輸出 undefinedlet sampleConst = 1;console.log(window.sampleConst) // 正常輸出 undefined
window
提供全局環境(即全局作用域)所有代碼都是在這個環境中運行。
函數里面的this
,如果函數不是作為對象的方法運行,而是單純作為函數運行,this
會指向頂層對象。但是,嚴格模式下,這時this
會返回undefined
。
不管是嚴格模式,還是普通模式,new Function(return this)()
,總是會返回全局對象。
function sampleFn(){ console.log(this);}sampleFn(); // 正常輸出 輸出全局對象 windowfunction sampleFn1(){ "use strict"; console.log(this)}sampleFn1(); // 正常輸出 undefined// 開啟嚴格模式"use strict";const sample = new Function(return this)();console.log(sample); // 正常輸出 輸出全局對象 window