ES6 详解之模拟 Set 实现

基本介绍

ES6 提供了新的数据结构 Set。

它类似于数组,但是成员的值都是唯一的,没有重复的值。

初始化

Set 本身是一个构造函数,用来生成 Set 数据结构。

let set = new Set();

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

let set  = new Set([1,2,3,4,5,6,7,8,7]);

console.log(set); //{1,2,3,4,5,6,7,8}

set = new Set(document.querySelectorAll('div'));

console.log(set.size); //66

set = new Set([1,3,4,3,4,5,6,7,8]);

console.log(set.size); // 7

注意:set 的 size 属性只输出过滤掉的不重复的数量

属性和方法

操作的方法有 a

  • 1.add(value):添加某个值,返回 Set 结构本身,可以连式编程.
  • 2.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • 3.has(value):返回一个布尔值,表示该值是否为 Set 的成员。
  • 4.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

遍历方法:

  • 1.keys():返回键名的遍历器
  • 2.values():返回键值的遍历器 (set 默认的遍历方法)
  • 3.entries():返回键值对的遍历器
  • 4.forEach():使用回调函数遍历每个成员,无返回值

注意:以上遍历方法除了 forEach,返回的都是遍历器 iterator,keys 和 values 返回的值是同一个;

 let set = new Set(['a', 'b', 'c']);

for (const item of set) {

console.log(item); // a,b.c

}

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

遍历的应用

1.扩展运算符 … 内部使用 for…of 循环,也可以用于 set 结构

let set2 = new Set(["one","two","three"]);

console.log([...set2]); //  ["one", "two", "three"]

2.用于去除重复数值,这个不用多说,本身特有的特性。

3.Set 可以很容易的实现并集,交集和差集。

//并集

let let3 = new Set([1,2,3,4,5]);

let let4 = new Set([6,7,8,9,10]);

let union = new Set([...let3,...let4]);

console.log(union); // set(10)[1,2,3,4,5,6,7,8,9,10]

// 交集

let let3 = new Set([1,2,3,4,5]);

let let4 = new Set([2,4,5,1,4]);

let intersect = new Set([...let3].filter(x=>let4.has(x)));

console.log(intersect); //Set(4) {1, 2, 4, 5}

// 错集

let let3 = new Set([1,2,3,4,5]);

let let4 = new Set([2,4,5,1,4]);

let difference = new Set([...let3].filter(x => !let4.has(x)));

console.log(difference); // Set(1) {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 数据结构是需要对NaN去重的:

let set = new Set();

set.add(NaN).add(NaN).add(NaN).add(NaN);

console.log(set.size); // 1

所以我们需要对 NaN 这个值进行单独的处理。

/**

* 模拟实现第二版

*/

(function(global) {

// 定义一个NaN的Symbol值

var NaNSymbol = Symbol('NaN');

function encodeVal(value) {

return value !== value ? NaNSymbol : value;

}

function decodeVal(value) {

return (value === NaNSymbol) ? NaN : value;

}

// 构造一个Set方法

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]]

当初始化传入一个迭代器的时候,我们可以根据可以模拟实现一个forOf 函数,遍历传入的迭代器的 Symbol.iterator 接口,然后依次执行 add 方法。

而当执行 keys() 方法时,我们可以返回一个对象,然后为其部署 Symbol.iterator 接口,实现的代码,也是最终的代码如下:

/**

* 模拟实现第三版

*/

(function(global) {

var NaNSymbol = Symbol('NaN');

function encodeVal(value) {

return value !== value ? NaNSymbol : value;

}

function decodeVal(value) {

return (value === NaNSymbol) ? NaN : value;

}

function makeIterator(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)

单元测试用例 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!");

});

打开浏览器进行查看测试结果。

以上是 ES6 详解之模拟 Set 实现 的全部内容, 来源链接: utcz.com/z/264122.html

回到顶部