本文转载自微信公众号「前端胖头鱼」,面试作者前端胖头鱼。官对个转载本文请联系前端胖头鱼公众号。解那 es6新增了Set数据结构,实现它允许你存储任何类型的面试唯一值,无论是官对个原始值还是对象引用。这篇文章希望通过模拟实现一个Set来增加对它的解那理解。 实际工作和学习过程中,实现你可能也经常用Set来对数组做去重处理 以下内容基本出自MDN,这里写出来,纯粹是官对个为了便于后面的模拟操作。如果你已经很熟悉了,解那可以直接略过。实现 可以传递一个可迭代对象,面试它的官对个所有元素将被添加到新的 Set中。如果不指定此参数或其值为null,解那则新的 Set为空。 constructor Set的构造函数 size Set 长度 Set.prototype.add(value) 在Set对象尾部添加一个元素。返回该Set对象。 Set.prototype.has(value) 返回一个布尔值,表示该值在Set中存在与否。 Set.prototype.delete(value) 移除Set中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,站群服务器返回true,否则返回false) Set.prototype.clear() 移除Set对象内的所有元素。没有返回值 Set.prototype.keys() 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。 Set.prototype.values() 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。 Set.prototype.entries() 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。为了使这个方法和Map对象保持相似, 每个值的键和值相等。 Set.prototype.forEach(callbackFn[, thisArg]) 按照插入顺序,为Set对象中的服务器租用每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。 以上回顾了一下Set的基本使用,我们可以开始尝试模拟实现一把啦。你也可以直接点击查看源码。 目录结构 ├──set-polyfill │ ├──iterator.js // 导出一个构造函数Iterator,模拟创建可迭代对象 │ ├──set.js // Set类 │ ├──utils.js // 辅助函数 │ ├──test.js // 测试 Set整体框架 开始实现Set细节前,我们先看一下会用到的一些辅助方法 assert, 这个方法是学习vuex源码时候看到的,感觉蛮实用的,主要用来对某些条件进行判断,抛出错误。 isDef, 过滤掉null和undefined isIterable, 简单判断value是否是迭代器对象. forOf, 模拟for of行为, 对迭代器对象进行遍历操作。 执行 node test.js size属性和操作方法 上面的例子把Set的size属性和操作方法过了一遍,打印出来的Set实例和原生的长得不太一样,就先不管了。 遍历方法 遍历方法看起来也可以达到和前面例子一样的效果,源码实现部分基本就到这里啦,但是还没完... 从MDN找来这段话,在JavaScript中迭代器是一个对象,它提供了一个next() 方法,用来返回序列中的下一项。这个方法返回包含两个属性:done(表示遍历是否结束)和 value(当前的值)。 迭代器对象一旦被创建,就可以反复调用next()。 这个时候可以讲一下我们的iterator.js中的代码了 Iterator的实例有一个next方法,每次调用都会返回一个done属性和value属性,其语意和前面的解释是一样的。 看到这里你可能已经知道了,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实例本身,它是一个可以被迭代的对象。 到这里上面的问题2就可以解释通了。 再看看问题1为什么[ ...s2 ]可以得到数组[ s, e, t ]呢?,原因也是我们给Set、keys、values、entries部署了Symbol.iterator,使之具有“iterator”接口,而扩展运算符...的特点之一就是任何具有Iterator接口的对象,都可以用扩展运算符转为真正的数组。 模拟过程中可能会有相应的错误,也不是和原生的实现完全一致。仅当学习之用,欢迎大家拍砖。前言
用在前面
基本语法
实例属性和方法
属性
操作方法
栗子
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 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"] 整体结构
辅助方法
源码实现
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() } } 测试一把
iterator(迭代器)
for of
结尾
参考
Set 迭代器和生成器 ES6 系列之模拟实现一个 Set 数据结构 展开语法 for...of 循环