es6提供了新的資料結構set。它類似于數組,但是成員的值都是唯一的,沒有重複的值。
set本身是一個構造函數,用來生成set資料結構。
上面代碼通過<code>add</code>方法向set結構加入成員,結果表明set結構不會添加重複的值。
set函數可以接受一個數組(或類似數組的對象)作為參數,用來初始化。
上面代碼中,例一和例二都是<code>set</code>函數接受數組作為參數,例三是接受類似數組的對象作為參數。
上面代碼中,也展示了一種去除數組重複成員的方法。
向set加入值的時候,不會發生類型轉換,是以<code>5</code>和<code>"5"</code>是兩個不同的值。set内部判斷兩個值是否不同,使用的算法叫做“same-value equality”,它類似于精确相等運算符(<code>===</code>),主要的差別是<code>nan</code>等于自身,而精确相等運算符認為<code>nan</code>不等于自身。
上面代碼向set執行個體添加了兩個<code>nan</code>,但是隻能加入一個。這表明,在set内部,兩個<code>nan</code>是相等。
另外,兩個對象總是不相等的。
上面代碼表示,由于兩個空對象不相等,是以它們被視為兩個值。
set結構的執行個體有以下屬性。
<code>set.prototype.constructor</code>:構造函數,預設就是<code>set</code>函數。
<code>set.prototype.size</code>:傳回<code>set</code>執行個體的成員總數。
set執行個體的方法分為兩大類:操作方法(用于操作資料)和周遊方法(用于周遊成員)。下面先介紹四個操作方法。
<code>add(value)</code>:添加某個值,傳回set結構本身。
<code>delete(value)</code>:删除某個值,傳回一個布爾值,表示删除是否成功。
<code>has(value)</code>:傳回一個布爾值,表示該值是否為<code>set</code>的成員。
<code>clear()</code>:清除所有成員,沒有傳回值。
上面這些屬性和方法的執行個體如下。
下面是一個對比,看看在判斷是否包括一個鍵上面,<code>object</code>結構和<code>set</code>結構的寫法不同。
<code>array.from</code>方法可以将set結構轉為數組。
這就提供了去除數組重複成員的另一種方法。
set結構的執行個體有四個周遊方法,可以用于周遊成員。
<code>keys()</code>:傳回鍵名的周遊器
<code>values()</code>:傳回鍵值的周遊器
<code>entries()</code>:傳回鍵值對的周遊器
<code>foreach()</code>:使用回調函數周遊每個成員
需要特别指出的是,<code>set</code>的周遊順序就是插入順序。這個特性有時非常有用,比如使用set儲存一個回調函數清單,調用時就能保證按照添加順序調用。
(1)<code>keys()</code>,<code>values()</code>,<code>entries()</code>
<code>key</code>方法、<code>value</code>方法、<code>entries</code>方法傳回的都是周遊器對象(詳見《iterator對象》一章)。由于set結構沒有鍵名,隻有鍵值(或者說鍵名和鍵值是同一個值),是以<code>key</code>方法和<code>value</code>方法的行為完全一緻。
上面代碼中,<code>entries</code>方法傳回的周遊器,同時包括鍵名和鍵值,是以每次輸出一個數組,它的兩個成員完全相等。
set結構的執行個體預設可周遊,它的預設周遊器生成函數就是它的<code>values</code>方法。
這意味着,可以省略<code>values</code>方法,直接用<code>for...of</code>循環周遊set。
(2)<code>foreach()</code>
set結構的執行個體的<code>foreach</code>方法,用于對每個成員執行某種操作,沒有傳回值。
上面代碼說明,<code>foreach</code>方法的參數就是一個處理函數。該函數的參數依次為鍵值、鍵名、集合本身(上例省略了該參數)。另外,<code>foreach</code>方法還可以有第二個參數,表示綁定的this對象。
(3)周遊的應用
擴充運算符(<code>...</code>)内部使用<code>for...of</code>循環,是以也可以用于set結構。
擴充運算符和set結構相結合,就可以去除數組的重複成員。
而且,數組的<code>map</code>和<code>filter</code>方法也可以用于set了。
是以使用set可以很容易地實作并集(union)、交集(intersect)和差集(difference)。
如果想在周遊操作中,同步改變原來的set結構,目前沒有直接的方法,但有兩種變通方法。一種是利用原set結構映射出一個新的結構,然後指派給原來的set結構;另一種是利用<code>array.from</code>方法。
上面代碼提供了兩種方法,直接在周遊操作中改變原來的set結構。
weakset結構與set類似,也是不重複的值的集合。但是,它與set有兩個差別。
首先,weakset的成員隻能是對象,而不能是其他類型的值。
其次,weakset中的對象都是弱引用,即垃圾回收機制不考慮weakset對該對象的引用,也就是說,如果其他對象都不再引用該對象,那麼垃圾回收機制會自動回收該對象所占用的記憶體,不考慮該對象還存在于weakset之中。這個特點意味着,無法引用weakset的成員,是以weakset是不可周遊的。
上面代碼試圖向weakset添加一個數值和<code>symbol</code>值,結果報錯,因為weakset隻能放置對象。
weakset是一個構造函數,可以使用<code>new</code>指令,建立weakset資料結構。
作為構造函數,weakset可以接受一個數組或類似數組的對象作為參數。(實際上,任何具有iterable接口的對象,都可以作為weakset的參數。)該數組的所有成員,都會自動成為weakset執行個體對象的成員。
上面代碼中,<code>a</code>是一個數組,它有兩個成員,也都是數組。将<code>a</code>作為weakset構造函數的參數,<code>a</code>的成員會自動成為weakset的成員。
注意,是<code>a</code>數組的成員成為weakset的成員,而不是<code>a</code>數組本身。這意味着,數組的成員隻能是對象。
上面代碼中,數組<code>b</code>的成員不是對象,加入weakset就會報錯。
weakset結構有以下三個方法。
weakset.prototype.add(value):向weakset執行個體添加一個新成員。
weakset.prototype.delete(value):清除weakset執行個體的指定成員。
weakset.prototype.has(value):傳回一個布爾值,表示某個值是否在weakset執行個體之中。
下面是一個例子。
weakset沒有<code>size</code>屬性,沒有辦法周遊它的成員。
上面代碼試圖擷取<code>size</code>和<code>foreach</code>屬性,結果都不能成功。
weakset不能周遊,是因為成員都是弱引用,随時可能消失,周遊機制無法保證成員的存在,很可能剛剛周遊結束,成員就取不到了。weakset的一個用處,是儲存dom節點,而不用擔心這些節點從文檔移除時,會引發記憶體洩漏。
下面是weakset的另一個例子。
上面代碼保證了<code>foo</code>的執行個體方法,隻能在<code>foo</code>的執行個體上調用。這裡使用weakset的好處是,<code>foos</code>對執行個體的引用,不會被計入記憶體回收機制,是以删除執行個體的時候,不用考慮<code>foos</code>,也不會出現記憶體洩漏。
javascript的對象(object),本質上是鍵值對的集合(hash結構),但是傳統上隻能用字元串當作鍵。這給它的使用帶來了很大的限制。
上面代碼原意是将一個dom節點作為對象<code>data</code>的鍵,但是由于對象隻接受字元串作為鍵名,是以<code>element</code>被自動轉為字元串<code>[object htmldivelement]</code>。
為了解決這個問題,es6提供了map資料結構。它類似于對象,也是鍵值對的集合,但是“鍵”的範圍不限于字元串,各種類型的值(包括對象)都可以當作鍵。也就是說,object結構提供了“字元串—值”的對應,map結構提供了“值—值”的對應,是一種更完善的hash結構實作。如果你需要“鍵值對”的資料結構,map比object更合适。
上面代碼使用<code>set</code>方法,将對象<code>o</code>當作<code>m</code>的一個鍵,然後又使用<code>get</code>方法讀取這個鍵,接着使用<code>delete</code>方法删除了這個鍵。
作為構造函數,map也可以接受一個數組作為參數。該數組的成員是一個個表示鍵值對的數組。
上面代碼在建立map執行個體時,就指定了兩個鍵<code>name</code>和<code>title</code>。
map構造函數接受數組作為參數,實際上執行的是下面的算法。
下面的例子中,字元串<code>true</code>和布爾值<code>true</code>是兩個不同的鍵。
如果對同一個鍵多次指派,後面的值将覆寫前面的值。
上面代碼對鍵<code>1</code>連續指派兩次,後一次的值覆寫前一次的值。
如果讀取一個未知的鍵,則傳回<code>undefined</code>。
注意,隻有對同一個對象的引用,map結構才将其視為同一個鍵。這一點要非常小心。
上面代碼的<code>set</code>和<code>get</code>方法,表面是針對同一個鍵,但實際上這是兩個值,記憶體位址是不一樣的,是以<code>get</code>方法無法讀取該鍵,傳回<code>undefined</code>。
同理,同樣的值的兩個執行個體,在map結構中被視為兩個鍵。
上面代碼中,變量<code>k1</code>和<code>k2</code>的值是一樣的,但是它們在map結構中被視為兩個鍵。
由上可知,map的鍵實際上是跟記憶體位址綁定的,隻要記憶體位址不一樣,就視為兩個鍵。這就解決了同名屬性碰撞(clash)的問題,我們擴充别人的庫的時候,如果使用對象作為鍵名,就不用擔心自己的屬性與原作者的屬性同名。
如果map的鍵是一個簡單類型的值(數字、字元串、布爾值),則隻要兩個值嚴格相等,map将其視為一個鍵,包括<code>0</code>和<code>-0</code>。另外,雖然<code>nan</code>不嚴格相等于自身,但map将其視為同一個鍵。
map結構的執行個體有以下屬性和操作方法。
(1)size屬性
<code>size</code>屬性傳回map結構的成員總數。
(2)set(key, value)
<code>set</code>方法設定<code>key</code>所對應的鍵值,然後傳回整個map結構。如果<code>key</code>已經有值,則鍵值會被更新,否則就新生成該鍵。
<code>set</code>方法傳回的是map本身,是以可以采用鍊式寫法。
(3)get(key)
<code>get</code>方法讀取<code>key</code>對應的鍵值,如果找不到<code>key</code>,傳回<code>undefined</code>。
(4)has(key)
<code>has</code>方法傳回一個布爾值,表示某個鍵是否在map資料結構中。
(5)delete(key)
<code>delete</code>方法删除某個鍵,傳回true。如果删除失敗,傳回false。
(6)clear()
<code>clear</code>方法清除所有成員,沒有傳回值。
map原生提供三個周遊器生成函數和一個周遊方法。
<code>keys()</code>:傳回鍵名的周遊器。
<code>values()</code>:傳回鍵值的周遊器。
<code>entries()</code>:傳回所有成員的周遊器。
<code>foreach()</code>:周遊map的所有成員。
需要特别注意的是,map的周遊順序就是插入順序。
下面是使用執行個體。
上面代碼最後的那個例子,表示map結構的預設周遊器接口(<code>symbol.iterator</code>屬性),就是<code>entries</code>方法。
map結構轉為數組結構,比較快速的方法是結合使用擴充運算符(<code>...</code>)。
結合數組的<code>map</code>方法、<code>filter</code>方法,可以實作map的周遊和過濾(map本身沒有<code>map</code>和<code>filter</code>方法)。
此外,map還有一個<code>foreach</code>方法,與數組的<code>foreach</code>方法類似,也可以實作周遊。
<code>foreach</code>方法還可以接受第二個參數,用來綁定<code>this</code>。
上面代碼中,<code>foreach</code>方法的回調函數的<code>this</code>,就指向<code>reporter</code>。
(1)map轉為數組
前面已經提過,map轉為數組最友善的方法,就是使用擴充運算符(...)。
(2)數組轉為map
将數組轉入map構造函數,就可以轉為map。
(3)map轉為對象
如果所有map的鍵都是字元串,它可以轉為對象。
(4)對象轉為map
(5)map轉為json
map轉為json要區分兩種情況。一種情況是,map的鍵名都是字元串,這時可以選擇轉為對象json。
另一種情況是,map的鍵名有非字元串,這時可以選擇轉為數組json。
(6)json轉為map
json轉為map,正常情況下,所有鍵名都是字元串。
但是,有一種特殊情況,整個json就是一個數組,且每個數組成員本身,又是一個有兩個成員的數組。這時,它可以一一對應地轉為map。這往往是數組轉為json的逆操作。
<code>weakmap</code>結構與<code>map</code>結構基本類似,唯一的差別是它隻接受對象作為鍵名(<code>null</code>除外),不接受其他類型的值作為鍵名,而且鍵名所指向的對象,不計入垃圾回收機制。
上面代碼中,如果将<code>1</code>和<code>symbol</code>作為weakmap的鍵名,都會報錯。
<code>weakmap</code>的設計目的在于,鍵名是對象的弱引用(垃圾回收機制不将該引用考慮在内),是以其所對應的對象可能會被自動回收。當對象被回收後,<code>weakmap</code>自動移除對應的鍵值對。典型應用是,一個對應dom元素的<code>weakmap</code>結構,當某個dom元素被清除,其所對應的<code>weakmap</code>記錄就會自動被移除。基本上,<code>weakmap</code>的專用場合就是,它的鍵所對應的對象,可能會在将來消失。<code>weakmap</code>結構有助于防止記憶體洩漏。
下面是<code>weakmap</code>結構的一個例子,可以看到用法上與<code>map</code>幾乎一樣。
上面代碼中,變量<code>wm</code>是一個<code>weakmap</code>執行個體,我們将一個<code>dom</code>節點<code>element</code>作為鍵名,然後銷毀這個節點,<code>element</code>對應的鍵就自動消失了,再引用這個鍵名就傳回<code>undefined</code>。
weakmap與map在api上的差別主要是兩個,一是沒有周遊操作(即沒有<code>key()</code>、<code>values()</code>和<code>entries()</code>方法),也沒有<code>size</code>屬性;二是無法清空,即不支援<code>clear</code>方法。這與<code>weakmap</code>的鍵不被計入引用、被垃圾回收機制忽略有關。是以,<code>weakmap</code>隻有四個方法可用:<code>get()</code>、<code>set()</code>、<code>has()</code>、<code>delete()</code>。
前文說過,weakmap應用的典型場合就是dom節點作為鍵名。下面是一個例子。
上面代碼中,<code>myelement</code>是一個dom節點,每當發生click事件,就更新一下狀态。我們将這個狀态作為鍵值放在weakmap裡,對應的鍵名就是<code>myelement</code>。一旦這個dom節點删除,該狀态就會自動消失,不存在記憶體洩漏風險。
weakmap的另一個用處是部署私有屬性。
上面代碼中,countdown類的兩個内部屬性<code>_counter</code>和<code>_action</code>,是執行個體的弱引用,是以如果删除執行個體,它們也就随之消失,不會造成記憶體洩漏。