1. 前言
1.1 这个库,是干啥的
面试的时候是不是经常会遇到问如果一个很长很长的数组,让你操作,如果减少便利的时间? 这个库看起来不就是数组的两个方法么:
Array.push()可向数组的末尾添加一个或多个元素,并返回新的长度,改变原数组Array.shift()在取出队列首端的一个元素,整个队列往前移,这样原先排第二的元素现在排在了第一,并返回移除的值,改变原数组
如果学过数据结构,就会敏锐地发现,诶这两个操作,不就是在模拟队列吗
queue(译*队列) 队列是一个有序的元素列表,其中一个元素插入到队列的末尾,然后从队列的前面移除。队列的工作原理是先进先出(FIFO) 。
JS 没有queue这个数据结构,用数组模拟就好了,真方便!
nonono,回到开头,当数据量较小的时候,似乎没什么影响,但如果数据量较大,性能就会严重下降
这是因为在底层实现中,数组是顺序存储的,当你shift的时候,会先取出队列首端的一个元素,整个队列往前移——整个操作的事件时间复杂度是**O(n)**
如果你的项目正如上面我所说的情况,那么你很可能就需要这个包 yocto-queue,它能让你的shift操作时间复杂度降为O(1)。(在这库里面shift用的是dequeue方法)
1.2 你能学到
- ES6 中的
class - 链表和数组的区别,时间复杂度
- JS 实现链表的方法
- 学习 Symbol.iterator 的使用场景
- 调试源码
2. 准备
2.1 了解API
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());
//=> '🌈'
复制代码
queue = new Queue()
The instance is an Iterable, which means you can iterate over the queue front to back with a “for…of” loop, or use spreading to convert the queue to an array. Don't do this unless you really need to though, since it's slow.
该实例是可枚举的,也就是说 你可以用for...of来遍历,并且可以用扩展运算符将其变为数组,但是尽量不要这样做,这样性能很差
.enqueue(value)
添加一个元素到队尾
.dequeue()
删去队头,并返回被删除的值 || 或者是 undefined(队列本来就已经为空的情况)
.clear()
清空队列
.size
返回队列的大小
3 看看 源码
3.1 环境准备
# 克隆官方仓库
git clone https://github.com/sindresorhus/yocto-queue.git
cd .\yocto-queue\
npm install
code .
复制代码
3.3 调试源码
查看 package.json文件来确定主入口为 index.js
demo
新建文件夹examples,存放 demo index.js
// yocto-queue/examples/index.js
import Queue from "../index.js";
const queue = new Queue(); //此处打断点
queue.enqueue("⛵");
queue.enqueue("🌊");
console.log(queue.dequeue());
console.log(queue.size);
for (let q of queue) {
console.log(q);
}
queue.clear();
复制代码
node examples/index.js或者直接F5也可以即可开始调试源码,其实这个代码复杂度不手动调试也可以的,但是通过调试可以让你很明确地看到哪一步代码用到了哪里的东西
源码解析
// 定义一个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;
}
}
}
参考文章链接
前舟 juejin.cn/post/709165… molychn juejin.cn/post/709538…
4. 学习资源
5. 总结 & 收获
-
复习了 ES6 中的
class以及相关语法 -
链表和数组的区别,时间复杂度,通过指针的空间 来省下按顺序遍历的时间——一种空间换时间的性能优化策略
-
JS 实现链表的方法,有了
class这个语法后,和其他语言差不多了Node结点,存当前value以及与用于相邻结点相连的指针
-
复习
Symbol.iterator的使用场景 以及 生成器这个平时可能用的较少的知识点
🌊如果有所帮助,欢迎点赞关注,一起进步⛵