今日学习yocto-queue源码-只有几十行
yocto-queue这个库的使用场景是,当我们需要频繁的进行数组的push或shift操作时,就可以用这个库,因为这个库的时间复杂度是 O(1) ,利用空间换取了时间,并且在p-limit库中,其实就用到了这个库,让我们来看看这个库的源码,代码量也不多,总共也就几十行,有任何分析不对的地方,各位大佬麻烦帮忙指出
class Node {
/// value;
/// next;
constructor(value) {
this.value = value;
// TODO: Remove this when targeting Node.js 12.
this.next = undefined;
}
}
class Queue {
// TODO: Use private class fields when targeting Node.js 12.
// #_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;
}
get size() {
return this._size;
}
* [Symbol.iterator]() {
let current = this._head;
while (current) {
yield current.value;
current = current.next;
}
}
}
module.exports = Queue;
Node
声明了一个Node类,并且接收一个参数value,初始化了实例对象的value和next属性
当我看到value 和 next 这两个属性时,顿时感到无比的熟悉,具体是用来干嘛的呢?我们接着往下看
Queue(队列)
声明了一个Queue类,看到这,大概也能猜到,这是一个实现队列的库,constructor函数直接调用了clear函数,也就是说,new调用Queue类时,会初始化实例对象的三个属性
头部指针_head ,尾部指针 _tail,队列长度 _size
clear() {
this._head = undefined; // 头部指针
this._tail = undefined; // 尾部指针
this._size = 0; // 队列长度
}
进入队列
下述代码重点是 this._tail.next = node; 的这段代码, this._head的最后一个next === this._tail
,你只需要能理解这个,下述代码就豁然开朗
每次修改 this._tail.next = node 其实就是在修改head的最后一个next的值
enqueue(value) { // value-> 进入队列的任务
const node = new Node(value);
if (this._head) { // 判断有没有头部指针,也就是判断是否是第一个进入队列,(如果第一个进入队列,就会走else)
this._tail.next = node; // 将尾部指针的next指向新的node ,重点:这里的tail其实跟head的最后一个next的地址是一样的,所以这里的next相当于是 给head的最后一个next赋值,看不懂的话要多看几遍
this._tail = node; // 将尾部指针指向最新加进来的任务
} else {
//第一次进入队列
this._head = node; // 头部指针指向第一个任务
this._tail = node; // 尾部指针指向第一个任务
}
this._size++; // 增加队列长度
}
退出队列
dequeue() {
const current = this._head; // 获取头部指针的地址
if (!current) {
return;
}
// 其实就是把当前头部的任务送出去,并且将指针指向下一个任务
// 把头部指针的地址切换成下一个的,此步骤切换地址是不会影响current的地址
this._head = this._head.next; // 退出队列 ,并将拿到排队的任务
this._size--; // 队列长度-1
return current.value; 返回退出的任务
}
迭代器
有Symbol.iterator 属性表示可以被for...of 遍历, 加上* 表示是个生成器对象,可以使用yield关键字,yield 右边的值就是每次循环拿到的值
对象一开始是没有Symbol.iterator,所以普通对象是不可被for...of遍历的
* [Symbol.iterator]() {
let current = this._head;//遍历从头部指针开始
while (current) {
yield current.value; // current.value就是每次循环的值
current = current.next;// 循环的每一项,每次拿完值后,将current修改为下一个指针 ,避免死循环
}
}
使用
const queue = new Queue()
queue.enqueue('你好你好') // 加入队列
console.log(queue.dequeue()) // 退出队列并返回值
'你好你好'
// 我们也可以这样做
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.enqueue(4)
queue.enqueue(5)
console.log([...queue]) // [1,2,3,4,5]
for (const val of queue) {
console.log(val)
}
总结:
以上就是我分享整个 yocto-queue 源码的过程。整体看下来代码其实并不长,但是作用却很大,非常实用。让我从中学到了数组、队列、指针、链表的知识,对我个人来说作用很大。