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

141 阅读2分钟

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

源码地址

github.com/sindresorhu…

前提概要

如果你使用的数据结构是Queue(即使JS里对于数据结构都是通过Array/Object模拟出来的),那么在遵循FIFO的原则下,当你在数据量巨大的数据中进行操作时,Array#shift操作的时间复杂度达到了O(n)。而如果要在JS中实现FIFO的优化,利用Object模拟实现链表反而比Array来得更为高效。

源码解析

// 定义一个Node节点类用于每次新增数据时创建节点
class Node {
    value;
    next;

    constructor(value) {
        this.value = value;
    }
}

export default class Queue {
    // 创建私有属性头指针,尾指针和大小
    #head;
    #tail;
    #size;
  
    // 每次实例化时执行clear方法清除指针指向与size清零
    constructor() {
        this.clear();
    }

    enqueue(value) {
    // 对于入队操作的数据将其实例化为一个Node节点
        const node = new Node(value);

    // 头指针判断,首次有数据进入队列时头尾指针都指向该节点
    // 否则只需要将尾指针指向目标节点
    // 需要注意的是确保链表的连接性,因此需要先赋值#tail.next,再赋值#tail
    if (this.#head) {
        this.#tail.next = node;
        this.#tail = node;
    } else {
        this.#head = node;
        this.#tail = node;
    }

    // 每次入队时增加其大小
    this.#size++;
    }

    dequeue() {
        // 出队操作永远是获取链表中的头指针数据
        const current = this.#head;
        // 如果current无值则说明该队列为空
        if (!current) {
            return;
        }
        // 将头指针后移
        this.#head = this.#head.next;
        this.#size--;
        return current.value;
    }

    // 清空头尾指针的引用与size的值即清空了队列的数据,其余的交给垃圾回收机制
    clear() {
        this.#head = undefined;
        this.#tail = undefined;
        this.#size = 0;
    }

    get size() {
        return this.#size;
    }

    // 这一步在于为了实现该队列可迭代,也就是可被for...of遍历以及使用(...)扩展转换为数组
    * [Symbol.iterator]() {
        let current = this.#head;

        while (current) {
            yield current.value;
            current = current.next;
        }
    }
}

读源码时我们在读什么?

Javascript因为其数据类型的薄弱,对于各种数据结构都是以数组和对象的方式去模拟,以至于当我们使用JS去某些大数据场景去操作数据时并不能很清晰地看出时间复杂度,毕竟在我们了解到数组时,它所携带的API几乎已经囊括了我们所需要的业务需求。

而在此次的源码学习中我们需要知道如何去模拟链表,这在之后学习算法时会用到,而symbol实现迭代的方法,也是一个常用技巧。

let arr1 = new Array(100000).fill(0);
console.time('Array');
while(arr1.length) {
  arr1.shift();
}
console.timeEnd('Array'); // 1.121s

console.time('Queue in');
let arr2 = new Queue();
while(arr2.size < 100000) {
  arr2.enqueue(0);
}
console.timeEnd('Queue in'); // 46.138ms

console.time('Queue out');
while(arr2.size > 0) {
  arr2.dequeue();
}
console.timeEnd('Queue out'); // 4.872ms