ES6常用語法(下)

等一輪新月發表於2018-12-05
  • Symbol型別
     ES5 的物件屬性名都是字串,這容易造成屬性名的衝突。比如,你使用了一個他人提供的物件,但又想為這個物件新增新的方法,新方法的名字就有可能與現有方法產生衝突。如果有一種機制,保證每個屬性的名字都是獨一無二的就好了,這樣就從根本上防止屬性名的衝突。這就是 ES6 引入Symbol的原因
 
     ES6 引入了一種新的原始資料型別Symbol,表示獨一無二的值。它是 JavaScript 語言的第七種資料型別,前六種是:undefinednull、布林值(Boolean)、字串(String)、數值(Number)、物件(Object)
 
     Symbol 值通過Symbol函式生成。這就是說,物件的屬性名現在可以有兩種型別,一種是原來就有的字串,另一種就是新增的 Symbol 型別。凡是屬性名屬於 Symbol 型別,就都是獨一無二的,可以保證不會與其他屬性名產生衝突
 
let s = Symbol();

typeof s
// “symbol”

上面程式碼中,變數s就是一個獨一無二的值。typeof運算子的結果,表明變數s是 Symbol 資料型別,而不是字串之類的其他型別
注意,Symbol函式前不能使用new命令,否則會報錯。這是因為生成的 Symbol 是一個原始型別的值,不是物件。也就是說,由於 Symbol 值不是物件,所以不能新增屬性。基本上,它是一種類似於字串的資料型別。
Symbol函式可以接受一個字串作為引數,表示對 Symbol 例項的描述,主要是為了在控制檯顯示,或者轉為字串時,比較容易區分。
 
let s1 = Symbol(`foo`);let s2 = Symbol(`bar`);

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // “Symbol(foo)”
s2.toString() // “Symbol(bar)”

上面程式碼中,s1s2是兩個 Symbol 值。如果不加引數,它們在控制檯的輸出都是Symbol(),不利於區分。有了引數以後,就等於為它們加上了描述,輸出的時候就能夠分清,到底是哪一個值。
 
注意,Symbol函式的引數只是表示對當前 Symbol 值的描述,因此相同引數的Symbol函式的返回值是不相等的。
 
 
// 沒有引數的情況
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false

// 有引數的情況

let s1 = Symbol(`foo`);
let s2 = Symbol(`foo`);
s1 === s2 // false
上面程式碼中,s1s2都是Symbol函式的返回值,而且引數相同,但是它們是不相等的。
Symbol 值也可以轉為布林值,但是不能轉為數值
 
 
let sym = Symbol();
Boolean(sym) // true
!sym // false
if (sym) {
// …
}

Number(sym) // TypeError
sym + 2 // TypeError

 
作為屬性名的 Symbol
由於每一個 Symbol 值都是不相等的,這意味著 Symbol 值可以作為識別符號,用於物件的屬性名,就能保證不會出現同名的屬性,能防止某一個鍵被不小心改寫或覆蓋。
 
// 第一種寫法
let a = {};
a[mySymbol] = `Hello!`;
// 第二種寫法
let a = {
  [mySymbol]: `Hello!`};
// 以上寫法都得到同樣結果
a[mySymbol] // “Hello!”
注意,Symbol 值作為物件屬性名時,不能用點運算子。
 
const mySymbol = Symbol();
const a = {};

a.mySymbol = `Hello!`;
a[mySymbol] // undefined
a[`mySymbol`] // “Hello!”

上面程式碼中,因為點運算子後面總是字串,所以不會讀取mySymbol作為標識名所指代的那個值,導致a的屬性名實際上是一個字串,而不是一個 Symbol 值。
同理,在物件的內部,使用 Symbol 值定義屬性時,Symbol 值必須放在方括號之中。
 
 
let s = Symbol();

let obj = {
  [s]: function (arg) { … }};

obj[s](123);

上面程式碼中,如果s不放在方括號中,該屬性的鍵名就是字串s,而不是s所代表的那個 Symbol 值。
 
 

 
 
  • Set和Map結構
ES6 提供了新的資料結構 Set。它類似於陣列,但是成員的值都是唯一的,沒有重複的值。
Set 本身是一個建構函式,用來生成 Set 資料結構。
 
const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);}
// 2 3 5 4

//s=[2,3,5,4] 
//陣列去重的方法(1)
 
上面程式碼通過add方法向 Set 結構加入成員,結果表明 Set 結構不會新增重複的值。
 
Set 函式可以接受一個陣列(獲取dom的nodelist物件)作為引數,用來初始化。
 
// 例一
const set = new Set([1, 2, 3, 4, 4]);
          […set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 例三

const set = new Set(document.querySelectorAll(`div`));
set.size // 56
上面程式碼也展示了一種去除陣列重複成員的方法。
 
// 去除陣列的重複成員(2)
[…new Set(array)]
向 Set 加入值的時候,不會發生型別轉換,所以5"5"是兩個不同的值。Set 內部判斷兩個值是否不同,使用的演算法叫做“Same-value-zero equality”,它類似於精確相等運算子(===),主要的區別是NaN等於自身,而精確相等運算子認為NaN不等於自身。
 
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
 
Set 結構的例項有以下屬性
  • constructor:建構函式,預設就是Set函式。
  • size:返回Set例項的成員總數。
Set 例項的方法分為兩大類:操作方法(用於運算元據)和遍歷方法(用於遍歷成員)。下面四個操作方法
 
– add(value):新增某個值,返回 Set 結構本身。
– delete(value):刪除某個值,返回一個布林值,表示刪除是否成功。
– has(value):返回一個布林值,表示該值是否為Set的成員。
– clear():清除所有成員,沒有返回值。
 
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

 
Array.from方法可以將 Set 結構轉為陣列。
 
 
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
 
Set 結構的例項預設可遍歷
 
let set = new Set([`red`, `green`, `blue`]);

for (let x of set) {
  console.log(x);}
// red
// green
// blue

 
Set 結構的例項與陣列一樣,也擁有forEach方法,用於對每個成員執行某種操作,沒有返回值。
 
 
set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ` : ` + value))
// 1 : 1
// 4 : 4
// 9 : 9
上面程式碼說明,forEach方法的引數就是一個處理函式。該函式的引數與陣列的forEach一致,依次為鍵值、鍵名、集合本身(上例省略了該引數)。這裡需要注意,Set 結構的鍵名就是鍵值(兩者是同一個值),因此第一個引數與第二個引數的值永遠都是一樣的。
 
擴充套件運算子和 Set 結構相結合,就可以去除陣列的重複成員。
 
let arr = [3, 5, 2, 2, 5, 5];
let unique = […new Set(arr)];
// [3, 5, 2]
 
map結構
JavaScript 的物件(Object),本質上是鍵值對的集合(Hash 結構),但是傳統上只能用字串當作鍵。這給它的使用帶來了很大的限制。
 
ES6 提供了 Map 資料結構。它類似於物件,也是鍵值對的集合,但是“鍵”的範圍不限於字串,各種型別的值(包括物件)都可以當作鍵。也就是說,Object 結構提供了“字串—值”的對應,Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。如果你需要“鍵值對”的資料結構,Map 比 Object 更合適。
 
 

Map 結構的例項有以下屬性和操作方法。

(1)size 屬性

 
size屬性返回 Map 結構的成員總數。
 
const map = new Map();
map.set(`foo`, true);
map.set(`bar`, false);

map.size // 2

 

(2)set(key, value)

 
set方法設定鍵名key對應的鍵值為value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。
 
const m = new Map();

m.set(`edition`, 6)      // 鍵是字串
m.set(262, `standard`)    // 鍵是數值
m.set(undefined, `nah`)  // 鍵是 undefined

 
 
set方法返回的是當前的Map物件,因此可以採用鏈式寫法。
 
let map = new Map()
  .set(1, `a`)
  .set(2, `b`)
  .set(3, `c`);
 
 
(3)get(key)
 
get方法讀取key對應的鍵值,如果找不到key,返回undefined
 
const m = new Map();

const hello = function() {console.log(`hello`);};
m.set(hello, `Hello ES6!`) // 鍵是函式

m.get(hello) // Hello ES6!

 
 
(4)has(key)
 
has方法返回一個布林值,表示某個鍵是否在當前 Map 物件之中。
 
 
const 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

 
 
 
(5)delete(key)
 
delete方法刪除某個鍵,返回true。如果刪除失敗,返回false
 
const m = new Map();
m.set(undefined, `nah`);
m.has(undefined)    // true

m.delete(undefined)
m.has(undefined)      // false

 
(6)clear()
 
clear方法清除所有成員,沒有返回值。
 
let map = new Map();
map.set(`foo`, true);
map.set(`bar`, false);
map.size // 2
map.clear()
map.size // 0
 

Map 結構原生提供三個遍歷器生成函式和一個遍歷方法。

  • keys():返回鍵名的遍歷器。
  • values():返回鍵值的遍歷器。
  • entries():返回所有成員的遍歷器。
  • forEach():遍歷 Map 的所有成員。
 
需要特別注意的是,Map 的遍歷順序就是插入順序
 
 
const 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);}
// “F” “no”
// “T” “yes”

// 等同於使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);}
// “F” “no”
// “T” “yes”

Map 結構轉為陣列結構,比較快速的方法是使用擴充套件運算子(...)。
 
const map = new Map([
  [1, `one`],
  [2, `two`],
  [3, `three`],]);

[…map.keys()]
// [1, 2, 3]
[…map.values()]
// [`one`, `two`, `three`]
[…map.entries()]
// [[1,`one`], [2, `two`], [3, `three`]]
[…map]
// [[1,`one`], [2, `two`], [3, `three`]]

 

 
 
  • Generators生成器函式
     Generator 函式是 ES6 提供的一種非同步程式設計解決方案,語法行為與傳統函式完全不同
     Generator 函式有多種理解角度。語法上,首先可以把它理解成,Generator 函式是一個狀態機,封裝了多個內部狀態。
     執行 Generator 函式會返回一個遍歷器物件,也就是說,Generator 函式除了狀態機,還是一個遍歷器物件生成函式。返回的遍歷器物件,可以依次遍歷 Generator 函式內部的每一個狀態。
     形式上,Generator 函式是一個普通函式,但是有兩個特徵。一是,function關鍵字與函式名之間有一個星號;二是,函式體內部使用yield表示式,定義不同的內部狀態(yield在英語裡的意思就是“產出”)
 
function* helloWorldGenerator() {
  yield `hello`;
  yield `world`;
  return `ending`;}

var hw = helloWorldGenerator();

上面程式碼定義了一個 Generator 函式helloWorldGenerator,它內部有兩個yield表示式(helloworld),即該函式有三個狀態:hello,world 和 return 語句(結束執行)。
 
     然後,Generator 函式的呼叫方法與普通函式一樣,也是在函式名後面加上一對圓括號。不同的是,呼叫 Generator 函式後,該函式並不執行,返回的也不是函式執行結果,而是一個指向內部狀態的指標物件
 
yield 表示式
 
由於 Generator 函式返回的遍歷器物件,只有呼叫next方法才會遍歷下一個內部狀態,所以其實提供了一種可以暫停執行的函式。yield表示式就是暫停標誌。

(1)遇到yield表示式,就暫停執行後面的操作,並將緊跟在yield後面的那個表示式的值,作為返回的物件的value屬性值。

(2)下一次呼叫next方法時,再繼續往下執行,直到遇到下一個yield表示式。

(3)如果沒有再遇到新的yield表示式,就一直執行到函式結束,直到return語句為止,並將return語句後面的表示式的值,作為返回的物件的value屬性值。

 
(4)如果該函式沒有return語句,則返回的物件的value屬性值為undefined
 
     下一步,必須呼叫遍歷器物件的next方法,使得指標移向下一個狀態。也就是說,每次呼叫next方法,內部指標就從函式頭部或上一次停下來的地方開始執行,直到遇到下一個yield表示式(或return語句)為止。換言之,Generator 函式是分段執行的,yield表示式是暫停執行的標記,而next方法可以恢復執行。
 
hw.next()
// { value: `hello`, done: false }

hw.next()
// { value: `world`, done: false }

hw.next()
// { value: `ending`, done: true }

hw.next()
// { value: undefined, done: true }

上面程式碼一共呼叫了四次next方法。

第一次呼叫,Generator 函式開始執行,直到遇到第一個yield表示式為止。next方法返回一個物件,它的value屬性就是當前yield表示式的值hellodone屬性的值false,表示遍歷還沒有結束。

第二次呼叫,Generator 函式從上次yield表示式停下的地方,一直執行到下一個yield表示式。next方法返回的物件的value屬性就是當前yield表示式的值worlddone屬性的值false,表示遍歷還沒有結束。

第三次呼叫,Generator 函式從上次yield表示式停下的地方,一直執行到return語句(如果沒有return語句,就執行到函式結束)。next方法返回的物件的value屬性,就是緊跟在return語句後面的表示式的值(如果沒有return語句,則value屬性的值為undefined),done屬性的值true,表示遍歷已經結束。

 
第四次呼叫,此時 Generator 函式已經執行完畢,next方法返回物件的value屬性為undefineddone屬性為true。以後再呼叫next方法,返回的都是這個值。
 
總結一下,呼叫 Generator 函式,返回一個遍歷器物件,代表 Generator 函式的內部指標。以後,每次呼叫遍歷器物件的next方法,就會返回一個有著valuedone兩個屬性的物件。value屬性表示當前的內部狀態的值,是yield表示式後面那個表示式的值;done屬性是一個布林值,表示是否遍歷結束。