基础引见
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 文件:
<!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 系列
ES6 系列目次地点:https://github.com/mqyqingfeng/Blog
ES6 系列估计写二十篇摆布,旨在加深 ES6 部份知识点的明白,重点解说块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模仿完成、模块加载计划、异步处置惩罚等内容。
假如有毛病或许不严谨的处所,请务必赋予斧正,非常谢谢。假如喜好或许有所启示,迎接 star,对作者也是一种勉励。