下面我將會慢慢的介紹一些常用的ES6語法特性,這其中有很大一部分引用於阮一峯老師的《ECMAScript 6 入門》,算是自己對這本書的一個筆記吧。
目錄
async/await特點
-
async/await,async 是“異步”的簡寫,async function 用於申明一個 function 是異步的; await,可以認爲是async wait的簡寫, 用於等待一個異步方法執行完成;
async
函數的返回值是 Promise 對象。 -
async
函數完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await
命令就是內部then
命令的語法糖。
Promise
Promise
對象代表一個異步操作,有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise
對象的狀態改變,只有兩種可能:從pending
變爲fulfilled
和從pending
變爲rejected
。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。
前端模塊化
服務器端:CommonJS(同步加載模塊),node.js用的就是CommonJS規範。客戶端:AMD、CMD(異步加載模塊)。 AMD規範的實現主要有RequireJS, CMD規範的主要實現有SeaJS。
AMD、CMD區別:
CMD 推崇依賴就近; AMD 推崇依賴前置
CMD 是延遲執行; AMD 是提前執行
CMD性能好,因爲只有用戶需要的時候才執行; AMD用戶體驗好,因爲沒有延遲,依賴模塊提前執行了
ES6 Module:
- ES6 在語言標準的層面上,實現了模塊功能,而且實現得相當簡單,完全可以取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
-
ES6 模塊設計思想:儘量的靜態化、使得編譯時就能確定模塊的依賴關係,以及輸入和輸出的變量(CommonJS和AMD模塊,都只能在運行時確定這些東西)。
// 導入
import "/app";
import React from “react”;
import { Component } from “react”;
// 導出
export function multiply() {...};
export var year = 2018;
export default ...
...
Set數據結構
Set類似於數組,但是成員的值都是唯一的,沒有重複的值。Set
本身是一個構造函數,用來生成 Set 數據結構,可用於數組去重。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));// 2 3 5 4
const s2 = new Set([1, 2, 3, 4, 5, '5', 5, 5]);
s2.size // 6
[...s2]// [1, 2, 3, 4, 5, '5']
關於值的判斷:
向 Set 加入值時,不會發生類型轉換,所以5
和"5"
是兩個不同的值(例如上面的實例),對象總是不相等的,即便都是空對象,但是NaN
是相等的。
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set.size // 3
實例:
- .add() 添加成員,返回Set結構本身
- .size 返回
Set
實例的成員總數 - .delete() 刪除某值,返回Set結構本身
- .has() 返回一個布爾值,表示該值是否爲
Set
的成員 .clear()
清除所有成員,沒有返回值.keys()
返回鍵名的遍歷器.values()
返回鍵值的遍歷器.entries()
返回鍵值對的遍歷器.forEach()
使用回調函數遍歷每個成員,沒有返回值
Set
的遍歷順序就是插入順序,這個特性有時非常有用,比如使用 Set 保存一個回調函數列表,調用時就能保證按照添加順序調用。Set 結構的實例默認可遍歷,它的默認遍歷器生成函數就是它的values
方法,所以可以省略 values 方法直接遍歷Set實例。
Map數據結構
在JavaScript中對象(Object)本質是鍵值對的集合,但只能用字符串當作鍵。而Map 數據結構的出現就是爲了解決這個限制,它類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。
Map可以通過set方法添加成員,或者接受成員是一個個表示鍵值對的數組,分別如下:
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
const map = new Map([
['name', '張三'],
['title', 'Author']
]);
不僅僅是數組,任何具有 Iterator 接口、且每個成員都是一個雙元素的數組的數據結構都可以當作Map
構造函數的參數。也就是說,Set
和Map
都可以用來生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
注意,只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵,對同樣的值的兩個實例,在 Map 結構中被視爲兩個鍵。
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map.set(k1, 111).set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
實例:
- .set(key, value) 設置
key
對應的value
,返回 Map 結構。如果key
已經有值,則鍵值會被更新。 - .get(key) 讀取
key
對應的鍵值,如果找不到key
,返回undefined
。 - .has(key) 方法返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。
.delete()
方法刪除某個鍵,返回true
,如果刪除失敗,返回false
。.clear()
方法清除所有成員,沒有返回值。.keys():
返回鍵名的遍歷器。.values()
:返回鍵值的遍歷器。.entries()
:返回所有成員的遍歷器。.forEach()
:遍歷 Map 的所有成員。
WeakSet 和 WeakMap 數據結構
WeakSet/WeakMap 結構與 Set/Map 類似,也是不重複的值的集合。但是,它與 Set /Map有兩個區別。
- 成員只能是對象,而不能是其他類型的值。
- WeakSet/WeakMap 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet/WeakMap 對該對象的引用,所以WeakSet/WeakMap不需要考慮垃圾回收問題。因爲是弱引用的原因,所以沒有size屬性,也沒辦法遍歷它。
let 和 const 命令
ES5 只有兩種聲明變量的方法:var
命令和function
命令。ES6 除了添加let
和const
命令,另外兩種聲明變量的方法:import
命令和class
命令。所以,ES6 一共有 6 種聲明變量的方法。
let 的用法類似於var
,但是所聲明的變量,只在let
命令所在的代碼塊內有效。
{
let a = 10;
var b = 1;
}
a // 報錯
b // 1
因爲
循環的計數器,就很合適使用let
命令所在的代碼塊內纔會有效,所以像forlet
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
// ReferenceError: i is not defined
暫時性死區:在代碼塊內,使用
let
命令聲明變量之前,該變量都是不可用的。這在語法上,稱爲“暫時性死區”(temporal dead zone,簡稱 TDZ)。
var a = 123;
{
a = 'abc'; // ReferenceError
let a;
}
ES6 明確規定,如果區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。
const 顧名思義就是常量的意思, 一旦聲明,常量的值就不能改變。
const a = 123;
a // 123
a = 321;//報錯
const
聲明的變量不得改變值,這意味着const
聲明的同時必須初始化,不能留到以後賦值, 不然也是會報錯的。
const foo;
// SyntaxError: Missing initializer in const declaration
let 和 const 相同的地方
1.只在聲明所在的塊級作用域內有效
2.不可重複聲明
3.同樣存在暫時性死區,只能在聲明的位置後面使用。
變量的解構賦值
ES6 允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。以前爲變量賦值,只能直接指定值, 但ES6 有了更方便的寫法。
數組結構賦值:
//before
let a = 1;
let b = 2;
let c = 3;
//after
let [a, b, c] = [1, 2, 3];
這種寫法屬於“模式匹配”,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值,如果解構不成功,變量的值就等於undefined
。
let [a, [[b], c]] = [1, [[2], 3]];
a// 1
b// 2
c// 3
let [ , , c] = ["A", "B", "C"];
c// "C"
let [a, , c] = [1, 2, 3];
a // 1
c // 3
let [a, ...b] = [1, 2, 3, 4];
a// 1
b// [2, 3, 4]
let [a, b, ...z] = ['A'];
a // "a"
b // undefined
z // []
上面用到的...b和...z是rest參數,ES6新增語法,後面將會提到
另一種情況是不完全解構,即等號左邊的模式,只匹配一部分的等號右邊的數組。這種情況下,解構依然可以成功。
let [a, b] = [1, 2, 3];
a// 1
b// 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
所以使用解構賦值的時候,=左邊所包含的變量,= 右邊一定要有
如果等號的右邊不是可遍歷的結構,那麼將會報錯。
// 報錯
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的語句都會報錯,因爲等號右邊的值,要麼轉爲對象以後不具備 Iterator 接口(前五個表達式),要麼本身就不具備 Iterator 接口(最後一個表達式)。
事實上,只要某種數據結構具有 Iterator 接口,都可以採用數組形式的解構賦值。
// 這是一個 Generator函數,後面也會寫到
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
first // 0
sixth // 5
對象結構賦值:
let { a, b} = { a: "aaa", b: "bbb" };
a// "aaa"
b// "bbb"
不同的是,數組的元素是按次序排列的,變量的取值由它的位置決定,而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。
let { c } = { a: "aaa", b: "bbb" };
c// undefined
如果變量名與屬性名不一致,必須寫成下面這樣。
let { a: c} = { a: 'aaa', b: 'bbb' };
c// "aaa"
a// ReferenceError: a is not defined
對象的解構賦值的內部機制,是先找到同名屬性,然後再賦給對應的變量。真正被賦值的是後者,而不是前者。上面代碼中,a
是匹配的模式,c
纔是變量。真正被賦值的是變量c
,而不是模式a
。
解構也可以用於嵌套結構的對象,這和數組是一樣的
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
如果想給p也賦值,可以寫成下面這樣。
let { p, p: [x, { y }] } = obj;
p // ["Hello", {y: "World"}]
默認值,不管是對象還是數組,解構賦值都允許指定默認值。
let [a = 1] = []; // 數組
a // 1
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
var {x, y = 5} = {x: 1}; // 對象
x // 1
y // 5
注意,ES6 內部使用嚴格相等運算符(===
),判斷一個位置是否有值。所以,只有當一個數組成員嚴格等於undefined
,默認值纔會生效。
let [x = 1] = [null];
x // null
如果默認值是一個表達式,那麼這個表達式是惰性求值的,即只有在用到的時候,纔會求值。
function fun() {
console.log('aaa');
}
let [x = fun()] = [1];//因爲x能取到值,所以fun並不會執行
因爲x能取到值,所以fun並不會執行
默認值可以引用解構賦值的其他變量,但該變量必須已經聲明。
let [a = 1, b = a] = [1, 2]; // a=1; b=2
let [a = b, b = 1] = []; // ReferenceError: b is not defined
對象的解構默認值基本和數組的一樣,但需要注意如果要將一個已經聲明的變量用於解構賦值,必須小心。
// 錯誤的寫法
let x;
{x} = {x: 1};// SyntaxError: syntax error
// 正確的寫法
let x;
({x} = {x: 1});
因爲 JavaScript 引擎會將{x}
理解成一個代碼塊,從而發生語法錯誤。只有不將大括號寫在行首,避免 JavaScript 將其解釋爲代碼塊,才能解決這個問題。
對象的解構賦值,可以很方便地將現有對象的方法,賦值到某個變量。
let { log, sin, cos } = Math;
由於數組本質是特殊的對象,因此可以對數組進行對象屬性的解構。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
字符串結構賦值,字符串也可以解構賦值,這是因爲此時,字符串被轉換成了一個類似數組的對象。
let [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
這裏有個小技巧,類似數組的對象都有一個length
屬性,因此還可以對這個屬性解構賦值。
let {length : len} = 'hello';
len // 5
函數的參數的解構賦值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
上面這個還是很容易理解,數組參數被解構成變量x
和y
[[1, 2], [3, 4]].map(([a, b]) => a + b);
//等同於
[[1, 2], [3, 4]].map(function ([a, b]){
return a + b;
});
這裏用到的 => 箭頭函數,後面也會寫到
函數參數的解構也可以使用默認值。
function fun({x = 0, y = 0} = {}) {
return [x, y];
}
fun({x: 3, y: 8}); // [3, 8]
fun({x: 3}); // [3, 0]
fun({}); // [0, 0]
fun(); // [0, 0]
上面代碼中,函數fun
的參數是一個對象,通過對這個對象進行解構,得到變量x
和y
的值。如果解構失敗,x
和y
等於默認值。
function fun({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
fun({x: 3, y: 8}); // [3, 8]
fun({x: 3}); // [3, undefined]
fun({}); // [undefined, undefined]
fun(); // [0, 0]
上面代碼是爲函數move
的參數指定默認值,而不是爲變量x
和y
指定默認值,所以會得到與前一種寫法不同的結果。
用途:
1.交換變量的值
下面代碼交換變量a
和b
的值,這樣的寫法不僅簡潔易讀。
let a = 1;
let b = 2;
[a, b] = [a, b];
2.從函數返回多個值
因爲函數只能返回一個值,在以前如果要返回多個值,只能將它們放在數組或對象裏返回,然後遍歷賦值。有了解構賦值,取出這些值就非常方便。
// 數組
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 對象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
3.函數參數的定義
解構賦值可以方便地將一組參數與變量名對應起來,這樣我們給函數傳值的時候就可以直接傳數組或對象。
let arr = [1, 2, 3];
function f([x, y, z]) { ... }
f(arr);
let obj = {z: 3, y: 2, x: 1};
function f({x, y, z}) { ... }
f(obj);
4.提取 JSON 數據
提取 JSON 對象中的數據,使用解構賦值會更簡潔,省時省力
let jsonData = {
id: 1001,
status: "yes",
data: ['Joe', 23]
};
let { id, status, data} = jsonData;
5.遍歷 Map 結構
任何部署了 Iterator 接口的對象,都可以用for...of
循環遍歷。Map 結構原生支持 Iterator 接口,配合變量的解構賦值,獲取鍵名和鍵值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
6.輸入模塊的指定方法
加載模塊時,往往需要指定輸入哪些方法。解構賦值使得輸入語句非常清晰。
const { fun1, fun2 } = require("funs");
箭頭函數
通過=>函數可以更簡潔快速的定義一個函數
var fun = a => a;
// 等同於
var fun = function (a) {
return a;
};
如果箭頭函數不需要傳參或需要多個傳參,就需要使用()括起來。
var fun = () => 1;
// 等同於
var fun = function () { return 1 };
var sum = (n1, n2) => n1 + n2;
// 等同於
var sum = function(n1, n2) {
return n1 + n2;
};
如果箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,並使用return
語句返回。
var sum = (n1, n2) => { n1 += n2; return n1; }
但是由於大括號被解釋爲代碼塊,所以如果箭頭函數直接返回一個對象,必須在對象外面加上括號,否則會報錯。
let fun = id => { name: "ABC" };
// 報錯
let fun = id => ({ name: "ABC" });
// 不報錯
如果箭頭函數只有一行語句,且不需要返回值,可以採用下面的寫法,就不用寫大括號了。
var fun = () => console.log(123);