1
更多系列文章请看

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

Alan
1.5k 声望1.7k 粉丝

青青子衿,悠悠我心。