ES6 提供了新的數據結構 Set。css
它相似於數組,可是成員的值都是惟一的,沒有重複的值。html
Set 自己是一個構造函數,用來生成 Set 數據結構。git
let set = new Set();
Set 函數能夠接受一個數組(或者具備 iterable 接口的其餘數據結構)做爲參數,用來初始化。github
let set = new Set([1, 2, 3, 4, 4]); console.log(set); // Set(4) {1, 2, 3, 4} set = new Set(document.querySelectorAll('div')); console.log(set.size); // 66 set = new Set(new Set([1, 2, 3, 4])); console.log(set.size); // 4
操做方法有:數組
舉個例子:瀏覽器
let set = new Set(); console.log(set.add(1).add(2)); // Set [ 1, 2 ] console.log(set.delete(2)); // true console.log(set.has(2)); // false console.log(set.clear()); // undefined console.log(set.has(1)); // false
之因此每一個操做都 console 一下,就是爲了讓你們注意每一個操做的返回值。數據結構
遍歷方法有:異步
注意 keys()、values()、entries() 返回的是遍歷器函數
let set = new Set(['a', 'b', 'c']); console.log(set.keys()); // SetIterator {"a", "b", "c"} console.log([...set.keys()]); // ["a", "b", "c"]
let set = new Set(['a', 'b', 'c']); console.log(set.values()); // SetIterator {"a", "b", "c"} console.log([...set.values()]); // ["a", "b", "c"]
let set = new Set(['a', 'b', 'c']); console.log(set.entries()); // SetIterator {"a", "b", "c"} console.log([...set.entries()]); // [["a", "a"], ["b", "b"], ["c", "c"]]
let set = new Set([1, 2, 3]); set.forEach((value, key) => console.log(key + ': ' + value)); // 1: 1 // 2: 2 // 3: 3
屬性:測試
若是要模擬實現一個簡單的 Set 數據結構,實現 add、delete、has、clear、forEach 方法,仍是很容易寫出來的,這裏直接給出代碼:
/** * 模擬實現初版 */ (function(global) { function Set(data) { this._values = []; this.size = 0; data && data.forEach(function(item) { this.add(item); }, this); } Set.prototype['add'] = function(value) { if (this._values.indexOf(value) == -1) { this._values.push(value); ++this.size; } return this; } Set.prototype['has'] = function(value) { return (this._values.indexOf(value) !== -1); } Set.prototype['delete'] = function(value) { var idx = this._values.indexOf(value); if (idx == -1) return false; this._values.splice(idx, 1); --this.size; return true; } Set.prototype['clear'] = function(value) { this._values = []; this.size = 0; } Set.prototype['forEach'] = function(callbackFn, thisArg) { thisArg = thisArg || global; for (var i = 0; i < this._values.length; i++) { callbackFn.call(thisArg, this._values[i], this._values[i], this); } } Set.length = 0; global.Set = Set; })(this)
咱們能夠寫段測試代碼:
let set = new Set([1, 2, 3, 4, 4]); console.log(set.size); // 4 set.delete(1); console.log(set.has(1)); // false set.clear(); console.log(set.size); // 0 set = new Set([1, 2, 3, 4, 4]); set.forEach((value, key, set) => { console.log(value, key, set.size) }); // 1 1 4 // 2 2 4 // 3 3 4 // 4 4 4
在初版中,咱們使用 indexOf 來判斷添加的元素是否重複,本質上,仍是使用 === 來進行比較,對於 NaN 而言,由於:
console.log([NaN].indexOf(NaN)); // -1
模擬實現的 Set 其實能夠添加多個 NaN 而不會去重,然而對於真正的 Set 數據結構:
let set = new Set(); set.add(NaN); set.add(NaN); console.log(set.size); // 1
因此咱們須要對 NaN 這個值進行單獨的處理。
處理的方式是當判斷添加的值是 NaN 時,將其替換爲一個獨一無二的值,好比說一個很難重複的字符串相似於 @@NaNValue
,固然了,說到獨一無二的值,咱們也能夠直接使用 Symbol,代碼以下:
/** * 模擬實現第二版 */ (function(global) { var NaNSymbol = Symbol('NaN'); var encodeVal = function(value) { return value !== value ? NaNSymbol : value; } var decodeVal = function(value) { return (value === NaNSymbol) ? NaN : value; } function Set(data) { this._values = []; this.size = 0; data && data.forEach(function(item) { this.add(item); }, this); } Set.prototype['add'] = function(value) { value = encodeVal(value); if (this._values.indexOf(value) == -1) { this._values.push(value); ++this.size; } return this; } Set.prototype['has'] = function(value) { return (this._values.indexOf(encodeVal(value)) !== -1); } Set.prototype['delete'] = function(value) { var idx = this._values.indexOf(encodeVal(value)); if (idx == -1) return false; this._values.splice(idx, 1); --this.size; return true; } Set.prototype['clear'] = function(value) { ... } Set.prototype['forEach'] = function(callbackFn, thisArg) { ... } Set.length = 0; global.Set = Set; })(this)
寫段測試用例:
let set = new Set([1, 2, 3]); set.add(NaN); console.log(set.size); // 3 set.add(NaN); console.log(set.size); // 3
在模擬實現 Set 時,最麻煩的莫過於迭代器的實現和處理,好比初始化以及執行 keys()、values()、entries() 方法時都會返回迭代器:
let set = new Set([1, 2, 3]); console.log([...set]); // [1, 2, 3] console.log(set.keys()); // SetIterator {1, 2, 3} console.log([...set.keys()]); // [1, 2, 3] console.log([...set.values()]); // [1, 2, 3] console.log([...set.entries()]); // [[1, 1], [2, 2], [3, 3]]
並且 Set 也支持初始化的時候傳入迭代器:
let set = new Set(new Set([1, 2, 3])); console.log(set.size); // 3
當初始化傳入一個迭代器的時候,咱們能夠根據咱們在上一篇 《ES6 系列之迭代器與 for of》中模擬實現的 forOf 函數,遍歷傳入的迭代器的 Symbol.iterator 接口,而後依次執行 add 方法。
而當執行 keys() 方法時,咱們能夠返回一個對象,而後爲其部署 Symbol.iterator 接口,實現的代碼,也是最終的代碼以下:
/** * 模擬實現第三版 */ (function(global) { var NaNSymbol = Symbol('NaN'); var encodeVal = function(value) { return value !== value ? NaNSymbol : value; } var decodeVal = function(value) { return (value === NaNSymbol) ? NaN : value; } var makeIterator = function(array, iterator) { var nextIndex = 0; // new Set(new Set()) 會調用這裏 var obj = { next: function() { return nextIndex < array.length ? { value: iterator(array[nextIndex++]), done: false } : { value: void 0, done: true }; } }; // [...set.keys()] 會調用這裏 obj[Symbol.iterator] = function() { return obj } return obj } function forOf(obj, cb) { let iterable, result; if (typeof obj[Symbol.iterator] !== "function") throw new TypeError(obj + " is not iterable"); if (typeof cb !== "function") throw new TypeError('cb must be callable'); iterable = obj[Symbol.iterator](); result = iterable.next(); while (!result.done) { cb(result.value); result = iterable.next(); } } function Set(data) { this._values = []; this.size = 0; forOf(data, (item) => { this.add(item); }) } Set.prototype['add'] = function(value) { value = encodeVal(value); if (this._values.indexOf(value) == -1) { this._values.push(value); ++this.size; } return this; } Set.prototype['has'] = function(value) { return (this._values.indexOf(encodeVal(value)) !== -1); } Set.prototype['delete'] = function(value) { var idx = this._values.indexOf(encodeVal(value)); if (idx == -1) return false; this._values.splice(idx, 1); --this.size; return true; } Set.prototype['clear'] = function(value) { this._values = []; this.size = 0; } Set.prototype['forEach'] = function(callbackFn, thisArg) { thisArg = thisArg || global; for (var i = 0; i < this._values.length; i++) { callbackFn.call(thisArg, this._values[i], this._values[i], this); } } Set.prototype['values'] = Set.prototype['keys'] = function() { return makeIterator(this._values, function(value) { return decodeVal(value); }); } Set.prototype['entries'] = function() { return makeIterator(this._values, function(value) { return [decodeVal(value), decodeVal(value)]; }); } Set.prototype[Symbol.iterator] = function(){ return this.values(); } Set.prototype['forEach'] = function(callbackFn, thisArg) { thisArg = thisArg || global; var iterator = this.entries(); forOf(iterator, (item) => { callbackFn.call(thisArg, item[1], item[0], this); }) } Set.length = 0; global.Set = Set; })(this)
寫段測試代碼:
let set = new Set(new Set([1, 2, 3])); console.log(set.size); // 3 console.log([...set.keys()]); // [1, 2, 3] console.log([...set.values()]); // [1, 2, 3] console.log([...set.entries()]); // [1, 2, 3]
由上咱們也能夠發現,每當咱們進行一版的修改時,只是寫了新的測試代碼,可是代碼改寫後,對於以前的測試代碼是否還能生效呢?是否不當心改了什麼致使之前的測試代碼沒有經過呢?
爲了解決這個問題,針對模擬實現 Set 這樣一個簡單的場景,咱們能夠引入 QUnit 用於編寫測試用例,咱們新建一個 HTML 文件:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Set 的模擬實現</title> <link rel="stylesheet" href="qunit-2.4.0.css"> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="qunit-2.4.0.js"></script> <script src="polyfill-set.js"></script> <script src="test.js"></script> </body> </html>
編寫測試用例,由於語法比較簡單,咱們就直接看編寫的一些例子:
QUnit.test("unique value", function(assert) { const set = new Set([1, 2, 3, 4, 4]); assert.deepEqual([...set], [1, 2, 3, 4], "Passed!"); }); QUnit.test("unique value", function(assert) { const set = new Set(new Set([1, 2, 3, 4, 4])); assert.deepEqual([...set], [1, 2, 3, 4], "Passed!"); }); QUnit.test("NaN", function(assert) { const items = new Set([NaN, NaN]); assert.ok(items.size == 1, "Passed!"); }); QUnit.test("Object", function(assert) { const items = new Set([{}, {}]); assert.ok(items.size == 2, "Passed!"); }); QUnit.test("set.keys", function(assert) { let set = new Set(['red', 'green', 'blue']); assert.deepEqual([...set.keys()], ["red", "green", "blue"], "Passed!"); }); QUnit.test("set.forEach", function(assert) { let temp = []; let set = new Set([1, 2, 3]); set.forEach((value, key) => temp.push(value * 2) ) assert.deepEqual(temp, [2, 4, 6], "Passed!"); });
用瀏覽器預覽 HTML 頁面,效果以下圖:
完整的 polyfill 及 Qunit 源碼在 https://github.com/mqyqingfeng/Blog/tree/master/demos/qunit。
ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級做用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。