摘要:基本介紹提供了新的數據結構。初始化本身是一個構造函數,用來生成數據結構。函數可以接受一個數組或者具有接口的其他數據結構作為參數,用來初始化。返回一個布爾值,表示該值是否為的成員。清除所有成員,無返回值。
基本介紹
ES6 提供了新的數據結構 Set。
它類似于數組,但是成員的值都是唯一的,沒有重復的值。
初始化Set 本身是一個構造函數,用來生成 Set 數據結構。
let set = new Set();
Set 函數可以接受一個數組(或者具有 iterable 接口的其他數據結構)作為參數,用來初始化。
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屬性和方法
操作方法有:
add(value):添加某個值,返回 Set 結構本身。
delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
has(value):返回一個布爾值,表示該值是否為 Set 的成員。
clear():清除所有成員,無返回值。
舉個例子:
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():返回鍵值對的遍歷器
forEach():使用回調函數遍歷每個成員,無返回值
注意 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.prototype.constructor:構造函數,默認就是 Set 函數。
Set.prototype.size:返回 Set 實例的成員總數。
模擬實現第一版如果要模擬實現一個簡單的 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]QUnit
由上我們也可以發現,每當我們進行一版的修改時,只是寫了新的測試代碼,但是代碼改寫后,對于之前的測試代碼是否還能生效呢?是否不小心改了什么導致以前的測試代碼沒有通過呢?
為了解決這個問題,針對模擬實現 Set 這樣一個簡單的場景,我們可以引入 QUnit 用于編寫測試用例,我們新建一個 HTML 文件:
Set 的模擬實現
編寫測試用例,因為語法比較簡單,我們就直接看編寫的一些例子:
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 系列ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標簽模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96224.html
摘要:注意這里因為添加完元素之后返回的是該對象,所以可以鏈式調用結果是,但是中只會存一個模擬實現的整體結構除此之外我們還需要二個輔助方法模擬行為對迭代器對象進行遍歷操作。 更多系列文章請看 在實現之前我們可以通過阮一峰的ECMAScript 6 入門了解一下Set的基本信息 1、Set的基本語法 new Set([ iterable ]) 可以傳遞一個可迭代對象,它的所有元素將被添加到新的 ...
摘要:一個對象若只被弱引用所引用,則被認為是不可訪問或弱可訪問的,并因此可能在任何時刻被回收。也就是說,一旦不再需要,里面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。 前言 我們先從 WeakMap 的特性說起,然后聊聊 WeakMap 的一些應用場景。 特性 1. WeakMap 只接受對象作為鍵名 const map = ...
摘要:回顧我們先來回顧下箭頭函數的基本語法。主要區別包括沒有箭頭函數沒有,所以需要通過查找作用域鏈來確定的值。箭頭函數并沒有方法,不能被用作構造函數,如果通過的方式調用,會報錯。 回顧 我們先來回顧下箭頭函數的基本語法。 ES6 增加了箭頭函數: let func = value => value; 相當于: let func = function (value) { return ...
摘要:值可以作為標識符,用于對象的屬性名,可以保證不會出現同名的屬性。的結果為因為不是通過的方式實現的,所以的結果自然是。這個實現類似于函數記憶,我們建立一個對象,用來儲存已經創建的值即可。方法返回一個已登記的類型值的。 前言 實際上,Symbol 的很多特性都無法模擬實現……所以先讓我們回顧下有哪些特性,然后挑點能實現的……當然在看的過程中,你也可以思考這個特性是否能實現,如果可以實現,該...
摘要:前言在了解是如何編譯前,我們先看看的和的構造函數是如何對應的。這是它跟普通構造函數的一個主要區別,后者不用也可以執行。該函數的作用就是將函數數組中的方法添加到構造函數或者構造函數的原型中,最后返回這個構造函數。 前言 在了解 Babel 是如何編譯 class 前,我們先看看 ES6 的 class 和 ES5 的構造函數是如何對應的。畢竟,ES6 的 class 可以看作一個語法糖,...
閱讀 1437·2021-11-25 09:43
閱讀 2580·2021-09-24 10:30
閱讀 3659·2021-09-06 15:02
閱讀 3593·2019-08-30 15:55
閱讀 3300·2019-08-30 15:53
閱讀 1693·2019-08-30 15:52
閱讀 2142·2019-08-30 14:21
閱讀 2010·2019-08-30 13:55