ECMAScript 6.0( 以下簡(jiǎn)稱(chēng)ES6) 是JavaScript語(yǔ)言的下一代標(biāo)準(zhǔn)。
ECMAScript和JavaScript的關(guān)系是, 前者是后者的規(guī)格, 后者是前者的一種實(shí)現(xiàn)( 另外的ECMAScript方言還有Jscript和ActionScript) 。 日常場(chǎng)合, 這兩個(gè)詞是可以互換的。
在前端工程化的現(xiàn)在,學(xué)習(xí)es6還是有必要的。
本文為個(gè)人根據(jù)阮老師的es6標(biāo)準(zhǔn)入門(mén)學(xué)習(xí)筆記。
ES6
let和const命令
let
- let用來(lái)聲明變量。 它的用法類(lèi)似于var, 但是所聲明的變量, 只在let命令所在的代碼塊內(nèi)有效
- 在循環(huán)中,如果變量i是var聲明的, 在全局范圍內(nèi)都有效。 所以每一次循環(huán),新的i值都會(huì)覆蓋舊值,如果變量i是let聲明的, 當(dāng)前的i只在本輪循環(huán)有效, 所以每一次循環(huán)的i其實(shí)都是一個(gè)新的變量。
- let不像var那樣會(huì)發(fā)生“變量提升”現(xiàn)象。 所以, 變量一定要在聲明后使用, 否則報(bào)錯(cuò)
- let不允許在相同作用域內(nèi), 重復(fù)聲明同一個(gè)變量
//所聲明的變量, 只在let命令所在的代碼塊內(nèi)有效
{
let a = 10;
var b = 1;
console.log('a=' + a + '\nb=' + b);
}
console.log('let代碼塊外b=' + b);
// console.log('let代碼塊外b=' + a);
arr = [1, 2, 3, 4, 5, 6, 4];
for (let i = 0; i < arr.length; i++) {
console.log(i);
}
/**
* 變量i是var聲明的, 在全局范圍內(nèi)都有效。 所以每一次循環(huán),
* 新的i值都會(huì)覆蓋舊值, 導(dǎo)致最后輸出的是最后一輪的i的值
*/
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = i;
}
console.log(i);
/**
* 變量i是let聲明的, 當(dāng)前的i只在本輪循環(huán)有效, 所以每一次循環(huán)的i其實(shí)都是一個(gè)新的變量, 所以最后輸出的是6
* @type {Array}
*/
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = i;
}
console.log(a[6]);
/**
* 只要塊級(jí)作用域內(nèi)存在let命令, 它所聲明的變量就“綁定”( binding) 這個(gè)區(qū)域, 不再受外部的影響
*存在全局變量tmp, 但是塊級(jí)作用域內(nèi)let又聲明了一個(gè)局部變量tmp, 導(dǎo)致后者綁定這個(gè)塊級(jí)作用域, 所以在let聲明變量前, 對(duì)tmp賦
值會(huì)報(bào)錯(cuò)。
如果區(qū)塊中存在let和const命令, 這個(gè)區(qū)塊對(duì)這些命令聲明的變量, 從一開(kāi)始就形成了封閉作用域。 凡是在聲明之前就使用這些變
量, 就會(huì)報(bào)錯(cuò)。
總之, 在代碼塊內(nèi), 使用let命令聲明變量之前, 該變量都是不可用的
暫時(shí)性死區(qū)”也意味著typeof不再是一個(gè)百分之百安全的操作
* @type {number}
*/
var tmp = 123;
if (true) {
//tmp = 'abc'; // ReferenceError
let tmp;
}
/**
*調(diào)用bar函數(shù)之所以報(bào)錯(cuò)
*參數(shù)x默認(rèn)值等于另一個(gè)參數(shù)y, 而此時(shí)y還沒(méi)有聲明
* @param x
* @param y
* @returns {[null,null]}
*/
function bar(x = y, y = 2) {
return [x, y];
}
//bar(); //報(bào)錯(cuò)
function bar(x = 2, y = x) {
return [x, y];
}
bar();
/**
* let不允許在相同作用域內(nèi), 重復(fù)聲明同一個(gè)變量,都會(huì)報(bào)錯(cuò)
*/
// function () {
// let a = 10;
// var a = 1;
// }
//
// function () {
// let a = 10;
// let a = 1;
// }
結(jié)果為:

const
- const聲明一個(gè)只讀的常量。 一旦聲明, 常量的值就不能改變。
- const聲明的變量不得改變值, 這意味著, const一旦聲明變量, 就必須立即初始化, 不能留到以后賦值。
- onst的作用域與let命令相同: 只在聲明所在的塊級(jí)作用域內(nèi)有效。
- const命令聲明的常量也是不提升, 同樣存在暫時(shí)性死區(qū), 只能在聲明的位置后面使用
- const聲明的常量, 也與let一樣不可重復(fù)聲明
const PI=3.1415;
console.log(PI);
/**
* 量a是一個(gè)數(shù)組, 這個(gè)數(shù)組本身是可寫(xiě)的, 但是如果將另一個(gè)數(shù)組賦值給a, 就會(huì)報(bào)錯(cuò)
* @type {Array}
*/
const a=[];
a.push('hello');
//a = ['Dave'];
全局對(duì)象的屬性
- var命令和function命令聲明的全局變量, 依舊是全局對(duì)象的屬性
- let命令、 const命令、 class命令聲明的全局變量, 不屬于全局對(duì)象的屬性
var a = 1;
// 如果在Node的REPL環(huán)境, 可以寫(xiě)成global.a
// 或者采用通用方法, 寫(xiě)成this.a
this.a // 1
let b = 1;
//window.b // undefined
變量的解構(gòu)賦值
數(shù)組的解構(gòu)賦值
- ES6允許按照一定模式, 從數(shù)組和對(duì)象中提取值, 對(duì)變量進(jìn)行賦值, 這被稱(chēng)為解構(gòu)( Destructuring)
/**
* 同時(shí)給abc賦值可以用一下方式
*/
var [a, b, c] = [1, 2, 3];
console.log('a='+a+' b='+b+' c='+c);
/**
* 這種寫(xiě)法屬于“模式匹配”, 只要等號(hào)兩邊的模式相同, 左邊的變量就會(huì)被賦予對(duì)應(yīng)的值
*/
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log('foo='+foo+' bar='+bar+' baz='+baz);
let [ , , third] = ["foo", "bar", "baz"];
console.log('third='+third);
let [x, , y] = [1, 2, 3];
console.log('x='+x+' y='+y);
let [head, ...tail] = [1, 2, 3, 4];
console.log('head='+head+' tail='+tail);
/**
*另一種情況是不完全解構(gòu), 即等號(hào)左邊的模式, 只匹配一部分的等號(hào)右邊的數(shù)組
*/
let [x2, y2] = [1, 2, 3];
console.log('x2='+x2+' y2='+y2);
let [a2, [b2], c2] = [1, [2, 3], 4];
console.log('a2='+a2+' b='+b2+' c2='+c2);
結(jié)果為:

注:
- 只要某種數(shù)據(jù)結(jié)構(gòu)具有Iterator接口, 都可以采用數(shù)組形式的解構(gòu)賦值
- 解構(gòu)賦值允許指定默認(rèn)值,ES6內(nèi)部使用嚴(yán)格相等運(yùn)算符( ===) , 判斷一個(gè)位置是否有值。 所以, 如果一個(gè)數(shù)組成員不嚴(yán)格等于undefined, 默認(rèn)值是不會(huì)生效的
對(duì)象的解構(gòu)賦值
解構(gòu)不僅可以用于數(shù)組, 還可以用于對(duì)象
- 對(duì)象的解構(gòu)與數(shù)組有一個(gè)重要的不同。 數(shù)組的元素是按次序排列的, 變量的取值由它的位置決定; 而對(duì)象的屬性沒(méi)有次序, 變量必須與屬性同名, 才
能取到正確的值。 - 對(duì)象的解構(gòu)也可以指定默認(rèn)值。默認(rèn)值生效的條件是, 對(duì)象的屬性值嚴(yán)格等于undefined。
var {foo, bar} = {foo: "aaa", bar: "bbb"};
console.log('foo=' + foo + ' bar=' + bar);
var {foo2: foo2, bar2: bar2} = {foo2: "aaa", bar2: "bbb"};
console.log('foo2=' + foo2 + ' bar2=' + bar2);
/**
* 真正被賦值的是變量baz, 而不是模式foo。
*/
var {foo: baz} = {foo: "aaa", bar: "bbb"};
console.log(baz);
/**
* 和數(shù)組一樣, 解構(gòu)也可以用于嵌套結(jié)構(gòu)的對(duì)象
* 這時(shí)p是模式, 不是變量, 因此不會(huì)被賦值
* @type {{p: [string,null]}}
*/
var obj = {
p: [
'Hello',
{y: 'World'}
]
};
var {p: [x, {y}]} = obj;
console.log('x=' + x + ' y=' + y);
/**
* line和column是變量, loc和start都是模式, 不會(huì)被賦值
* @type {{loc: {start: {line: number, column: number}}}}
*/
var node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
var {loc: {start: {line, column}}} = node;
console.log('line=' + line + ' column=' + column);
/**
* 嵌套賦值
* let命令下面一行的圓括號(hào)是必須的, 否則會(huì)報(bào)錯(cuò)。 因?yàn)榻馕銎鲿?huì)將起首的大括號(hào), 理解成一個(gè)代碼塊, 而不是賦值語(yǔ)句。
* @type {{}}
*/
let obj2 = {};
let arr = [];
({foo: obj2.prop, bar: arr[0]} = {foo: 123, bar: true});
console.log('obj2.prop='+obj2.prop+' arr='+arr);
/**
* 對(duì)象的解構(gòu)賦值, 可以很方便地將現(xiàn)有對(duì)象的方法, 賦值到某個(gè)變量
* 將Math對(duì)象的對(duì)數(shù)、 正弦、 余弦三個(gè)方法, 賦值到對(duì)應(yīng)的變量上
*/
let { log, sin, cos } = Math;
結(jié)果為:

字符串的解構(gòu)賦值
字符串也可以解構(gòu)賦值。 這是因?yàn)榇藭r(shí), 字符串被轉(zhuǎn)換成了一個(gè)類(lèi)似數(shù)組的對(duì)象
注:類(lèi)似數(shù)組的對(duì)象都有一個(gè)length屬性, 因此還可以對(duì)這個(gè)屬性解構(gòu)賦值
let [a, b, c, d, e] = 'hello';
let {length : len} = 'hello';
console.log(a+b+c+d+e+' length='+len);
結(jié)果:
hello length=5
函數(shù)參數(shù)的解構(gòu)賦值
函數(shù)的參數(shù)也可以使用解構(gòu)賦值。
函數(shù)add的參數(shù)表面上是一個(gè)數(shù)組, 但在傳入?yún)?shù)的那一刻, 數(shù)組參數(shù)就被解構(gòu)成變量x和y
function add([x, y]) {
return x + y;
}
console.log(add([1, 2]));
/**
* 使用默認(rèn)值
* @param x
* @param y
* @returns {[null,null]}
*/
function move({x = 0, y = 0} = {}) {
return [x, y];
}
圓括號(hào)問(wèn)題
只要有可能導(dǎo)致解構(gòu)的歧義, 就不得使用圓括號(hào)
不能使用圓括號(hào)的情況
- 變量聲明語(yǔ)句中, 不能帶有圓括號(hào)
- 函數(shù)參數(shù)中, 模式不能帶有圓括號(hào)。
- 賦值語(yǔ)句中, 不能將整個(gè)模式, 或嵌套模式中的一層, 放在圓括號(hào)之中。
// 全部報(bào)錯(cuò)
var [(a)] = [1];
var {x: (c)} = {};
var ({x: c}) = {};
var {(x: c)} = {};
var {(x): c} = {};
var { o: ({ p: p }) } = { o: { p: 2 } };
// 報(bào)錯(cuò)
function f([(z)]) { return z; }
// 全部報(bào)錯(cuò)
({ p: a }) = { p: 42 };
([a]) = [5];
可以使用圓括號(hào)的情況
賦值語(yǔ)句的非模式部分, 可以使用圓括號(hào)。
[(b)] = [3]; // 正確
({ p: (d) } = {}); // 正確
[(parseInt.prop)] = [3]; // 正確
變量的解構(gòu)賦值用途
交換變量的值
[x, y] = [y, x];
從函數(shù)返回多個(gè)值
// 返回一個(gè)數(shù)組
function example() {
return [1, 2, 3];
}
var [a, b, c] = example();
// 返回一個(gè)對(duì)象
function example() {
return {
foo: 1,
bar: 2
};
}
var {foo, bar} = example();
提取JSON數(shù)據(jù)
var jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let {id, status, data: number} = jsonData;
遍歷Map結(jié)構(gòu)
任何部署了Iterator接口的對(duì)象, 都可以用for...of循環(huán)遍歷。 Map結(jié)構(gòu)原生支持Iterator接口, 配合變量的解構(gòu)賦值, 獲取鍵名和鍵值就非常方便。
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
如果只想獲取鍵名, 或者只想獲取鍵值, 可以寫(xiě)成下面這樣
// 獲取鍵名
for (let [key] of map) {
// ...
}
// 獲取鍵值
for (let [,value] of map) {
// ...
}
字符串
字符串的遍歷器接口
字符串可以被for...of循環(huán)遍歷
for (let codePoint of 'hello') {
console.log(codePoint)
}
常用的新方法
includes(), startsWith(), endsWith()
- includes(): 返回布爾值, 表示是否找到了參數(shù)字符串
- startsWith(): 返回布爾值, 表示參數(shù)字符串是否在源字符串的頭部
- endsWith(): 返回布爾值, 表示參數(shù)字符串是否在源字符串的尾部。
var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
- 三個(gè)方法都支持第二個(gè)參數(shù), 表示開(kāi)始搜索的位置
var s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
repeat()
repeat 方法返回一個(gè)新字符串, 表示將原字符串重復(fù)n 次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
模板字符串
模板字符串( template string) 是增強(qiáng)版的字符串, 用反引號(hào)( `) 標(biāo)識(shí)。 它可以當(dāng)作普通字符串使用, 也可以用來(lái)定義多行字符串, 或者在字符串中
嵌入變量
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入變量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
//可以放入任意的JavaScript表達(dá)式, 可以進(jìn)行運(yùn)算
var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
//調(diào)用函數(shù)
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
注:
- 如果在模板字符串中需要使用反引號(hào), 則前面要用反斜杠轉(zhuǎn)義
- 如果使用模板字符串表示多行字符串, 所有的空格和縮進(jìn)都會(huì)被保留在輸出之中
- 模板字符串中嵌入變量, 需要將變量名寫(xiě)在${}之中
- 大括號(hào)內(nèi)部可以放入任意的JavaScript表達(dá)式, 可以進(jìn)行運(yùn)算, 以及引用對(duì)象屬性
- 模板字符串之中還能調(diào)用函數(shù)
數(shù)值擴(kuò)展
Math對(duì)象的擴(kuò)展
Math.trunc()
Math.trunc方法用于去除一個(gè)數(shù)的小數(shù)部分, 返回整數(shù)部分
Math.sign()
Math.sign方法用來(lái)判斷一個(gè)數(shù)到底是正數(shù)、 負(fù)數(shù)、 還是零
Math.cbrt()
Math.cbrt方法用于計(jì)算一個(gè)數(shù)的立方根
數(shù)組的擴(kuò)展
Array.from()
Array.from方法用于將兩類(lèi)對(duì)象轉(zhuǎn)為真正的數(shù)組: 類(lèi)似數(shù)組的對(duì)象( array-like object) 和可遍歷( iterable) 的對(duì)象( 包括ES6新增的數(shù)據(jù)結(jié)構(gòu)Set和
Map) 。
//常見(jiàn)的類(lèi)似數(shù)組的對(duì)象是DOM操作返回的NodeList集合
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
//只要是部署了Iterator接口的數(shù)據(jù)結(jié)構(gòu), Array.from都能將其轉(zhuǎn)為數(shù)組
Array.from('hello')
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
數(shù)組實(shí)例的find()和findIndex()
數(shù)組實(shí)例的find方法, 用于找出第一個(gè)符合條件的數(shù)組成員。 它的參數(shù)是一個(gè)回調(diào)函數(shù), 所有數(shù)組成員依次執(zhí)行該回調(diào)函數(shù), 直到找出第一個(gè)返回值
為true的成員, 然后返回該成員。 如果沒(méi)有符合條件的成員, 則返回undefined。
數(shù)組實(shí)例的fill()
fill方法使用給定值, 填充一個(gè)數(shù)組
- fill方法還可以接受第二個(gè)和第三個(gè)參數(shù), 用于指定填充的起始位置和結(jié)束位置
數(shù)組實(shí)例的entries(), keys()和values()
可以用for...of循環(huán)進(jìn)行遍歷, 唯一的區(qū)別是keys()是對(duì)鍵名的遍歷、 values()是對(duì)鍵值的遍歷, entries()是對(duì)鍵值對(duì)的遍歷。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
//如果不使用for...of循環(huán), 可以手動(dòng)調(diào)用遍歷器對(duì)象的next方法, 進(jìn)行遍歷
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
數(shù)組實(shí)例的includes()
表示某個(gè)數(shù)組是否包含給定的值, 與字符串的includes方法類(lèi)似
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true
該方法的第二個(gè)參數(shù)表示搜索的起始位置, 默認(rèn)為0。 如果第二個(gè)參數(shù)為負(fù)數(shù), 則表示倒數(shù)的位置, 如果這時(shí)它大于數(shù)組長(zhǎng)度( 比如第二個(gè)參數(shù)為-4,
但數(shù)組長(zhǎng)度為3) , 則會(huì)重置為從0開(kāi)始
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
函數(shù)的擴(kuò)展
函數(shù)參數(shù)的默認(rèn)值
ES6允許為函數(shù)的參數(shù)設(shè)置默認(rèn)值, 即直接寫(xiě)在參數(shù)定義的后面
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
rest參數(shù)
ES6引入rest參數(shù)( 形式為“...變量名”) , 用于獲取函數(shù)的多余參數(shù), 這樣就不需要使用arguments對(duì)象了。 rest參數(shù)搭配的變量是一個(gè)數(shù)組, 該變量將多余的參數(shù)放入數(shù)組中
//利用rest參數(shù), 可以向該函數(shù)傳入任意數(shù)目的參數(shù)
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
//rest參數(shù)中的變量代表一個(gè)數(shù)組, 所以數(shù)組特有的方法都可以用于這個(gè)變量
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
注:rest參數(shù)之后不能再有其他參數(shù)( 即只能是最后一個(gè)參數(shù))
擴(kuò)展運(yùn)算符
擴(kuò)展運(yùn)算符( spread) 是三個(gè)點(diǎn)( ...) 。 它好比rest參數(shù)的逆運(yùn)算, 將一個(gè)數(shù)組轉(zhuǎn)為用逗號(hào)分隔的參數(shù)序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
//合并數(shù)組
[1, 2, ...more]
[...arr1, ...arr2, ...arr3]
//轉(zhuǎn)為真正的數(shù)組。
var nodeList = document.querySelectorAll('div');
var array = [...nodeList];
//map轉(zhuǎn)數(shù)組
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
箭頭函數(shù)
使用“箭頭”( =>) 定義函數(shù)
var f = v => v;
//等同于
// var f = function (v) {
// return v;
// };
//如果箭頭函數(shù)不需要參數(shù)或需要多個(gè)參數(shù), 就使用一個(gè)圓括號(hào)代表參數(shù)部分
var f2 = () => 5;
// 等同于
// var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
// var sum = function(num1, num2) {
// return num1 + num2;
// };
//如果箭頭函數(shù)的代碼塊部分多于一條語(yǔ)句, 就要使用大括號(hào)將它們括起來(lái), 并且使用return語(yǔ)句返回
var sum = (num1, num2) => {
return num1 + num2;
}
//由于大括號(hào)被解釋為代碼塊, 所以如果箭頭函數(shù)直接返回一個(gè)對(duì)象, 必須在對(duì)象外面加上括號(hào)
var getTempItem = id => ({id: id, name: "Temp"});
console.log(getTempItem(5));
const full = ({ first, last }) => first + ' ' + last;
// 等同于
// function full(person) {
// return person.first + ' ' + person.last;
// }
console.log([1,2,3].map(x => x * x));
注:
- 函數(shù)體內(nèi)的this對(duì)象, 就是定義時(shí)所在的對(duì)象, 而不是使用時(shí)所在的對(duì)象。
- 不可以當(dāng)作構(gòu)造函數(shù), 也就是說(shuō), 不可以使用new命令, 否則會(huì)拋出一個(gè)錯(cuò)誤
函數(shù)綁定
箭頭函數(shù)可以綁定this對(duì)象, 大大減少了顯式綁定this對(duì)象的寫(xiě)法( call、 apply、 bind) 。
函數(shù)綁定運(yùn)算符是并排的兩個(gè)雙冒號(hào)( ::) , 雙冒號(hào)左邊是一個(gè)對(duì)象, 右邊是一個(gè)函數(shù)。 該運(yùn)算符會(huì)自動(dòng)將左邊的對(duì)象, 作為上下文環(huán)境( 即this對(duì)象) , 綁定到右邊的函數(shù)上面。
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
//如果雙冒號(hào)左邊為空, 右邊是一個(gè)對(duì)象的方法, 則等于將該方法綁定在該對(duì)象上面。
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
let log = ::console.log;
// 等同于
var log = console.log.bind(console);
尾調(diào)用優(yōu)化
尾調(diào)用
某個(gè)函數(shù)的最后一步是調(diào)用另一個(gè)函數(shù)
function f(x){
return g(x);
}
//函數(shù)f的最后一步是調(diào)用函數(shù)g, 這就叫尾調(diào)用
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
//函數(shù)m和n都屬于尾調(diào)用, 因?yàn)樗鼈兌际呛瘮?shù)f的最后一步操作
Set和Map數(shù)據(jù)結(jié)構(gòu)
Set
提供了新的數(shù)據(jù)結(jié)構(gòu)Set。 它類(lèi)似于數(shù)組, 但是成員的值都是唯一的, 沒(méi)有重復(fù)的值
//Set本身是一個(gè)構(gòu)造函數(shù), 用來(lái)生成Set數(shù)據(jù)結(jié)構(gòu)
let s = new Set();
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
for (let i of s) {
console.log(i);
}
//Set函數(shù)接受數(shù)組作為參數(shù)
//Set函數(shù)可以接受一個(gè)數(shù)組( 或類(lèi)似數(shù)組的對(duì)象) 作為參數(shù), 用來(lái)初始化
var set = new Set([1, 2, 3, 4, 4]);
// 例二
var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(items.size) // 5
//接受類(lèi)似數(shù)組的對(duì)象作為參數(shù)
// 例三
function divs() {
return [...document.querySelectorAll('div')];
}
var set2 = new Set(divs());
console.log(set2.size); // 56
// 類(lèi)似于
divs().forEach(div => set.add(div));
console.log(set2.size); // 56
Set實(shí)例的屬性和方法
屬性
- Set.prototype.constructor: 構(gòu)造函數(shù), 默認(rèn)就是Set函數(shù)
- Set.prototype.size: 返回Set實(shí)例的成員總數(shù)。
方法
- 操作方法:
- add(value): 添加某個(gè)值, 返回Set結(jié)構(gòu)本身
- delete(value): 刪除某個(gè)值, 返回一個(gè)布爾值, 表示刪除是否成功
- has(value): 返回一個(gè)布爾值, 表示該值是否為Set的成員
- clear(): 清除所有成員, 沒(méi)有返回值
s.add(1).add(2).add(2);
// 注意2被加入了兩次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
- 遍歷方法
- keys(): 返回鍵名的遍歷器
- values(): 返回鍵值的遍歷器
- entries(): 返回鍵值對(duì)的遍歷器
- forEach(): 使用回調(diào)函數(shù)遍歷每個(gè)成員
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(value * 2) )
// 2
// 4
// 6
注:由于Set結(jié)構(gòu)沒(méi)有鍵名, 只有鍵值( 或者說(shuō)鍵名和鍵值是同一個(gè)值) , 所以key方法和value方法的行為完全一致。
- 應(yīng)用
//擴(kuò)展運(yùn)算符( ...) 內(nèi)部使用for...of循環(huán), 所以也可以用于Set結(jié)構(gòu)
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
//去除數(shù)組的重復(fù)成員。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
//數(shù)組的map和filter方法也可以用于Set
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set結(jié)構(gòu): {2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set結(jié)構(gòu): {2, 4}
//并集( Union) 、 交集( Intersect) 和差集( Difference)
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
Map
Map結(jié)構(gòu)提供了“值—值”的對(duì)應(yīng), 是一種更完善的Hash結(jié)構(gòu)實(shí)現(xiàn)。 如果你需要“鍵值對(duì)”的
數(shù)據(jù)結(jié)構(gòu),請(qǐng)使用Map
var m = new Map();
var o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
// Map也可以接受一個(gè)數(shù)組作為參數(shù)。 該數(shù)組的成員是一個(gè)個(gè)表示鍵值對(duì)的數(shù)組
var map = new Map([
['name', '張三'],
['title', 'Author']
]);
console.log(map); //Map { 'name' => '張三', 'title' => 'Author' }
map.size // 2
map.has('name') // true
map.get('name') // "張三"
map.has('title') // true
map.get('title') // "Author"
//如果對(duì)同一個(gè)鍵多次賦值, 后面的值將覆蓋前面的值
let map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"
屬性和操作方法
size屬性
size屬性返回Map結(jié)構(gòu)的成員總數(shù)。
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
set(key, value)
set方法設(shè)置key所對(duì)應(yīng)的鍵值, 然后返回整個(gè)Map結(jié)構(gòu)。 如果key已經(jīng)有值, 則鍵值會(huì)被更新, 否則就新生成該鍵
var m = new Map();
m.set("edition", 6) // 鍵是字符串
m.set(262, "standard") // 鍵是數(shù)值
m.set(undefined, "nah") // 鍵是undefined
get(key)
get方法讀取key對(duì)應(yīng)的鍵值, 如果找不到key, 返回undefined
var m = new Map();
var hello = function() {console.log("hello");}
m.set(hello, "Hello ES6!") // 鍵是函數(shù)
m.get(hello) // Hello ES6!
has(key)
has方法返回一個(gè)布爾值, 表示某個(gè)鍵是否在Map數(shù)據(jù)結(jié)構(gòu)中
var m = new Map();
m.set("edition", 6);
m.set(262, "standard");
m.set(undefined, "nah");
m.has("edition") // true
m.has("years") // false
m.has(262) // true
m.has(undefined) // true
delete(key)
delete方法刪除某個(gè)鍵, 返回true。 如果刪除失敗, 返回false。
var m = new Map();
m.set(undefined, "nah");
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
clear()
clear方法清除所有成員, 沒(méi)有返回值
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
遍歷
- keys(): 返回鍵名的遍歷器。
- values(): 返回鍵值的遍歷器
- entries(): 返回所有成員的遍歷器。
- forEach(): 遍歷Map的所有成員
let map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
結(jié)合數(shù)組的map方法、 filter方法, 可以實(shí)現(xiàn)Map的遍歷和過(guò)濾( Map本身沒(méi)有map和filter方法)
let map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
let map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 產(chǎn)生Map結(jié)構(gòu) {1 => 'a', 2 => 'b'}
let map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 產(chǎn)生Map結(jié)構(gòu) {2 => '_a', 4 => '_b', 6 => '_c'}
map與其他數(shù)據(jù)結(jié)構(gòu)的互相轉(zhuǎn)換:
- Map轉(zhuǎn)為數(shù)組
Map轉(zhuǎn)為數(shù)組最方便的方法, 就是使用擴(kuò)展運(yùn)算符( ...) 。
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
- 數(shù)組轉(zhuǎn)為Map
將數(shù)組轉(zhuǎn)入Map構(gòu)造函數(shù), 就可以轉(zhuǎn)為Map
new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}
- Map轉(zhuǎn)為JSON
Map轉(zhuǎn)為JSON要區(qū)分兩種情況:
- Map的鍵名都是字符串, 這時(shí)可以選擇轉(zhuǎn)為對(duì)象JSON
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
- Map的鍵名有非字符串, 這時(shí)可以選擇轉(zhuǎn)為數(shù)組JSON
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
- JSON轉(zhuǎn)為Map
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes":true,"no":false}')
// Map {'yes' => true, 'no' => false}
Generator 函數(shù)
Generator函數(shù)是ES6提供的一種異步編程解決方案, 語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同。
執(zhí)行Generator函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象, 也就是說(shuō), Generator函數(shù)除了狀態(tài)機(jī),還是一個(gè)遍歷器對(duì)象生成函數(shù)。 返回的遍歷器對(duì)象, 可以依次遍歷Generator函數(shù)內(nèi)部的每一個(gè)狀態(tài)。
形式上, Generator函數(shù)是一個(gè)普通函數(shù), 但是有兩個(gè)特征。
- function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);
- 函數(shù)體內(nèi)部使用yield語(yǔ)句, 定義不同的內(nèi)部狀態(tài)( yield語(yǔ)句在英語(yǔ)里的意思就是“產(chǎn)出”) 。
Generator函數(shù)的調(diào)用方法與普通函數(shù)一樣, 也是在函數(shù)名后面加上一對(duì)圓括號(hào)。 不同的是, 調(diào)用Generator函數(shù)后, 該函數(shù)并不執(zhí)行, 返回的也不是函數(shù)運(yùn)行結(jié)果, 而是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象, 也就是上一章介紹的遍歷器對(duì)象( Iterator Object)
必須調(diào)用遍歷器對(duì)象的next方法, 使得指針移向下一個(gè)狀態(tài)。 也就是說(shuō), 每次調(diào)用next方法, 內(nèi)部指針就從函數(shù)頭部或上一次停下來(lái)的地方開(kāi)始執(zhí)行, 直到遇到下一個(gè)yield語(yǔ)句( 或return語(yǔ)句) 為止。 換言之, Generator函數(shù)是分段執(zhí)行的,yield語(yǔ)句是暫停執(zhí)行的標(biāo)記, 而next方法可
以恢復(fù)執(zhí)行。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
yield語(yǔ)句
遍歷器對(duì)象的next方法的運(yùn)行邏輯如下
- 遇到y(tǒng)ield語(yǔ)句, 就暫停執(zhí)行后面的操作, 并將緊跟在yield后面的那個(gè)表達(dá)式的值, 作為返回的對(duì)象的value屬性值
- 下一次調(diào)用next方法時(shí), 再繼續(xù)往下執(zhí)行, 直到遇到下一個(gè)yield語(yǔ)句。
- 如果沒(méi)有再遇到新的yield語(yǔ)句, 就一直運(yùn)行到函數(shù)結(jié)束, 直到return語(yǔ)句為止, 并將return語(yǔ)句后面的表達(dá)式的值, 作為返回的對(duì)象的value屬性值。
- 如果該函數(shù)沒(méi)有return語(yǔ)句, 則返回的對(duì)象的value屬性值為undefined
注: yield語(yǔ)句不能用在普通函數(shù)中, 否則會(huì)報(bào)錯(cuò)
Promise對(duì)象
所謂Promise, 簡(jiǎn)單說(shuō)就是一個(gè)容器, 里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件( 通常是一個(gè)異步操作) 的結(jié)果。 從語(yǔ)法上說(shuō), Promise是一個(gè)對(duì)象, 從它可以獲取異步操作的消息
Promise對(duì)象有以下兩個(gè)特點(diǎn):
- 對(duì)象的狀態(tài)不受外界影響。 Promise對(duì)象代表一個(gè)異步操作, 有三種狀態(tài): Pending( 進(jìn)行中) 、 Resolved( 已完成, 又稱(chēng)Fulfilled)和Rejected( 已失敗) 。 只有異步操作的結(jié)果, 可以決定當(dāng)前是哪一種狀態(tài), 任何其他操作都無(wú)法改變這個(gè)狀態(tài)。 這也是Promise這個(gè)名字的由來(lái), 它的英語(yǔ)意思就是“承諾”, 表示其他手段無(wú)法改變
- 一旦狀態(tài)改變, 就不會(huì)再變, 任何時(shí)候都可以得到這個(gè)結(jié)果。 Promise對(duì)象的狀態(tài)改變, 只有兩種可能: 從Pending變?yōu)镽esolved和從Pending變?yōu)镽ejected。 只要這兩種情況發(fā)生, 狀態(tài)就凝固了, 不會(huì)再變了, 會(huì)一直保持這個(gè)結(jié)果。 就算改變已經(jīng)發(fā)生了, 你再對(duì)Promise對(duì)象添加回調(diào)函數(shù), 也會(huì)立即得到這個(gè)結(jié)果。 這與事件( Event) 完全不同, 事件的特點(diǎn)是, 如果你錯(cuò)過(guò)了它, 再去監(jiān)聽(tīng), 是得不到結(jié)果的。
//創(chuàng)造了一個(gè)Promise實(shí)例。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
//可以用then方法分別指定Resolved狀態(tài)和Reject狀態(tài)的回調(diào)函數(shù)
promise.then(function(value) {
// success
}, function(error) {
// failure
});
//用Promise對(duì)象實(shí)現(xiàn)的Ajax操作的例子
var getJSON = function (url) {
var promise = new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if(this.status === 200)
{
resolve(this.response);
}
else
{
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function (json) {
console.log('Contents: ' + json);
}, function (error) {
console.error('出錯(cuò)了', error);
});
async函數(shù)
async函數(shù)就是Generator函數(shù)的語(yǔ)法糖。
async函數(shù)就是將Generator函數(shù)的星號(hào)( *) 替換成async, 將yield替換成await, 僅此而已
//async函數(shù)返回一個(gè)Promise對(duì)象, 可以使用then方法添加回調(diào)函數(shù)。 當(dāng)函數(shù)執(zhí)行的時(shí)候, 一旦遇到await就會(huì)先返回, 等到觸發(fā)的異步操作完成, 再接著執(zhí)行函數(shù)體內(nèi)后面的語(yǔ)句。
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
//指定多少毫秒后輸出一個(gè)值
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value)
}
asyncPrint('hello world', 50);
//Async函數(shù)有多種使用形式。
// 函數(shù)聲明
async function foo() {}
// 函數(shù)表達(dá)式
const foo = async function () {};
// 對(duì)象的方法
let obj = { async foo() {} };
// 箭頭函數(shù)
const foo = async () => {};
Class
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `x=${this.x} y=${this.y}`;
}
}
let point = new Point(1, 2);
console.log(point.toString()); //x=1 y=2
注:定義“類(lèi)”的方法的時(shí)候, 前面不需要加上function這個(gè)關(guān)鍵字, 直接把函數(shù)定義放進(jìn)去了就可以了。 另外, 方法之間不需要逗號(hào)分隔, 加了會(huì)報(bào)錯(cuò)。
constructor方法
constructor方法是類(lèi)的默認(rèn)方法, 通過(guò)new命令生成對(duì)象實(shí)例時(shí), 自動(dòng)調(diào)用該方法。 一個(gè)類(lèi)必須有constructor方法, 如果沒(méi)有顯式定義, 一個(gè)空的constructor方法會(huì)被默認(rèn)添加
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
Class的繼承
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調(diào)用父類(lèi)的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 調(diào)用父類(lèi)的toString()
}
}
注:
- 子類(lèi)必須在constructor方法中調(diào)用super方法, 否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。 這是因?yàn)樽宇?lèi)沒(méi)有自己的this對(duì)象, 而是繼承父類(lèi)的this對(duì)象, 然后對(duì)其進(jìn)行加工
- 在子類(lèi)的構(gòu)造函數(shù)中, 只有調(diào)用super之后, 才可以使用this關(guān)鍵字, 否則會(huì)報(bào)錯(cuò)
Module
模塊功能主要由兩個(gè)命令構(gòu)成: export和import。 export命令用于規(guī)定模塊的對(duì)外接口, import命令用于輸入其他模塊提供的功能。
一個(gè)模塊就是一個(gè)獨(dú)立的文件。 該文件內(nèi)部的所有變量, 外部無(wú)法獲取。 如果你希望外部能夠讀取模塊內(nèi)部的某個(gè)變量, 就必須使用export關(guān)鍵字輸出該變量
export
//用export命令對(duì)外部輸出了三個(gè)變量
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//另外一種寫(xiě)法
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
//export命令除了輸出變量, 還可以輸出函數(shù)或類(lèi)( class) 。
export function multiply(x, y) {
return x * y;
};
//export輸出的變量就是本來(lái)的名字, 但是可以使用as關(guān)鍵字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
//export命令規(guī)定的是對(duì)外的接口, 必須與模塊內(nèi)部的變量建立一一對(duì)應(yīng)關(guān)系
// 寫(xiě)法一
export var m = 1;
// 寫(xiě)法二
var m = 1;
export {m};
// 寫(xiě)法三
var n = 1;
export {n as m};
import
使用export命令定義了模塊的對(duì)外接口以后, 其他JS文件就可以通過(guò)import命令加載這個(gè)模塊( 文件)
import命令接受一個(gè)對(duì)象( 用大括號(hào)表示) , 里面指定要從其他模塊導(dǎo)入的變量名。 大括號(hào)里面的變量名, 必須與被導(dǎo)入模塊( profile.js) 對(duì)外接口的名稱(chēng)相同。
//從profile中導(dǎo)入firstName, lastName, year
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
//import命令要使用as關(guān)鍵字, 將輸入的變量重命名
import { lastName as surname } from './profile';
模塊的整體加載
除了指定加載某個(gè)輸出值, 還可以使用整體加載, 即用星號(hào)( *) 指定一個(gè)對(duì)象, 所有輸出值都加載在這個(gè)對(duì)象上面
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
//整體加載
import * as circle from './circle';
console.log('圓面積: ' + circle.area(4));
console.log('圓周長(zhǎng): ' + circle.circumference(14));
export default命令
為模塊指定默認(rèn)輸出
// export-default.js
//默認(rèn)輸出是一個(gè)函數(shù)。
export default function () {
console.log('foo');
}
// import-default.js
//import命令可以為該匿名函數(shù)指定任意名字。
import customName from './export-default';
customName(); // 'foo'
注:一個(gè)模塊只能有一個(gè)默認(rèn)輸出, 因此export deault命令只能使用一次。 所以, import命令后面才不用加大括號(hào), 因?yàn)橹豢赡軐?duì)應(yīng)一個(gè)方法。
跨模塊常量
const聲明的常量只在當(dāng)前代碼塊有效。 如果想設(shè)置跨模塊的常量( 即跨多個(gè)文件) , 可以采用下面的寫(xiě)法
// constants.js 模塊
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模塊
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模塊
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3