ES6常用語法(下)
阿新 • • 發佈:2018-12-05
- Symbol型別
Symbol
的原因
ES6 引入了一種新的原始資料型別
Symbol
,表示獨一無二的值。它是 JavaScript 語言的第七種資料型別,前六種是:
undefined
null
、布林值(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)" 上面程式碼中,
s1
s2
是兩個 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 上面程式碼中,
s1
和
s2
都是
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結構
[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
例項的成員總數。
// 注意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 的所有成員。
['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生成器函式
function
關鍵字與函式名之間有一個星號;二是,函式體內部使用
yield
表示式,定義不同的內部狀態(
yield
在英語裡的意思就是“產出”)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';}
var hw = helloWorldGenerator(); 上面程式碼定義了一個 Generator 函式
helloWorldGenerator
,它內部有兩個
yield
表示式(
hello
和
world
),即該函式有三個狀態:hello,world 和 return 語句(結束執行)。
然後,Generator 函式的呼叫方法與普通函式一樣,也是在函式名後面加上一對圓括號。不同的是,呼叫 Generator 函式後,該函式並不執行,返回的也不是函式執行結果,而是一個指向內部狀態的指標物件
yield 表示式
由於 Generator 函式返回的遍歷器物件,只有呼叫
next
方法才會遍歷下一個內部狀態,所以其實提供了一種可以暫停執行的函式。
yield
表示式就是暫停標誌。
(1)遇到yield
表示式,就暫停執行後面的操作,並將緊跟在yield
後面的那個表示式的值,作為返回的物件的value
屬性值。
(2)下一次呼叫next
方法時,再繼續往下執行,直到遇到下一個yield
表示式。
(3)如果沒有再遇到新的yield
表示式,就一直執行到函式結束,直到return
語句為止,並將return
語句後面的表示式的值,作為返回的物件的value
屬性值。
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
表示式的值hello
,done
屬性的值false
,表示遍歷還沒有結束。
第二次呼叫,Generator 函式從上次yield
表示式停下的地方,一直執行到下一個yield
表示式。next
方法返回的物件的value
屬性就是當前yield
表示式的值world
,done
屬性的值false
,表示遍歷還沒有結束。
第三次呼叫,Generator 函式從上次yield
表示式停下的地方,一直執行到return
語句(如果沒有return
語句,就執行到函式結束)。next
方法返回的物件的value
屬性,就是緊跟在return
語句後面的表示式的值(如果沒有return
語句,則value
屬性的值為undefined
),done
屬性的值true
,表示遍歷已經結束。
next
方法返回物件的
value
屬性為
undefined
,
done
屬性為
true
。以後再呼叫
next
方法,返回的都是這個值。
總結一下,呼叫 Generator 函式,返回一個遍歷器物件,代表 Generator 函式的內部指標。以後,每次呼叫遍歷器物件的
next
方法,就會返回一個有著
value
和
done
兩個屬性的物件。
value
屬性表示當前的內部狀態的值,是
yield
表示式後面那個表示式的值;
done
屬性是一個布林值,表示是否遍歷結束。