es6新增了Set数据结构,它容许你存储任何类型的惟一值,不管是原始值仍是对象引用。这篇文章但愿经过模拟实现一个Set来增长对它的理解。
原文连接javascript
实际工做和学习过程当中,你可能也常常用Set来对数组作去重处理
let unique = (array) => { return [ ...new Set(array) ] } console.log(unique([ 1, 2, 3, 4, 1, 2, 5 ])) // [1, 2, 3, 4, 5]
如下内容基本出自MDN,这里写出来,纯粹是为了便于后面的模拟操做。若是你已经很熟悉了,能够直接略过。
new Set([ iterable ])
能够传递一个可迭代对象,它的全部元素将被添加到新的 Set中。若是不指定此参数或其值为null,则新的 Set为空。vue
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) {}
属性java
constructor
Set的构造函数node
size
Set 长度git
操做方法es6
在Set对象尾部添加一个元素。返回该Set对象。github
返回一个布尔值,表示该值在Set中存在与否。vuex
移除Set中与这个值相等的元素,返回Set.prototype.has(value)在这个操做前会返回的值(即若是该元素存在,返回true,不然返回false)数组
移除Set对象内的全部元素。没有返回值 数据结构
栗子
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) {}
遍历方法
返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的全部元素的值。
返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的全部元素的值。
返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的全部元素的值的[value, value]数组。为了使这个方法和Map对象保持类似, 每一个值的键和值相等。
按照插入顺序,为Set对象中的每个值调用一次callBackFn。若是提供了thisArg参数,回调中的this会是这个参数。
栗子
let s = new Set([ 's', 'e', 't' ]) s // SetIterator {"s", "e", "t"} s.keys() // SetIterator {"s", "e", "t"} s.values() // SetIterator {"s", "e", "t"} s.entries() // SetIterator {"s", "e", "t"} // log [ ...s ] // ["s", "e", "t"] [ ...s.keys() ] // ["s", "e", "t"] [ ...s.values() ] // ["s", "e", "t"] [ ...s.entries() ] // [["s", "s"], ["e", "e"], ["t", "t"]] s.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 s.forEach(function () { console.log(this) }, { name: 'qianlongo' }) // {name: "qianlongo"} // {name: "qianlongo"} // {name: "qianlongo"} for (let value of s) { console.log(value) } // s // e // t for (let value of s.entries()) { console.log(value) } // ["s", "s"] // ["e", "e"] // ["t", "t"]
以上回顾了一下Set的基本使用,咱们能够开始尝试模拟实现一把啦。你也能够 直接点击查看源码。
目录结构
├──set-polyfill
│ ├──iterator.js // 导出一个构造函数Iterator,模拟建立可迭代对象
│ ├──set.js // Set类
│ ├──utils.js // 辅助函数
│ ├──test.js // 测试
Set总体框架
class Set { constructor (iterable) {} get size () {} has () {} add () {} delete () {} clear () {} forEach () {} keys () {} values () {} entries () {} [ Symbol.iterator ] () {} }
开始实现Set细节前,咱们先看一下会用到的一些辅助方法
const assert = (condition, msg) => { if (!condition) throw new Error(msg) }
null
和undefined
const isDef = (value) => { return value != void 0 }
const isIterable = (value) => { return isDef(value) && typeof value[ Symbol.iterator ] === 'function' }
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() } }
class Set { constructor (iterable) { // 使用数组来存储Set的每一项元素 this.value = [] // 判断是否使用new调用 assert(this instanceof Set, 'Constructor Set requires "new"') // 过滤掉null和undefined if (isDef(iterable)) { // 是可迭代对象才进行下一步forOf元素添加 assert(isIterable(iterable), `${iterable} is not iterable`) // 循环可迭代对象,初始化 forOf(iterable, (value) => { this.add(value) }) } } // 获取s.size时候会调用 size函数,返回value数组的长度 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/get get size () { return this.value.length } // 使用数组的includes方法判断是否包含value // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes // [ NaN ].includes(NaN)会返回true,正好Set也只能存一个NaN has (value) { return this.value.includes(value) } // 经过has方法判断value是否存在,不存在则添加进数组,最后返回Set自己,支持链式调用 add (value) { if (!this.has(value)) { this.value.push(value) } return this } // 在删除以前先判断value是否存在用之当作返回值,存在则经过splice方法移除 delete (value) { let result = this.has(value) if (result) { this.value.splice(this.value.indexOf(value), 1) } return result } // 从新赋值一个空数组,即实现clear方法 clear () { this.value = [] } // 经过forOf遍历 values返回的迭代对象,实现forEach forEach (callback, thisArg) { forOf(this.values(), (value) => { callback.call(thisArg, value, value, this) }) } // 返回一个迭代对象,该对象中的值是Set中的value keys () { return new Iterator(this.value) } // 同keys values () { return this.keys() } // 返回一个迭代对象,不一样keys和values的是其值是[value, value] entries () { return new Iterator(this.value, (value) => [ value, value ]) } // 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的全部元素的值。 [ Symbol.iterator ] () { return this.values() } }
测试一把
执行 node test.js
size属性和操做方法
const Set = require('./set') const s = new Set() s.add(1) .add(2) .add(NaN) .add(NaN) console.log(s) // Set { value: [ 1, 2, NaN ] } console.log(s.has(1)) // true console.log(s.has(NaN)) // true console.log(s.size) // 3 s.delete(1) console.log(s.has(1)) // false console.log(s.size) // 2 s.clear() console.log(s) // Set { value: [] }
上面的例子把Set的size属性和操做方法过了一遍,打印出来的Set实例和原生的长得不太同样,就先无论了。
遍历方法
let s2 = new Set([ 's', 'e', 't' ]) console.log(s2) // Set { value: [ 's', 'e', 't' ] } console.log(s2.keys()) // Iterator {} console.log(s2.values()) // Iterator {} console.log(s2.entries()) // Iterator {} console.log([ ...s2 ]) // [ 's', 'e', 't' ] console.log([ ...s2.keys() ]) // [ 's', 'e', 't' ] console.log([ ...s2.values() ]) // [ 's', 'e', 't' ] console.log([ ...s2.entries() ]) // [ [ 's', 's' ], [ 'e', 'e' ], [ 't', 't' ] ] s2.forEach(function (value, key, set) { console.log(value, key, set, this) }) // s s Set { value: [ 's', 'e', 't' ] } global // e e Set { value: [ 's', 'e', 't' ] } global // t t Set { value: [ 's', 'e', 't' ] } global s2.forEach(function () { console.log(this) }, { name: 'qianlongo' }) // { name: 'qianlongo' } // { name: 'qianlongo' } // { name: 'qianlongo' } // {name: "qianlongo"} // {name: "qianlongo"} // {name: "qianlongo"} for (let value of s) { console.log(value) } // s // e // t for (let value of s.entries()) { console.log(value) } // ["s", "s"] // ["e", "e"] // ["t", "t"]
遍历方法看起来也能够达到和前面例子同样的效果,源码实现部分基本就到这里啦,可是还没完...
[ ...s2 ]
能够获得数组[ 's', 'e', 't' ]
呢?s2
为何能够被for of
循环呢?从 MDN找来这段话,在JavaScript中迭代器是一个对象,它提供了一个next() 方法,用来返回序列中的下一项。这个方法返回包含两个属性:done(表示遍历是否结束)和 value(当前的值)。
迭代器对象一旦被建立,就能够反复调用next()。
function makeIterator(array){ var nextIndex = 0 return { next: function () { return nextIndex < array.length ? { done: false, value: array[ nextIndex++ ] } : { done: true, value: undefined } } }; } var it = makeIterator(['yo', 'ya']) console.log(it.next()) // { done: false, value: "yo" } console.log(it.next()) // { done: false, value: "ya" } console.log(it.next()) // { done: true, value: undefined }
这个时候能够讲一下咱们的iterator.js
中的代码了
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 } }
Iterator
的实例有一个next方法,每次调用都会返回一个done
属性和value
属性,其语意和前面的解释是同样的。
let it = new Iterator(['yo', 'ya']) console.log(it.next()) // { done: false, value: "yo" } console.log(it.next()) // { done: false, value: "ya" } console.log(it.next()) // { done: true, value: undefined }
看到这里你可能已经知道了,Iterator要实现的功能之一就是提供一个迭代器。那这个又和上面的问题1和2有啥关系呢?咱们再来看看for of
一个数据结构只要部署了Symbol.iterator属性,就被视为具备iterator接口,就能够用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法 for...of 循环
默认只有(Array,Map,Set,String,TypedArray,arguments)可被for of
迭代。咱们自定义的Set
类不在这其中,前面的例子中却在for of
循环中打印出了想要的值。缘由就是咱们给Iterator
类部署了Symbol.iterator
方法,执行该方法便返回Iterator
实例自己,它是一个能够被迭代的对象。
[ Symbol.iterator ] () { return this }
到这里上面的问题2就能够解释通了。
再看看问题1 为何
[ ...s2 ]能够获得数组
[ 's', 'e', 't' ]呢?
,缘由也是咱们给Set
、keys
、values
、entries
部署了Symbol.iterator,使之具备“iterator”接口,而扩展运算符...
的特色之一就是任何具备Iterator接口的对象,均可以用扩展运算符转为真正的数组。
模拟过程当中可能会有相应的错误,也不是和原生的实现彻底一致。仅当学习之用,欢迎你们拍砖。