在实现之前我们可以通过阮一峰的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();
}
}
模拟过程中可能会有相应的错误,也不是和原生的实现完全一致。仅当学习之用.