第32期 | yocto-queue

132 阅读3分钟

1. 源码简介

yocto-queue在上一期 p-limit 里用被引用到,粗略理解是封装的一个队列数据结构的包,队列是种数据结构,遵循先进先出原则。

官方简介:Tiny queue data structure。

官方提示了一句,You should use this package instead of an array if you do a lot of Array#push() and Array#shift() on large arrays, since Array#shift() has linear time complexity O(n) while Queue#dequeue() has constant time complexity O(1). That makes a huge difference for large arrays.

意思是,如果你在一个大型数组上操作数据,请考虑使用这个包,而不是Array函数,因为Array操作时间复杂度为O(n),但队列的时间复杂度为O(1), That makes a huge difference for large arrays。😀

2. 测试用例

先介绍一下队列queue的几个API:

  • enqueue(value) 入队
  • dequeue() 出队
  • clear() 清除队列
  • size 队列大小

测试用例也不多,来看看。

  • .enqueue(),测试进队。(非常通俗易懂)
test('.enqueue()', t => {
    const queue = new Queue();
    queue.enqueue('🦄');
    t.is(queue.dequeue(), '🦄');
    queue.enqueue('🌈');
    queue.enqueue('❤️');
    t.is(queue.dequeue(), '🌈');
    t.is(queue.dequeue(), '❤️');
});
  • .dequeue(),测试出队,这里还测试了队列没有值的情况。
test('.dequeue()', t => {
    const queue = new Queue();
    t.is(queue.dequeue(), undefined);
    t.is(queue.dequeue(), undefined);
    queue.enqueue('🦄');
    t.is(queue.dequeue(), '🦄');
    t.is(queue.dequeue(), undefined);
});
  • clear(),清除队列。测试了队列没有值,队列存在一个值,队列存在多个值的清除情况。
test('.clear()', t => {
    const queue = new Queue();
    queue.clear();
    queue.enqueue(1);
    queue.clear();
    t.is(queue.size, 0);
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);
    queue.clear();
    t.is(queue.size, 0);
});
  • size,测试队列长度,测试了队列空值,清除队列后,执行入队操作,执行出队操作等边界情况。
  • iterable,
test('iterable', t => {
    const queue = new Queue();
    queue.enqueue('🦄');
    queue.enqueue('🌈');
    t.deepEqual([...queue], ['🦄', '🌈']);
});

3. 源码简解

// 创建Node节点
class Node {
    // 值
    value;
    // next指针
    next;
    constructor(value) {
        this.value = value;
    }
}
export default class Queue {
    // 头指针指向的Node
    #head;
    // 尾指针指向的Node
    #tail;
    // 队列长度
    #size;

    constructor() {
        // 先初始化一个空的队列
        this.clear();
    }
    // 入队操作
    enqueue(value) {
        // 把value转换成Node节点
        const node = new Node(value);
        // 如果头指针节点存在的话
        if (this.#head) {
            // 把尾指针节点的next指向当前的node, #tail.next -> node, #tail -> node
            this.#tail.next = node;
            this.#tail = node;
        } else {
            // 第一个值,#head -> node, #tail -> node
            this.#head = node;
            this.#tail = node;
        }
        // 长度+1
        this.#size++;
    }
    // 出队操作
    dequeue() {
        // 把头出掉
        const current = this.#head;
        // 简单判空处理
        if (!current) {
            return;
        }
        // #head -> #head.next, 移动一下指针
        this.#head = this.#head.next;
        // 长度-1
        this.#size--;
        // 返回这个出队的值
        return current.value;
    }
    // 清除队列,也就是初始化
    clear() {
        this.#head = undefined;
        this.#tail = undefined;
        this.#size = 0;
    }
    // 获取size
    get size() {
        return this.#size;
    }
    // 这里什么时候会被调用呢?是当进入for...of,就会进入Symbol.iterator这个方法。
    // 如果没找到,就会报错,像数组,对象都是有内置该方法。
    // 该方法必须返回一个迭代器,一个有next方法的对象。
    // *号代表生成器,调用生成器函数会产生一个Generator的迭代器对象。
    // 通过调用生成器函数的next(),generator函数将执行,直到遇到yield关键字。
    * [Symbol.iterator]() {
        // 从头开始迭代
        let current = this.#head;
        // 循环取值
        while (current) {
            // 遇到yield,return值
            yield current.value;
            // next给当前current,往下迭代
            current = current.next;
        }
    }
}

4. 总结

重新学习生成器和迭代器的概念,这个源码可以多写一写,常温习。