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

161 阅读2分钟

背景

前端没有队列这种数据结构,而我们一般使用数组来进行模拟

但是如果在大型数组上执行大量数组#push()和数组#shift(),则应该使用链表而不是数组,因为数组#shift()具有线性时间复杂度O(n),而队列#dequeue()具有恒定的时间复杂度O(1)。这对于大型阵列来说有很大的不同。

队列

队列是元素的有序列表,其中一个元素插入到队列的末尾,并从队列的前面移除。队列基于先进先出(FIFO)原则工作。

yocto-queue

用法

import Queue from 'yocto-queue';

const queue = new Queue();

queue.enqueue('🦄');
queue.enqueue('🌈');

console.log(queue.size);
//=> 2

console.log(...queue);
//=> '🦄 🌈'

console.log(queue.dequeue());
//=> '🦄'

console.log(queue.dequeue());
//=> '🌈'

源码

Node类

class Node {
	value;
	next;

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

node 类的作用就是用来定义一个典型链表,有value, next两个字段,通过next指向下一个节点//

Queue 类

  // 定义头指针,尾指针,队列长度
	#head;
	#tail;
	#size;

	constructor() {
    // 定义清空方法
		this.clear();
	}

  // 定义入队方法
	enqueue(value) {
    // 接受入参创建新的链表节点
		const node = new Node(value);

    
		if (this.#head) {
      // 队列存在时,根据先进先出插入队列末尾
			this.#tail.next = node;
			this.#tail = node;
		} else {
      
      // 队列不存在时,插入节点为头节点
			this.#head = node;
			this.#tail = node;
		}

    // 队列长度加一
		this.#size++;
	}

  // 定义出队方法
	dequeue() {
    // 获取当前队列,不存在直接返回
		const current = this.#head;
		if (!current) {
			return;
		}

    // 队列先进先出,将头节点指向下一节点,长度减一,并返回该队列
		this.#head = this.#head.next;
		this.#size--;
		return current.value;
	}

  // 清空方法,直接将数据重置
	clear() {
		this.#head = undefined;
		this.#tail = undefined;
		this.#size = 0;
	}

  // 长度方法,直接返回size
	get size() {
		return this.#size;
	}

  // Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用。
  // * yield 为generator 函数,内部实现迭代方法
  // 这也是为什么链表能使用 ... 方法和 for of 展开。
	* [Symbol.iterator]() {
		let current = this.#head;

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

总结

使用链表相较于数组的性能更好,数组的 #shift()具有线性时间复杂度O(n),而队列#dequeue()具有恒定的时间复杂度O(1);

在实现功能时,不仅要考虑把功能实现,更要注意性能问题。