1.ECMAScript 和 JavaScript 的關(guān)系
ECMAScript 和 JavaScript 的關(guān)系是,前者是后者的規(guī)格使碾,后者是前者的一種實(shí)現(xiàn)
2.let命令
let命令的基本用法:ES6 新增了let命令蜜徽,用來(lái)聲明變量。它的用法類似于var票摇,但是所聲明的變量拘鞋,只在let命令所在的代碼塊內(nèi)有效。
{
var a = 10;
let b = 1;
}
console.log(a); //10
console.log(b); //報(bào)錯(cuò)
for循環(huán)有一個(gè)特別之處矢门,就是設(shè)置循環(huán)變量的那部分是一個(gè)父作用域盆色,而循環(huán)體內(nèi)部是一個(gè)單獨(dú)的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
//abc
//abc
//abc
上面代碼正確運(yùn)行祟剔,輸出了3次abc隔躲。這表明函數(shù)內(nèi)部的變量i與循環(huán)變量i不在同一個(gè)作用域,有各自單獨(dú)的作用域峡扩。
let不存在變量提升
console.log(foo); //undefined
var foo = 2;
console.log(bar); //報(bào)錯(cuò)
let bar = 2;
暫時(shí)性死區(qū)
var tmp = 123;
if (true) {
tmp = 'abc'; //報(bào)錯(cuò)
let tmp;
}
上面代碼中蹭越,存在全局變量tmp,但是塊級(jí)作用域內(nèi)let又聲明了一個(gè)局部變量tmp教届,導(dǎo)致后者綁定這個(gè)塊級(jí)作用域响鹃,所以在let聲明變量前驾霜,對(duì)tmp賦值會(huì)報(bào)錯(cuò)。
ES6明確規(guī)定买置,如果區(qū)塊中存在let和const命令粪糙,這個(gè)區(qū)塊對(duì)這些命令聲明的變量,從一開始就形成了封閉作用域忿项。凡是在聲明之前就使用這些變量蓉冈,就會(huì)報(bào)錯(cuò)。
if (true) {
tmp = 'abc';
console.log(tmp); //報(bào)錯(cuò)
let tmp;
console.log(tmp); //undefined
tmp = 123;
console.log(tmp); //123
}
總之轩触,暫時(shí)性死區(qū)的本質(zhì)就是寞酿,只要一進(jìn)入當(dāng)前作用域,所要使用的變量就已經(jīng)存在了脱柱,但是不可獲取伐弹,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量榨为。
不允許重復(fù)聲明
let不允許在相同作用域內(nèi)惨好,重復(fù)聲明同一個(gè)變量。
//報(bào)錯(cuò)
function fn() {
let a = 10;
var a = 1;
}
//報(bào)錯(cuò)
function fn() {
let a = 10;
let a = 1;
}
因此随闺,不能在函數(shù)內(nèi)部重新聲明參數(shù)日川。
function fn(arg) {
let arg; //報(bào)錯(cuò)
}
function fn(arg) {
{
let arg;//不報(bào)錯(cuò)
}
}
3.塊級(jí)作用域
-
為什么需要塊級(jí)作用域矩乐?
ES5 只有全局作用域和函數(shù)作用域龄句,沒(méi)有塊級(jí)作用域,這帶來(lái)很多不合理的場(chǎng)景绰精。
第一種場(chǎng)景撒璧,內(nèi)層變量可能會(huì)覆蓋外層變量。
function fn() { console.log(tmp); if (false) { var tmp = 'hello world'; } } fn(); //undefined
上面代碼的原意是笨使,if代碼塊的外部使用外層的tmp變量卿樱,內(nèi)部使用內(nèi)層的tmp變量。但是硫椰,函數(shù)f執(zhí)行后繁调,輸出結(jié)果為undefined,原因在于變量提升靶草,導(dǎo)致內(nèi)層的tmp變量覆蓋了外層的tmp變量蹄胰。
第二種場(chǎng)景,用來(lái)計(jì)數(shù)的循環(huán)變量泄露為全局變量奕翔。
var str = 'hello'; for (var i = 0; i < str.length; i++) { console.log(str[i]); } console.log(i);
上面代碼中裕寨,變量i只用來(lái)控制循環(huán),但是循環(huán)結(jié)束后,它并沒(méi)有消失宾袜,泄露成了全局變量捻艳。
-
塊級(jí)作用域和函數(shù)聲明
ES6 引入了塊級(jí)作用域,明確允許在塊級(jí)作用域之中聲明函數(shù)庆猫。ES6規(guī)定认轨,塊級(jí)作用域之中,函數(shù)聲明語(yǔ)句的行為類似于let月培,在塊級(jí)作用域之外不可引用整陌。
但是注意在瀏覽器的ES6環(huán)境中钝诚,塊級(jí)作用域內(nèi)聲明的函數(shù)胳岂,行為類似于var聲明的變量旧乞。
function f() { console.log('outer'); } (function () { if (false) { function f() { console.log('inside'); } } f(); }())
在es5中結(jié)果是inside诈铛,因?yàn)樵趇f內(nèi)聲明的函數(shù)f會(huì)被提升到函數(shù)頭部柠硕;在es6中結(jié)果是outer省撑,因?yàn)閴K級(jí)作用域內(nèi)聲明的函數(shù)類似于let感凤,對(duì)作用域之外沒(méi)有影響匾荆。但是在符合es6的瀏覽器中會(huì)報(bào)錯(cuò)拌蜘。實(shí)際上執(zhí)行的代碼是
function f() { console.log('outer'); } (function () { var f; if (false) { function f() { console.log('inside'); } } f();//報(bào)錯(cuò):f不是一個(gè)函數(shù) }())
所以考慮到環(huán)境導(dǎo)致的行為差異太大,應(yīng)該避免在塊級(jí)作用域內(nèi)聲明函數(shù)牙丽。如果確實(shí)需要简卧,也應(yīng)該寫成函數(shù)表達(dá)式,而不是函數(shù)聲明語(yǔ)句烤芦。
//函數(shù)聲明語(yǔ)句 { let a = 'smm'; function f() { return a; } } //函數(shù)表達(dá)式 { let a = 'smm'; let f = function () { return a; } }
4.const命令
-
基本用法
const聲明一個(gè)只讀的常量举娩。一旦聲明,常量的值就不能改變构罗。改變常量的值會(huì)報(bào)錯(cuò)铜涉。
const PI = 3.1415; PI //3.1415 PI = 3; //報(bào)錯(cuò)
const一旦聲明變量,就必須立即初始化遂唧,不能留到以后賦值芙代。對(duì)于const來(lái)說(shuō),只聲明不賦值盖彭,就會(huì)報(bào)錯(cuò)纹烹。
const foo; //報(bào)錯(cuò)
其余的與let相同,1.只在聲明所在的塊級(jí)作用域內(nèi)有效召边。2.const命令聲明的常量也是不提升铺呵,同樣存在暫時(shí)性死區(qū),只能在聲明的位置后面使用隧熙。3.const聲明的常量片挂,也與let一樣不可重復(fù)聲明。
5.數(shù)組的結(jié)構(gòu)賦值
ES6 允許按照一定模式,從數(shù)組和對(duì)象中提取值音念,對(duì)變量進(jìn)行賦值滋将,這被稱為解構(gòu)(Destructuring)。
以前症昏,為變量賦值随闽,只能直接指定值。
let a = 1;
let b = 2;
let c = 3;
ES6 允許寫成下面這樣肝谭。
let [a,b,c] = [1,2,3];
上面代碼表示掘宪,可以從數(shù)組中提取值,按照對(duì)應(yīng)位置攘烛,對(duì)變量賦值魏滚。
本質(zhì)上,這種寫法屬于“模式匹配”坟漱,只要等號(hào)兩邊的模式相同鼠次,左邊的變量就會(huì)被賦予對(duì)應(yīng)的值。下面是一些使用嵌套數(shù)組進(jìn)行解構(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; //[]
如果解構(gòu)不成功腥寇,變量的值就等于undefined。
let [foo] = [];
let [bar, foo] = [1];
以上兩種情況都屬于解構(gòu)不成功觅捆,foo的值都會(huì)等于undefined赦役。
另一種情況是不完全解構(gòu),即等號(hào)左邊的模式栅炒,只匹配一部分的等號(hào)右邊的數(shù)組掂摔。這種情況下,解構(gòu)依然可以成功赢赊。
let [x, y] = [1, 2, 3];
x; //1
y; //2
let [a, [b], c] = [1, [2, 3], 4];
a; //1
b; //2
c; //4
如果等號(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á)式)趋观。
默認(rèn)值
解構(gòu)賦值允許指定默認(rèn)值。
let [foo = true] = [];
foo; //true
let [x, y = 'b'] = ['a']; //x = 'a',y = 'b'
let [x, y = 'b'] = ['a',undefined]; //x = 'a',y = 'b'
注意锋边,ES6 內(nèi)部使用嚴(yán)格相等運(yùn)算符(===)皱坛,判斷一個(gè)位置是否有值。所以豆巨,如果一個(gè)數(shù)組成員只有嚴(yán)格等于undefined剩辟,默認(rèn)值才會(huì)生效的。看下面例子:
let [x = 1] = [undefined];
x; //1
let [x = 1] = [null];
x; //null
上面代碼中贩猎,如果一個(gè)數(shù)組成員是null熊户,默認(rèn)值就不會(huì)生效,因?yàn)閚ull不嚴(yán)格等于undefined吭服。
如果默認(rèn)值是一個(gè)表達(dá)式嚷堡,那么這個(gè)表達(dá)式是惰性求值的,即只有在用到的時(shí)候艇棕,才會(huì)求值蝌戒。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
x; //1
上面代碼中,因?yàn)閤能取到值沼琉,所以函數(shù)f根本不會(huì)執(zhí)行北苟。
默認(rèn)值可以引用解構(gòu)賦值的其他變量,但該變量必須已經(jīng)聲明打瘪。
let [x = 1, y = x] = []; //x = 1; y = 1
let [x = 1, y = x] = [2]; //x = 2; y = 2
let [x = 1, y = x] = [1,2]; //x = 1; y = 2
let [x = y, y = 1] = []; //報(bào)錯(cuò)
上面最后一個(gè)表達(dá)式之所以會(huì)報(bào)錯(cuò)友鼻,是因?yàn)閤用到默認(rèn)值y時(shí),y還沒(méi)有聲明闺骚。
6.對(duì)象的結(jié)構(gòu)賦值
let {bar, foo} = {foo: 'aaa', bar: 'bbb'};
foo; //'aaa'
bar; //'bbb'
let {baz} = {foo: 'aaa', bar: 'bbb'};
baz; //undefined
對(duì)象的解構(gòu)與數(shù)組有一個(gè)重要的不同彩扔。數(shù)組的元素是按次序排列的,變量的取值由它的位置決定葛碧;而對(duì)象的屬性沒(méi)有次序借杰,變量必須與屬性同名,才能取到正確的值进泼。
上面代碼的第一個(gè)例子,等號(hào)左邊的兩個(gè)變量的次序纤虽,與等號(hào)右邊兩個(gè)同名屬性的次序不一致乳绕,但是對(duì)取值完全沒(méi)有影響。第二個(gè)例子的變量沒(méi)有對(duì)應(yīng)的同名屬性逼纸,導(dǎo)致取不到值洋措,最后等于undefined。
對(duì)象的解構(gòu)賦值的內(nèi)部機(jī)制杰刽,是先找到同名屬性菠发,然后再賦給對(duì)應(yīng)的變量。真正被賦值的是后者贺嫂,而不是前者滓鸠。
let {foo:baz} = {foo: 'aaa', bar: 'bbb'};
baz; //'aaa'
foo; // 報(bào)錯(cuò)
與數(shù)組一樣,解構(gòu)也可以用于嵌套結(jié)構(gòu)的對(duì)象第喳。
let obj = {
p: [
'hello',
{y: 'world'}
]
};
let {p: [x, {y}]} = obj;
x; //hello
y; //world
注意糜俗,這時(shí)p是模式,不是變量,因此不會(huì)被賦值悠抹。如果p也要作為變量賦值珠月,可以寫成下面這樣。
let obj = {
p: [
'hello',
{y: 'world'}
]
};
let {p,p: [x, {y}]} = obj;
x; //hello
y; //world
p; //['hello',{y:'world'}]
下面再看一個(gè)例子:
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let {loc, loc: {start}, loc: {start: {line}}} = node;
line; //1
loc; //{start : obj}
start //{line : 1, column : 5}
上面代碼有三次解構(gòu)賦值楔敌,分別是對(duì)loc啤挎、start、line三個(gè)屬性的解構(gòu)賦值卵凑。注意侵浸,最后一次對(duì)line屬性的解構(gòu)賦值之中,只有l(wèi)ine是變量氛谜,loc和start都是模式掏觉,不是變量。
下面是嵌套賦值的例子:
let obj = {};
let arr = [];
({foo: obj.prop, bar: arr[0]} = {foo: 123, bar: true});
obj; //{prop:123}
arr; //[true]
上邊的例子嚴(yán)格按照對(duì)象的解構(gòu)賦值的內(nèi)部機(jī)制賦值值漫,看起來(lái)就是很簡(jiǎn)單了
對(duì)象的解構(gòu)也可以指定默認(rèn)值澳腹。同樣,默認(rèn)值生效的條件是杨何,對(duì)象的屬性值嚴(yán)格等于undefined酱塔。
var {x = 3} = {};
x; //3
var {x, y = 5} = {x: 1};
x; //1
y; //5
var {x: y = 3} = {};
y; //3
var {x: y = 3} = {x: 5};
y; //5
var {message: msg = 'smm'} = {};
msg; //'smm'
再看例子:
var {x = 3} = {x: undefined};
x; //3
var {x = 3} = {x: null};
x; //null
上面代碼中,如果x屬性等于null危虱,就不嚴(yán)格相等于undefined羊娃,導(dǎo)致默認(rèn)值不會(huì)生效。
如果解構(gòu)失敗埃跷,變量的值等于undefined蕊玷。
let {foo} = {bar: 'bar'};
foo; //undefined
如果解構(gòu)模式是嵌套的對(duì)象,而且子對(duì)象所在的父屬性不存在弥雹,那么將會(huì)報(bào)錯(cuò)垃帅。
let {foo: {bar}} = {baz: 'baz'}; //報(bào)錯(cuò)
上面代碼中,等號(hào)左邊對(duì)象的foo屬性剪勿,對(duì)應(yīng)一個(gè)子對(duì)象贸诚。該子對(duì)象的bar屬性,解構(gòu)時(shí)會(huì)報(bào)錯(cuò)厕吉。原因很簡(jiǎn)單酱固,因?yàn)閒oo這時(shí)等于undefined,再取子屬性就會(huì)報(bào)錯(cuò)头朱。
如果要將一個(gè)已經(jīng)聲明的變量用于解構(gòu)賦值运悲,必須非常小心。
//報(bào)錯(cuò)
let x;
{x} = {x : 1};
正確的寫法是:
let x;
({x} = {x : 1});
上面代碼的寫法會(huì)報(bào)錯(cuò)髓窜,因?yàn)?JavaScript 引擎會(huì)將{x}理解成一個(gè)代碼塊扇苞,從而發(fā)生語(yǔ)法錯(cuò)誤欺殿。只有不將大括號(hào)寫在行首,避免JavaScript將其解釋為代碼塊鳖敷,才能解決這個(gè)問(wèn)題脖苏。
7.字符串的結(jié)構(gòu)賦值
字符串也可以解構(gòu)賦值。這是因?yàn)榇藭r(shí)定踱,字符串被轉(zhuǎn)換成了一個(gè)類似數(shù)組的對(duì)象棍潘。
let [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
8.函數(shù)參數(shù)的結(jié)構(gòu)賦值
function add([x, y]) {
return x + y;
}
add([1, 2]); //3
上面代碼中亦歉,函數(shù)add的參數(shù)表面上是一個(gè)數(shù)組,但在傳入?yún)?shù)的那一刻畅哑,數(shù)組參數(shù)就被解構(gòu)成變量x和y肴楷。對(duì)于函數(shù)內(nèi)部的代碼來(lái)說(shuō),它們能感受到的參數(shù)就是x和y荠呐。
下面是另一個(gè)例子赛蔫。
[[1, 2], [3, 4]].map(([x, y]) => x + y); //[3,7]
函數(shù)參數(shù)的解構(gòu)也可以使用默認(rèn)值。
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); //[3,8]
move({x: 3}); //[3,0]
move({}); //[0,0]
move(); //[0,0]
上面代碼中泥张,函數(shù)move的參數(shù)是一個(gè)對(duì)象呵恢,通過(guò)對(duì)這個(gè)對(duì)象進(jìn)行解構(gòu),得到變量x和y的值媚创。如果解構(gòu)失敗渗钉,x和y等于默認(rèn)值。
注意钞钙,下面的寫法會(huì)得到不一樣的結(jié)果鳄橘。
function move({x, y} = {x: 0, y: 0}) {
return [x, y];
}
move({x: 3, y: 8}); //[3,8]
move({x: 3}); //[3,undefined]
move({}); //[undefined,undefined]
move(); //[0,0]
上面代碼是為函數(shù)move的參數(shù)指定默認(rèn)值,而不是為變量x和y指定默認(rèn)值歇竟,所以會(huì)得到與前一種寫法不同的結(jié)果挥唠。
undefined就會(huì)觸發(fā)函數(shù)參數(shù)的默認(rèn)值。
[1, undefined, 3].map((x = 'yes') => x);
// [1,'yes',3]
9.變量的解構(gòu)賦值的用途
1.交換變量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
2.從函數(shù)返回多個(gè)值
函數(shù)只能返回一個(gè)值焕议,如果要返回多個(gè)值,只能將它們放在數(shù)組或?qū)ο罄锓祷鼗」亍S辛私鈽?gòu)賦值盅安,取出這些值就非常方便。
//返回一個(gè)數(shù)組
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
//返回一個(gè)對(duì)象
function example() {
return {
foo: 1,
bar: 2
}
}
let {foo, bar} = example();
3.函數(shù)參數(shù)的定義
解構(gòu)賦值可以方便地將一組參數(shù)與變量名對(duì)應(yīng)起來(lái)世囊。
//參數(shù)是一組有序的值
function f([x, y, z]) {
}
f([1, 2, 3]);
//參數(shù)是一組無(wú)序的值
function f({foo, bar}) {
}
f({bar: 1, foo: 2});
4.提取JSON數(shù)據(jù)
解構(gòu)賦值對(duì)提取JSON對(duì)象中的數(shù)據(jù)别瞭,尤其有用。
let jsonData = {
id: 42,
status: 'OK',
data: [867, 5309]
};
let {id, status, data: num} = jsonData;
console.log(id, status, num);
//42,'OK',[867,5309]
上面代碼可以快速提取 JSON 數(shù)據(jù)的值株憾。
5.函數(shù)參數(shù)的默認(rèn)值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true
}) {
}
指定參數(shù)的默認(rèn)值蝙寨,就避免了在函數(shù)體內(nèi)部再寫var foo = config.foo || 'default foo';這樣的語(yǔ)句晒衩。
6.遍歷Map結(jié)構(gòu)
任何部署了Iterator接口的對(duì)象,都可以用for...of循環(huán)遍歷墙歪。Map結(jié)構(gòu)原生支持Iterator接口听系,配合變量的解構(gòu)賦值,獲取鍵名和鍵值就非常方便虹菲。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + 'is' + value);
}
// 'first is hello'
// ''second is world
如果只想獲取鍵名靠胜,或者只想獲取鍵值,可以寫成下面這樣毕源。
//獲取鍵名
for (let [key] of map) {
}
//獲取鍵值
for (let [,value] of map) {
}
7.輸入模塊的指定方法
加載模塊時(shí)浪漠,往往需要指定輸入哪些方法。解構(gòu)賦值使得輸入語(yǔ)句非常清晰霎褐。
const {SourseMapConsumer, SourseNode} = require('sourse-map');
參考文章阮一峰的ECMAScript 6入門