Jamey's Jamey's
首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于

Jamey

首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于
  • 深入系列

  • 专题系列

  • underscore系列

  • ES6系列

    • ES6 系列之 let 和 const
    • ES6 系列之模板字符串
    • ES6 系列之箭头函数
    • ES6 系列之模拟实现 Symbol 类型
    • ES6 系列之迭代器与 for of
    • ES6 系列之模拟实现一个 Set 数据结构
      • 一. 基本介绍
      • 二. 初始化
      • 三. 属性和方法
      • 四. 模拟实现第一版
      • 五. 模拟实现第二版
      • 六. 模拟实现第三版
      • 七. QUnit
    • ES6 系列之 WeakMap
    • ES6 系列之我们来聊聊 Promise
    • ES6 系列之 Generator 的自动执行
    • ES6 系列之我们来聊聊 Async
    • ES6 系列之异步处理实战
    • ES6 系列之 Babel 将 Generator 编译成了什么样子
    • ES6 系列之 Babel 将 Async 编译成了什么样子
    • ES6 系列之 Babel 是如何编译 Class 的(上)
    • ES6 系列之 Babel 是如何编译 Class 的(下)
    • ES6 系列之 defineProperty 与 proxy
    • ES6 系列之模块加载方案
    • ES6 系列之我们来聊聊装饰器
    • ES6 系列之私有变量的实现
    • ES6 完全使用手册
  • 模块化

  • 正则表达式

  • 单元测试

  • 微前端

  • 实用函数

  • Rollup

  • 解决方案

  • 《JavaScript》笔记
  • ES6系列
Jamey
2021-11-24
目录

ES6 系列之模拟实现一个 Set 数据结构

# ES6 系列之模拟实现一个 Set 数据结构

# 一. 基本介绍

ES6 提供了新的数据结构 Set。

它类似于数组,但是成员的值都是唯一的,没有重复的值。

# 二. 初始化

Set 本身是一个构造函数,用来生成 Set 数据结构。

let set = new Set();
1

Set 函数可以接受一个数组(或者是具有 iterable 接口的其他数据结构)作为参数,用来初始化。

let set = new Set([1, 2, 3, 4, 4]);
console.log(set); // Set(4) {1, 2, 3, 4}

set = new Set(document.querySelectorAll('div'));
console.log(set.size); // 66

set = new Set(new Set([1, 2, 3, 4]));
console.log(set.size); // 4
1
2
3
4
5
6
7
8

# 三. 属性和方法

操作方法有:

  1. add(value):添加某个值,返回 Set 结构本身。
  2. delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  3. has(value):返回一个布尔值,表示该值是否为 Set 的成员。
  4. clear():清除所有成员,无返回值。

举个例子:

let set = new Set();
console.log(set.add(1).add(2)); // Set [ 1, 2 ]

console.log(set.delete(2)); // true
console.log(set.has(2)); // false

console.log(set.clear()); // undefined
console.log(set.has(1)); // false
1
2
3
4
5
6
7
8

之所以每个操作都 console 一下,就是为了让大家注意每个操作的返回值。

遍历方法有:

  1. keys():返回键名的遍历器。
  2. values():返回键值的遍历器。
  3. entries():返回键值对的遍历器。
  4. forEach():使用回调函数遍历每个成员,无返回值。

注意:keys()、values()、entries() 返回的是遍历器。

let set = new Set(['a', 'b', 'c']);
console.log(set.keys()); // SetIterator {"a", "b", "c"}
console.log([...set.keys()]); // ["a", "b", "c"]
1
2
3
let set = new Set(['a', 'b', 'c']);
console.log(set.values()); // SetIterator {"a", "b", "c"}
console.log([...set.values()]); // ["a", "b", "c"]
1
2
3
let set = new Set(['a', 'b', 'c']);
console.log(set.entries()); // SetIterator {"a", "b", "c"}
console.log([...set.entries()]); // [["a", "a"], ["b", "b"], ["c", "c"]]
1
2
3
let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(key + ': ' + value));
// 1: 1
// 2: 2
// 3: 3
1
2
3
4
5

属性:

  1. Set.prototype.constructor:构造函数,默认就是 Set 函数。
  2. Set.prototype.size:返回 Set 实例的成员总数。

# 四. 模拟实现第一版

如果要模拟实现一个简单的 Set 数据结构,实现 add、delete、has、clear、forEach 方法,还是很容易写出来的,这里直接给出代码:

/**
 * 模拟实现第一版
 */
(function(global) {

    function Set(data) {
        this._values = [];
        this.size = 0;

        data && data.forEach(function(item) {
            this.add(item);
        }, this);
    }

    Set.prototype['add'] = function(value) {
        if (this._values.indexOf(value) == -1) {
            this._values.push(value);
            ++this.size;
        }
        return this;
    }

    Set.prototype['has'] = function(value) {
        return (this._values.indexOf(value) !== -1);
    }

    Set.prototype['delete'] = function(value) {
        var idx = this._values.indexOf(value);
        if (idx == -1) return false;
        this._values.splice(idx, 1);
        --this.size;
        return true;
    }

    Set.prototype['clear'] = function(value) {
        this._values = [];
        this.size = 0;
    }

    Set.prototype['forEach'] = function(callbackFn, thisArg) {
        thisArg = thisArg || global;
        for (var i = 0; i < this._values.length; i++) {
            callbackFn.call(thisArg, this._values[i], this._values[i], this);
        }
    }

    Set.length = 0;

    global.Set = Set;

})(this)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

我们可以写段测试代码:

let set = new Set([1, 2, 3, 4, 4]);
console.log(set.size); // 4

set.delete(1);
console.log(set.has(1)); // false

set.clear();
console.log(set.size); // 0

set = new Set([1, 2, 3, 4, 4]);
set.forEach((value, key, set) => {
	console.log(value, key, set.size)
});
// 1 1 4
// 2 2 4
// 3 3 4
// 4 4 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 五. 模拟实现第二版

在第一版中,我们使用 indexOf 来判断添加的元素是否重复,本质上,还是使用 === 来进行比较,对于 NaN 而言,因为:

console.log([NaN].indexOf(NaN)); // -1
1

模拟实现的 Set 其实可以添加多个 NaN 而不会去重,然而对于真正的 Set 数据结构:

let set = new Set();
set.add(NaN);
set.add(NaN);
console.log(set.size); // 1
1
2
3
4

所以我们需要对 NaN 这个值进行单独的处理。

处理的方式是当判断添加的值是 NaN 时,将其替换为一个独一无二的值,比如说一个很难重复的字符串类似于 @@NaNValue,当然了,说到独一无二的值,我们也可以直接使用 Symbol,代码如下:

/**
 * 模拟实现第二版
 */
(function(global) {

    var NaNSymbol = Symbol('NaN');

    var encodeVal = function(value) {
        return value !== value ? NaNSymbol : value;
    }

    var decodeVal = function(value) {
        return (value === NaNSymbol) ? NaN : value;
    }

    function Set(data) {
        this._values = [];
        this.size = 0;

        data && data.forEach(function(item) {
            this.add(item);
        }, this);

    }

    Set.prototype['add'] = function(value) {
        value = encodeVal(value);
        if (this._values.indexOf(value) == -1) {
            this._values.push(value);
            ++this.size;
        }
        return this;
    }

    Set.prototype['has'] = function(value) {
        return (this._values.indexOf(encodeVal(value)) !== -1);
    }

    Set.prototype['delete'] = function(value) {
        var idx = this._values.indexOf(encodeVal(value));
        if (idx == -1) return false;
        this._values.splice(idx, 1);
        --this.size;
        return true;
    }

    Set.prototype['clear'] = function(value) {
        ...
    }

    Set.prototype['forEach'] = function(callbackFn, thisArg) {
        ...
    }

    Set.length = 0;

    global.Set = Set;

})(this)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

写段测试用例:

let set = new Set([1, 2, 3]);

set.add(NaN);
console.log(set.size); // 3

set.add(NaN);
console.log(set.size); // 3
1
2
3
4
5
6
7

# 六. 模拟实现第三版

在模拟实现 Set 时,最麻烦的莫过于迭代器的实现和处理,比如初始化以及执行 keys()、values()、entries() 方法时都会返回迭代器:

let set = new Set([1, 2, 3]);

console.log([...set]); // [1, 2, 3]
console.log(set.keys()); // SetIterator {1, 2, 3}
console.log([...set.keys()]); // [1, 2, 3]
console.log([...set.values()]); // [1, 2, 3]
console.log([...set.entries()]); // [[1, 1], [2, 2], [3, 3]]
1
2
3
4
5
6
7

而且 Set 也支持初始化的时候传入迭代器:

let set = new Set(new Set([1, 2, 3]));
console.log(set.size); // 3
1
2

当初始化传入一个迭代器的时候,我们可以根据我们在上一篇 《ES6 系列之迭代器与 for of》中模拟实现的 forOf 函数,遍历传入的迭代器的 Symbol.iterator 接口,然后依次执行 add 方法。

而当执行 keys() 方法时,我们可以返回一个对象,然后为其部署 Symbol.iterator 接口,实现的代码,也是最终的代码如下:

/**
 * 模拟实现第三版
 */
(function(global) {

    var NaNSymbol = Symbol('NaN');

    var encodeVal = function(value) {
        return value !== value ? NaNSymbol : value;
    }

    var decodeVal = function(value) {
        return (value === NaNSymbol) ? NaN : value;
    }

    var makeIterator = function(array, iterator) {
        var nextIndex = 0;

        // new Set(new Set()) 会调用这里
        var obj = {
            next: function() {
                return nextIndex < array.length ? { value: iterator(array[nextIndex++]), done: false } : { value: void 0, done: true };
            }
        };

        // [...set.keys()] 会调用这里
        obj[Symbol.iterator] = function() {
            return obj
        }

        return obj
    }

    function forOf(obj, cb) {
        let iterable, result;

        if (typeof obj[Symbol.iterator] !== "function") throw new TypeError(obj + " is not iterable");
        if (typeof cb !== "function") throw new TypeError('cb must be callable');

        iterable = obj[Symbol.iterator]();

        result = iterable.next();
        while (!result.done) {
            cb(result.value);
            result = iterable.next();
        }
    }

    function Set(data) {
        this._values = [];
        this.size = 0;

        forOf(data, (item) => {
            this.add(item);
        })

    }

    Set.prototype['add'] = function(value) {
        value = encodeVal(value);
        if (this._values.indexOf(value) == -1) {
            this._values.push(value);
            ++this.size;
        }
        return this;
    }

    Set.prototype['has'] = function(value) {
        return (this._values.indexOf(encodeVal(value)) !== -1);
    }

    Set.prototype['delete'] = function(value) {
        var idx = this._values.indexOf(encodeVal(value));
        if (idx == -1) return false;
        this._values.splice(idx, 1);
        --this.size;
        return true;
    }

    Set.prototype['clear'] = function(value) {
        this._values = [];
        this.size = 0;
    }

    Set.prototype['forEach'] = function(callbackFn, thisArg) {
        thisArg = thisArg || global;
        for (var i = 0; i < this._values.length; i++) {
            callbackFn.call(thisArg, this._values[i], this._values[i], this);
        }
    }

    Set.prototype['values'] = Set.prototype['keys'] = function() {
        return makeIterator(this._values, function(value) { return decodeVal(value); });
    }

    Set.prototype['entries'] = function() {
        return makeIterator(this._values, function(value) { return [decodeVal(value), decodeVal(value)]; });
    }

    Set.prototype[Symbol.iterator] = function(){
        return this.values();
    }

    Set.prototype['forEach'] = function(callbackFn, thisArg) {
        thisArg = thisArg || global;
        var iterator = this.entries();

        forOf(iterator, (item) => {
            callbackFn.call(thisArg, item[1], item[0], this);
        })
    }

    Set.length = 0;

    global.Set = Set;

})(this)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

写段测试代码:

let set = new Set(new Set([1, 2, 3]));
console.log(set.size); // 3

console.log([...set.keys()]); // [1, 2, 3]
console.log([...set.values()]); // [1, 2, 3]
console.log([...set.entries()]); // [1, 2, 3]
1
2
3
4
5
6

# 七. QUnit

由上我们也可以发现,每当我们进行一版的修改时,只是写了新的测试代码,但是代码改写后,对于之前的测试代码是否还能生效呢?是否不小心改了什么导致以前的测试代码没有通过呢?

为了解决这个问题,针对模拟实现 Set 这样一个简单的场景,我们可以引入 QUnit 用于编写测试用例,我们新建一个 HTML 文件:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Set 的模拟实现</title>
    <link rel="stylesheet" href="qunit-2.4.0.css">
</head>

<body>
    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <script src="qunit-2.4.0.js"></script>
    <script src="polyfill-set.js"></script>
    <script src="test.js"></script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

编写测试用例,因为语法比较简单,我们就直接看编写的一些例子:

QUnit.test("unique value", function(assert) {
    const set = new Set([1, 2, 3, 4, 4]);
    assert.deepEqual([...set], [1, 2, 3, 4], "Passed!");
});

QUnit.test("unique value", function(assert) {
    const set = new Set(new Set([1, 2, 3, 4, 4]));
    assert.deepEqual([...set], [1, 2, 3, 4], "Passed!");
});

QUnit.test("NaN", function(assert) {
    const items = new Set([NaN, NaN]);
    assert.ok(items.size == 1, "Passed!");
});

QUnit.test("Object", function(assert) {
    const items = new Set([{}, {}]);
    assert.ok(items.size == 2, "Passed!");
});

QUnit.test("set.keys", function(assert) {
    let set = new Set(['red', 'green', 'blue']);
    assert.deepEqual([...set.keys()], ["red", "green", "blue"], "Passed!");
});


QUnit.test("set.forEach", function(assert) {
    let temp = [];
    let set = new Set([1, 2, 3]);
    set.forEach((value, key) => temp.push(value * 2) )

    assert.deepEqual(temp, [2, 4, 6], "Passed!");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

用浏览器预览 HTML 页面,效果如下图:

javascript_05-17_01

#JavaScript ES6
上次更新: 2022/07/01, 17:34:19
ES6 系列之迭代器与 for of
ES6 系列之 WeakMap

← ES6 系列之迭代器与 for of ES6 系列之 WeakMap→

Theme by Vdoing | Copyright © 2017-2023 Jamey | blog 闽ICP备19022664号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式