共计 3347 个字符,预计需要花费 9 分钟才能阅读完成。
在实现之前我们可以通过阮一峰的 ECMAScript 6 入门了解一下 Set 的基本信息
1、Set 的基本语法
new Set([iterable])
可以传递一个可迭代对象,它的所有元素将被添加到新的 Set 中。如果不指定此参数或其值为 null,则新的 Set 为空。
let s = new Set([1, 2, 3]) // Set(3) {1, 2, 3}
let s2 = new Set() // Set(0) {}
let s3 = new Set(null /* or undefined */) // Set(0) {}
1.1 实例属性和方法
属性
constructor: Set 的构造函数
size: Set 长度
操作方法
add(value): 在 Set 对象尾部添加一个元素。返回该 Set 对象。
has(value): 返回一个布尔值,表示该值在 Set 中存在与否。
Sdelete(value): 移除 Set 中与这个值相等的元素,返回 has(value)在这个操作前会返回的值(即如果该元素存在,返回 true,否则返回 false)
-clear(): 移除 Set 对象内的所有元素。没有返回值
遍历方法
keys(): 返回一个新的迭代器对象,该对象包含 Set 对象中的按插入顺序排列的所有元素的值。
-values(): 返回一个新的迭代器对象,该对象包含 Set 对象中的按插入顺序排列的所有元素的值。
entries(): 返回一个新的迭代器对象,该对象包含 Set 对象中的按插入顺序排列的所有元素的值的 [value, value] 数组。为了使这个方法和 Map 对象保持相似,每个值的键和值相等。
-forEach(callbackFn[, thisArg]): 按照插入顺序,为 Set 对象中的每一个值调用一次 callBackFn。如果提供了 thisArg 参数,回调中的 this 会是这个参数。
let s = new Set()
s.add(1) // Set(1) {1}
.add(2) // Set(2) {1, 2}
.add(NaN) // Set(2) {1, 2, NaN}
.add(NaN) // Set(2) {1, 2, NaN}
// 注意这里因为添加完元素之后返回的是该 Set 对象,所以可以链式调用
// NaN === NaN 结果是 false,但是 Set 中只会存一个 NaN
s.has(1) // true
s.has(NaN) // true
s.size // 3
s.delete(1)
s.has(1) // false
s.size // 2
s.clear()
s // Set(0) {}
let s2 = new Set([‘s’, ‘e’, ‘t’])
s2 // SetIterator {“s”, “e”, “t”}
s2.keys() // SetIterator {“s”, “e”, “t”}
s2.values() // SetIterator {“s”, “e”, “t”}
s2.entries() // SetIterator {“s”, “e”, “t”}
// log
[…s2] // [“s”, “e”, “t”]
[…s2.keys() ] // [“s”, “e”, “t”]
[…s2.values() ] // [“s”, “e”, “t”]
[…s2.entries() ] // [[“s”, “s”], [“e”, “e”], [“t”, “t”]]
s2.forEach(function (value, key, set) {
console.log(value, key, set, this)
})
// s s Set(3) {“s”, “e”, “t”} Window
// e e Set(3) {“s”, “e”, “t”} Window
// t t Set(3) {“s”, “e”, “t”} Window
s2.forEach(function () {
console.log(this)
}, {name: ‘qianlongo’})
// {name: “qianlongo”}
// {name: “qianlongo”}
// {name: “qianlongo”}
for (let value of s2) {
console.log(value)
}
// s
// e
// t
for (let value of s2.entries()) {
console.log(value)
}
// [“s”, “s”]
// [“e”, “e”]
// [“t”, “t”]
2、模拟实现
2.1、Set 的整体结构
class Set {
constructor (iterable) {}
get size () {}
has () {}
add () {}
delete () {}
clear () {}
forEach () {}
keys () {}
values () {}
entries () {}
[Symbol.iterator] () {}
}
除此之外我们还需要二个辅助方法 1、forOf, 模拟 for of 行为, 对迭代器对象进行遍历操作。
const forOf = (iterable, callback, ctx) => {
let result
iterable = iterable[Symbol.iterator]()
result = iterable.next()
while (!result.done) {
callback.call(ctx, result.value)
result = iterable.next()
}
}
2、Iterator 迭代器, 更多迭代器信息请看 Iterator, 我们这里面用迭代器是为了让我们的 set 的 values()等可进行遍历。
class Iterator {
constructor (arrayLike, iteratee = (value) => value) {
this.value = Array.from(arrayLike)
this.nextIndex = 0
this.len = this.value.length
this.iteratee = iteratee
}
next () {
let done = this.nextIndex >= this.len
let value = done ? undefined : this.iteratee(this.value[ this.nextIndex++])
return {done, value}
}
[Symbol.iterator] () {
return this
}
}
2.3、实现源码
class Set {
constructor(iterable){
this.value = [];
if(!this instanceof Set) throw new Error(‘Constructor Set requires “new”‘);
if(isDef(iterable)) {
if(typeof iterable[ Symbol.iterator] !== ‘function’) new Error(`${iterable} is not iterable`);
// 循环可迭代对象,初始化
forOf(iterable, value => this.add(value));
}
}
get size(){
return this.value.length;
}
has(val) {
return this.value.includes(val); // [NaN].includes(NaN)会返回 true,正好 Set 也只能存一个 NaN
}
add(val) {
if(!this.has(val)) {
this.value.push(val);
}
return this;
}
delete(val) {
const index = this.value.indexOf(val);
if (index > -1) {
this.value.splice(index, 1);
return true;
}
return false;
}
clear() {
this.value.length = 0;
}
forEach(cb, arg) {
forOf(this.values(), val => {
cb.call(arg, val, val, this);
})
}
keys() {
return new Iterator(this.value);
}
values() {
return this.keys();
}
entries() {
return new Iterator(this.value, (value) => [value, value])
}
[Symbol.iterable]() {
return this.values();
}
}
模拟过程中可能会有相应的错误,也不是和原生的实现完全一致。仅当学习之用.