About
讀完阮一峰大神ES5教程后自覺(jué)獲益匪淺,遂拜讀其ES6教程诗力。為記錄所感所得凰浮,打算寫《阮一峰ES6教程讀后感》系列文章,有興趣的朋友可以關(guān)注一下姜骡。
一导坟、詳解let和const命令
let
和const
是ES6
新增的變量聲明命令,他們的用法與var
類似圈澈,但是經(jīng)它們聲明的變量與var
聲明的變量有很大的區(qū)別惫周。
(一)var和let的區(qū)別
-
var
聲明的變量的作用域?yàn)槿肿饔糜蚧蛘吆瘮?shù)作用域,而let
聲明的變量多一個(gè)塊級(jí)作用域 -
var
聲明的變量可以進(jìn)行變量提升康栈,即在聲明變量之前使用變量递递,而let
聲明的變量必須要在變量聲明語(yǔ)句之后使用,否則會(huì)報(bào)錯(cuò) -
let
聲明的變量存在“暫時(shí)性死區(qū)”啥么,即從區(qū)塊開(kāi)始至變量聲明語(yǔ)句之前是無(wú)法訪問(wèn)變量的 -
let
聲明的變量不允許重復(fù)聲明
1. 塊級(jí)作用域
我們先看看這個(gè)例子:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
其實(shí)我們預(yù)期的答案是6
登舞,但是如果這樣寫代碼,我們無(wú)法的到預(yù)期的答案悬荣,因?yàn)?code>a[6]里面存儲(chǔ)的函數(shù)語(yǔ)句中的i
指向的是一個(gè)全局變量菠秒,當(dāng)for
循環(huán)完畢后,i
的值為10氯迂,即函數(shù)語(yǔ)句中的每一個(gè)i
都指向了同一個(gè)地址践叠,而這個(gè)地址中存儲(chǔ)的數(shù)字為10
,所以我們無(wú)論通過(guò)數(shù)組a
中哪一個(gè)元素中的函數(shù)去打印i
得到的答案都是10
為了得到我們預(yù)期的答案嚼蚀,我們應(yīng)該將代碼進(jìn)行一定的改寫:
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
可以看到這里面的for
循環(huán)的i
是通過(guò)let聲明的禁灼,所以i
僅在當(dāng)前循環(huán)中有效,在其他循環(huán)中無(wú)法訪問(wèn)到當(dāng)前循環(huán)的i
轿曙,我們可以理解為每一個(gè)i
是獨(dú)立的弄捕,所以數(shù)組a
中每一個(gè)元素中存儲(chǔ)的函數(shù)語(yǔ)句中的i
指向的是不同的地址僻孝,每個(gè)地址存儲(chǔ)的是當(dāng)前循環(huán)中的i
值
2. 不存在變量提升
正常的邏輯是我們必須先聲明一個(gè)變量,然后再去使用守谓,如果在沒(méi)有聲明就去使用而僅僅告訴我undefined
穿铆,這顯然不能讓我們滿意。
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的情況
console.log(bar); // 報(bào)錯(cuò)ReferenceError
let bar = 2;
通過(guò)上面的代碼我們可以發(fā)現(xiàn)分飞,使用let
聲明的變量終于糾正了這一點(diǎn)悴务,我們無(wú)法在聲明變量之前去使用它。
3. 暫時(shí)性死區(qū)(temporal dead zone譬猫,簡(jiǎn)稱 TDZ)
正因?yàn)槭褂?code>let聲明的變量不存在變量提升現(xiàn)象讯檐,所以會(huì)出現(xiàn)暫時(shí)性死區(qū)的現(xiàn)象
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
tmp = '2333'
console.log(tmp) // 2333
}
盡管我們知道在使用大括號(hào)括起來(lái)的代碼塊內(nèi)使用let
聲明變量,該變量的作用域?yàn)閴K級(jí)作用域染服,但是該塊級(jí)作用域并非整個(gè)區(qū)塊别洪,從該區(qū)塊開(kāi)始到聲明變量之前的區(qū)域被稱之為“暫時(shí)性死區(qū)”,因?yàn)樵谶@一區(qū)域內(nèi)我們無(wú)法訪問(wèn)到后面聲明的變量柳刮。
4. 不允許重復(fù)聲明
在使用let
聲明的變量的作用域內(nèi)挖垛,不允許重復(fù)聲明,無(wú)論你用let
秉颗,var
還是const
痢毒。在函數(shù)內(nèi)也一樣:
function func(arg) {
let arg;
}
func() // 報(bào)錯(cuò)
function func(arg) {
{
let arg;
}
}
func() // 不報(bào)錯(cuò)
(二)塊級(jí)作用域與函數(shù)聲明
ES5
規(guī)定,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明蚕甥,不能在塊級(jí)作用域聲明哪替。但是,瀏覽器沒(méi)有遵守這個(gè)規(guī)定菇怀,為了兼容以前的舊代碼凭舶,還是支持在塊級(jí)作用域之中聲明函數(shù),因此上面兩種情況實(shí)際都能運(yùn)行爱沟,不會(huì)報(bào)錯(cuò)帅霜。比如:
// 在使用ES5標(biāo)準(zhǔn)的瀏覽器中
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重復(fù)聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}()); // 'I am inside!'
因?yàn)?code>ES5中,函數(shù)也是“一等公民”具有變量提升的作用呼伸,但是函數(shù)的變量提升不僅僅是函數(shù)名的變量提升身冀,而是包括整個(gè)函數(shù)體的提升,所以上述代碼可翻譯為:
// 在使用ES5標(biāo)準(zhǔn)的瀏覽器中
function f() { console.log('I am outside!'); }
(function () {
// 重復(fù)聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}()); // 'I am inside!'
但是在使用ES6
標(biāo)準(zhǔn)的瀏覽器中括享,上述代碼就會(huì)報(bào)錯(cuò)ES6 在附錄 B里面規(guī)定闽铐,瀏覽器的實(shí)現(xiàn)可以不遵守上面的規(guī)定,有自己的行為方式奶浦。
- 允許在塊級(jí)作用域內(nèi)聲明函數(shù)。
- 函數(shù)聲明類似于var踢星,即會(huì)提升到全局作用域或函數(shù)作用域的頭部澳叉。
- 同時(shí),函數(shù)聲明還會(huì)提升到所在的塊級(jí)作用域的頭部。
所以上述代碼可翻譯為:
// 在使用ES6標(biāo)準(zhǔn)的瀏覽器中
function f() { console.log('I am outside!'); }
(function () {
var f // 此時(shí)f為undefined
if (false) {
}
f();
}()); // Uncaught TypeError: f is not a function
(三)const命令
let
和const
的用法差不多成洗,只不過(guò)是const
一般被用來(lái)聲明常量五督,即聲明語(yǔ)句中必須賦值,并且聲明之后不能更改
1. const命令的本質(zhì)
const
實(shí)際上保證的瓶殃,并不是變量的值不得改動(dòng)充包,而是變量指向的那個(gè)內(nèi)存地址所保存的數(shù)據(jù)不得改動(dòng)。對(duì)于簡(jiǎn)單類型的數(shù)據(jù)(數(shù)值遥椿、字符串基矮、布爾值),值就保存在變量指向的那個(gè)內(nèi)存地址冠场,因此等同于常量家浇。但對(duì)于復(fù)合類型的數(shù)據(jù)(主要是對(duì)象和數(shù)組),變量指向的內(nèi)存地址碴裙,保存的只是一個(gè)指向?qū)嶋H數(shù)據(jù)的指針钢悲,const
只能保證這個(gè)指針是固定的(即總是指向另一個(gè)固定的地址),至于它指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的舔株,就完全不能控制了莺琳。因此,將一個(gè)對(duì)象聲明為常量必須非常小心载慈。
const foo = {};
// 為 foo 添加一個(gè)屬性惭等,可以成功
foo.prop = 123;
foo.prop // 123
// 將 foo 指向另一個(gè)對(duì)象,就會(huì)報(bào)錯(cuò)
foo = {}; // TypeError: "foo" is read-only
(四)頂層對(duì)象的屬性
在ES6
以前的標(biāo)準(zhǔn)中娃肿,我們使用var
和function
聲明的全局變量會(huì)變成頂層對(duì)象的屬性咕缎,ES6
為了改變這一點(diǎn),一方面規(guī)定料扰,為了保持兼容性凭豪,var
命令和function命
令聲明的全局變量,依舊是頂層對(duì)象的屬性晒杈;另一方面規(guī)定嫂伞,let
命令、const
命令拯钻、class
命令聲明的全局變量帖努,不屬于頂層對(duì)象的屬性。也就是說(shuō)粪般,從 ES6
開(kāi)始拼余,全局變量將逐步與頂層對(duì)象的屬性脫鉤。
var a = 1;
// 如果在 Node 的 REPL 環(huán)境亩歹,可以寫成 global.a
// 或者采用通用方法匙监,寫成 this.a
window.a // 1
let b = 1;
window.b // undefined
二凡橱、變量的解構(gòu)賦值
ES6
允許按照一定模式,從數(shù)組和對(duì)象中提取值亭姥,對(duì)變量進(jìn)行賦值稼钩,這被稱為解構(gòu)(Destructuring)。
(一)基本用法
let [a, b, c] = [1, 2, 3]; // a = 1, b = 2, c = 3
上面代碼表示达罗,可以從數(shù)組中提取值坝撑,按照對(duì)應(yīng)位置,對(duì)變量賦值粮揉。本質(zhì)上巡李,這種寫法屬于“模式匹配”,只要等號(hào)兩邊的模式相同滔蝉,左邊的變量就會(huì)被賦予對(duì)應(yīng)的值
1. 解構(gòu)成功
解構(gòu)成功的條件是=
兩邊的模式完全相同
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
2. 不完全解構(gòu)
因?yàn)槲覀兪褂媒鈽?gòu)的目的是為了給=
左邊的變量賦值击儡,那么如果從左到右都能找到對(duì)應(yīng)元素,那么就能完成解構(gòu)蝠引,如果此時(shí)從右到左無(wú)法完成一一對(duì)應(yīng)阳谍,那么就稱之為不完全解構(gòu),例如:
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
3. 解構(gòu)失敗
如果等號(hào)的右邊不是數(shù)組(或者嚴(yán)格地說(shuō)螃概,不是可遍歷的結(jié)構(gòu))矫夯,那么將會(huì)報(bào)錯(cuò)。
// 報(bào)錯(cuò)
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的語(yǔ)句都會(huì)報(bào)錯(cuò)吊洼,因?yàn)榈忍?hào)右邊的值训貌,要么轉(zhuǎn)為對(duì)象以后不具備 Iterator 接口(前五個(gè)表達(dá)式),要么本身就不具備 Iterator 接口(最后一個(gè)表達(dá)式)冒窍。
(二)解構(gòu)表達(dá)式默認(rèn)賦值
同聲明函數(shù)一樣递沪,我們可以在函數(shù)表達(dá)式中聲明實(shí)參時(shí)給實(shí)參賦默認(rèn)值,如果調(diào)用該函數(shù)時(shí)综液,形參為空款慨,那么實(shí)參就采用聲明函數(shù)時(shí)的默認(rèn)值。解構(gòu)賦值表達(dá)式同理
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x, y = 'b'] = ['a', 'c']; // x='a', y='c'
(三)對(duì)象的解構(gòu)賦值
與數(shù)組一樣谬莹,解構(gòu)也可以用于嵌套結(jié)構(gòu)的對(duì)象檩奠。
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
此時(shí)的p
僅僅是一個(gè)模式,如果要對(duì)p
賦值附帽,必須這么寫:
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
(四)字符串解構(gòu)賦值和布爾值解構(gòu)賦值
1. 字符串解構(gòu)賦值
字符串也可以解構(gòu)賦值埠戳。這是因?yàn)榇藭r(shí),字符串被轉(zhuǎn)換成了一個(gè)類似數(shù)組的對(duì)象蕉扮。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
類似數(shù)組的對(duì)象都有一個(gè)length屬性整胃,因此還可以對(duì)這個(gè)屬性解構(gòu)賦值。
let {length : len} = 'hello';
len // 5
2. 布爾值解構(gòu)賦值
解構(gòu)賦值時(shí)喳钟,如果等號(hào)右邊是數(shù)值和布爾值爪模,則會(huì)先轉(zhuǎn)為對(duì)象欠啤。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
(五)解構(gòu)賦值的作用
1. 交換變量的值
以前,這個(gè)功能只在Python中使用過(guò)屋灌,如今可以在JavaScript中使用還是很開(kāi)心的
[x, y] = [y, x]
2. 從函數(shù)返回多個(gè)值
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
3. 函數(shù)參數(shù)的定義
解構(gòu)賦值可以方便地將一組參數(shù)與變量名對(duì)應(yīng)起來(lái)。
4. 提取JSON數(shù)據(jù)
這個(gè)太有用了应狱,因?yàn)槲覀儚慕涌谥蝎@得的數(shù)據(jù)一般都是JSON對(duì)象共郭,所以使用結(jié)構(gòu)賦值可以使我們的代碼更簡(jiǎn)潔
const {ret, data} = res.data
5. 遍歷Map結(jié)構(gòu)
任何部署了 Iterator 接口的對(duì)象,都可以用for...of循環(huán)遍歷疾呻。Map 結(jié)構(gòu)原生支持 Iterator 接口除嘹,配合變量的解構(gòu)賦值,獲取鍵名和鍵值就非常方便岸蜗。這個(gè)在Vue中也是經(jīng)常使用
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
6. 輸入模塊的指定方法
加載模塊時(shí)尉咕,往往需要指定輸入哪些方法。解構(gòu)賦值使得輸入語(yǔ)句非常清晰璃岳。
const { SourceMapConsumer, SourceNode } = require("source-map");