ES6常用特性入門整理,面試必備,持更......

下面我將會慢慢的介紹一些常用的ES6語法特性,這其中有很大一部分引用於阮一峯老師的ECMAScript 6 入門,算是自己對這本書的一個筆記吧。

目錄

async/await特點

Promise

前端模塊化

Set數據結構

Map數據結構

WeakSet 和 WeakMap 數據結構

let 和 const 命令

變量的解構賦值

箭頭函數


async/await特點

  1. async/await,async 是“異步”的簡寫,async function 用於申明一個 function 是異步的; await,可以認爲是async wait的簡寫, 用於等待一個異步方法執行完成;async函數的返回值是 Promise 對象。

  2. 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構造函數的參數。也就是說,SetMap都可以用來生成新的 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有兩個區別。

  1. 成員只能是對象,而不能是其他類型的值。
  2. WeakSet/WeakMap 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet/WeakMap 對該對象的引用,所以WeakSet/WeakMap不需要考慮垃圾回收問題。因爲是弱引用的原因,所以沒有size屬性,也沒辦法遍歷它。

 

let 和 const 命令

ES5 只有兩種聲明變量的方法:var命令和function命令。ES6 除了添加letconst命令,另外兩種聲明變量的方法:import命令和class命令。所以,ES6 一共有 6 種聲明變量的方法。

let 的用法類似於var,但是所聲明的變量,只在let命令所在的代碼塊內有效。

{
  let a = 10;
  var b = 1;
}
a // 報錯
b // 1

因爲let命令所在的代碼塊內纔會有效,所以像for循環的計數器,就很合適使用let

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 明確規定,如果區塊中存在letconst命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。

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

上面這個還是很容易理解,數組參數被解構成變量xy

[[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的參數是一個對象,通過對這個對象進行解構,得到變量xy的值。如果解構失敗,xy等於默認值。

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的參數指定默認值,而不是爲變量xy指定默認值,所以會得到與前一種寫法不同的結果。

用途:

1.交換變量的值

下面代碼交換變量ab的值,這樣的寫法不僅簡潔易讀。

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);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章