- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
我们知道要存储多个元素,数组是最常用的数据结构,但是数组会有一个缺点,数组大小是固定的,要从数组的起点或中间插入或移除项的成本是很高的,因为需要移动元素。这个时候我们可以考虑到链表,链表是存储有序的元素集合并且元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成。本节我们一起学习yocto-queue
GitHub地址:github.com/sindresorhu…
下图展示了一个链表的结构
相对于数组,链表的一个好处在于添加或移除元素的时候不需要移动其他元素
yocto-queue
官网对已yocto-queue的介绍是如果你在大型数组上做很多事情,你应该使用这个包而不是Array#push()数组Array#shift(),因为Array#shift()它具有线性时间复杂度 O(n) 而Queue#dequeue()具有恒定时间复杂度 O(1) 。这对大型阵列产生了巨大的影响。
接下来我们来看看yocto-queue内部是如何实现的
class Node {
value;//当前元素的值
next;//指向下一个元素的指针
constructor(value) {
this.value = value;
}
}
在源码里有一个Node类,Node类表示我们想要添加到链表中的项
export default class Queue {
#head;//指向第一个元素的引用
#tail;//指向最后一个元素的引用
#size;//当前元素的个数
constructor() {
this.clear();
}
enqueue(value) {
//实例化一个Node
const node = new Node(value);
//如果没有#head,那么#head和#tail都是node
//如果有#head,那么#tail为最后一个节点,即node
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;
}
get size() {
//返回当前队列的长度
return this.#size;
}
//生成器函数,可以用for...of遍历
* [Symbol.iterator]() {
let current = this.#head;
while (current) {
yield current.value;
current = current.next;
}
}
}
因为涉及到数组的push操作和unshift操作所以这里用到队列数据结构,队列是遵循先进先出原则,在尾部添加新元素,在顶部移除元素,其中enqueue方法表示往队列尾部添加一个新元素,dequeue方法表示移除队列的第一项,clear方法表示清空队列,size方法返回当前队列元素个数,Symbol.iterator一个生成器函数,只要在内部有这个函数,那么就能用for...of遍历
总结
- 对队列与链表的使用
- Symbol.iterator的使用场景