【若川视野 x 源码共读】第32期| yocto-queue

190 阅读4分钟

【若川视野 x 源码共读】第32期| yocto-queue

本文参加了由公众号@若川视野发起的每周源码共读活动, 点击了解详情一起参与。

❑ 计算机内存犹如一大堆抽屉。

❑ 需要存储多个元素时,可使用数组或链表。

❑ 数组的元素都在一起。

❑ 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。

❑ 数组的读取速度很快。

❑ 链表的插入和删除速度很快。

场景:和室友去食堂吃饭,第一打完,后面都需要挪动位置,去填补空缺,当有人插队得时候,后面所有人都需要往后挪动位置,如果数据量很大,在软件开发中就是常谈得性能问题,需要一些手段进行优化。

线性表和链表的不同

  • 链表比较方便插入和删除操作,线性表中插入一个元素,那么后面的元素地址都要往后移,删除同理。而链表只需要修改结点中的指针信息,不需要修改结点地址。
  • 线性表内容可以随机访问,因为是连续的内存单元,地址连续所以在这个区间内可以进行随机,链表存储地址不一定时连续的,所以不能随机访问(因为知道第一个一直往后去找值 head.next.next.xxx);查找速度由于存储地址原因也快于链表。

此时可能你会问:JavaScript 当中的数组也是动态的呀,也可以随意添加和删除元素呀。确实如此,不过 JavaScript 提供的原生方法虽然用起来方便,但是性能很低。

yocto-queue介绍

定义:yocto-queue是一个小型队列数据结构

适用场景:用yocto-queue代替数组,当你遇到在大数组上执行大量的push和shift方法时。原因是shift方法的执行是线性复杂度的,而yocto-queue入队操作是常数复杂度。

  • enqueue():向队列尾部添加新元素
  • dequeue():移除队列的第一项
  • clear():清除队列里的所有元素
  • size():返回队列里元素的数量
/*
它是如何工作的:
`this.#head`是一个`Node`的实例,它记录了它的当前值,并嵌套了另一个`Node`的实例,它记录了它之后的值。当一个值被提供给`.enqueue()`时,代码需要遍历`this.#head`,不断深入以找到最后的值。然而,遍历每一个单项是很慢的。这个问题的解决方法是将最后一个值的引用保存为`this.#tail`,这样它就可以引用它来添加新的值。
*/class Node {
    value;
    next;
    // 链表 next 存储后继节点的地址。
    constructor(value) {
        this.value = value;
    }
}
​
export default class Queue {
    #head; // 头指针
    #tail; // 尾指针
    #size; // 数组长度constructor() {
        // new Queue() 将会重置
        this.clear();
    }
​
    enqueue(value) {
        const node = new Node(value);
​
        if (this.#head) {
            // 存在头指针时 将尾指针 #tail.next #tail本身 都赋值
            this.#tail.next = node;
            this.#tail = node;
        } else {
            // 第一次新增时候 头尾指针指向同一个值
            this.#head = node;
            this.#tail = node;
        }
        // 长度+1
        this.#size++;
    }
​
    dequeue() {
        const current = this.#head;
        if (!current) {
            // 没有就返回 undefined
            return;
        }
​
        this.#head = this.#head.next;
        this.#size--;
        return current.value;
    }
​
    clear() {
        // 可以使其触发 JS 的 GC
        this.#head = undefined;
        this.#tail = undefined;
        this.#size = 0;
    }
​
    get size() {
        return this.#size;
    }
​
    // 这里是给队列加上迭代器,并且用生成器去替代
    * [Symbol.iterator]() {
        let current = this.#head;
    // 判断头部指针是否还存在
        while (current) {
            // yield后面的值是当前迭代返回的值
            yield current.value;
            // 头部指针向后移一位
            current = current.next;
        }
    }
}
​

Symbol.iterator

可能我们并不常见,但是其实我们有天天接触

let obj = { x: 1, y: 2, z: 3 }
console.log([...obj]) // TypeError: obj is not iterable

Symbol.iterator为每一个对象定义了默认的迭代器。该迭代器可以被 for…of 循环使用。

*

第一次看到 * 还没明白什么语法,看了其他笔记分享才突然发现这是一个生成器。

调用生成器函数会产生一个生成器对象,其一开始处于暂停状态,该对象也实现了Iterator接口,通过调next()使其转为开始或者恢复执行状态。生成器函数在遇到yield关键字前会正常执行,遇到该关键字后,执行会停止,函数作用域的状态被保留 —— 有点像函数的 中间返回语句,它能让函数返回一个值出去,但是函数仍能继续执行。随后通过在生成器对象上调用next方法恢复执行。

await async 就是 promise + iterator 来实现的

\