前言
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
这是源码共读的第32期,链接【若川视野 x 源码共读】第32期 | yocto-queue 队列 链表 - 掘金 (juejin.cn)
介绍
yocto-queue是什么
我的理解是,他在用链表实现一个队列,其中实现了队列的push,unshift,size功能,因为数组具有迭代性,他也实现了可迭代
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());
//=> '🦄'
从代码中我们可以看出
enqueue:类似于pushdequeue:类似于unshift...queue:说明可迭代
源码
准备源码
git clone git@github.com:sindresorhus/yocto-queue.git
cd yocto-queue
npm install yocto-queue
查看依赖
"scripts": {
"test": "ava && tsd"
},
说明这里我们测试继续使用的是ava,一种Node.js环境下的测试运行器
代码
Node节点
class Node {
value;//当前value
next;//存储下一个节点指针
constructor(value) {
this.value = value;
}
}
Node
-
queue里面的itemvalue表示当前节点的值next表示当前节点的下一个节点
-
我们在构造器里,进行赋值,
constructor是一个类最先执行
const node = new Node(value)
node Node {value: 'hello', next: undefined}
Queue类
export default class Queue {
#head;// 指向头指针
#tail;// 指向当前节点
#size;// 当前队列元素个数
constructor() {
this.clear();
}
// 入队,模拟push
enqueue(val) {}
// 出队,模拟unshift
dequeue() {}
// 清空
clear() {}
// 当前队列元素个数
get(){}
enqueue
enqueue(value) {
const node = new Node(value);
if (this.#head) {
console.log("当前值1",this.#tail);
//将当前值的下一个节点指向这个节点
this.#tail.next = node;
console.log("当前值2",this.#tail);
//然后将当前节点指向这个节点
this.#tail = node;
console.log("当前值3",this.#tail);
} else {
// 当队列为空时,我们就设置头节点
this.#head = node;
this.#tail = node;
}
// 队列元素个数 + 1
this.#size++;
}
实现逻辑:
- 首先,因为是链表实现的,所以我们每一个其实都是节点,先将需要添加的值变为一个节点
- 判断是不是第一个节点
- 如果是第一个节点,那么就需要设置头节点,并将当前节点转到这个
node上 - 如果不是第一个节点,那么就好办了,直接往后放
- 如果是第一个节点,那么就需要设置头节点,并将当前节点转到这个
- 因为添加了元素,所以
size++
测试一下
const queue = new Queue();
console.log("queue",queue);
queue.enqueue("hello")
console.log("queue", queue);
queue.enqueue("summer")
console.log("queue", queue);
queue Queue {#head: undefined, #tail: undefined, #size: 0}
queue Queue {#head: Node, #tail: Node, #size: 1}
当前值1 Node {value: 'hello', next: undefined}
当前值2 Node {value: 'hello', next: Node}
当前值3 Node {value: 'summer', next: undefined}
queue Queue {#head: Node, #tail: Node, #size: 2}
发现:
-
在
new Queue时,因为在构造函数中写了clear()函数head为undefinedtail为undefinedsize为0
-
enque实现的时候,我们需要判断是不是第一个节点- 至于为什么,这是因为,等会
dequeue是删除第一个,这里就需要直到第一个节点是啥current - 如果是第一个节点,那么设置
头节点,并且转到当前节点tail - 如果不是,那就
this.tail.next = node,并将node设置为当前节点
- 至于为什么,这是因为,等会
dequeue
dequeue() {
const current = this.#head;
console.log("current",current);//所以他模拟的其实是unshift()
if (!current) {
return;
}
// 将头节点后移
this.#head = this.#head.next;
this.#size--;
return current.value;
}
这时候我们就可以知道为啥我们需要设置头节点了
因为我们这个使用链表来模拟队列
- 普通队列
unshift,时间复杂度时O(n) - 但是现在实现的这个
dqueue,时间复杂度O(1)
因为我们只是将头节点进行了后移
迭代器
其实我们就是将queue这个对象设置为可迭代的
* [Symbol.iterator]() {
let current = this.#head;
// 通过循环,不断挪动指针获取到值
while (current) {
yield current.value;
current = current.next;
}
}
这边就是自己在设置了一个迭代器
yeild:这个可以用来暂停生成器函数的执行,然后我们会将yeild后面的值返回给这个生成器的调用者
参考链接:迭代协议 - JavaScript | MDN (mozilla.org)
总结收获
了解到如何使用链表来实现一个队列
- 这种实现,对于
unshift这种操作,时间复杂度降低了很多,从原来的0(n)降到了O(1),但是对于push时间复杂度都是o(n) - 具体可以看这个js源码-数组中的push()和unshift()方法的源码实现 - KG-work-space - 博客园 (cnblogs.com)
困惑:我还是不太了解ava这种测试,或者说我不知道如何调ava,我感觉他就是在判断,实际输出和预期输出是不是相同?