【若川视野 x 源码共读】第32期| yocto-queue
本文参加了由公众号@若川视野发起的每周源码共读活动, 点击了解详情一起参与。
❑ 计算机内存犹如一大堆抽屉。
❑ 需要存储多个元素时,可使用数组或链表。
❑ 数组的元素都在一起。
❑ 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。
❑ 数组的读取速度很快。
❑ 链表的插入和删除速度很快。
场景:和室友去食堂吃饭,第一打完,后面都需要挪动位置,去填补空缺,当有人插队得时候,后面所有人都需要往后挪动位置,如果数据量很大,在软件开发中就是常谈得性能问题,需要一些手段进行优化。
线性表和链表的不同
- 链表比较方便插入和删除操作,线性表中插入一个元素,那么后面的元素地址都要往后移,删除同理。而链表只需要修改结点中的指针信息,不需要修改结点地址。
- 线性表内容可以随机访问,因为是连续的内存单元,地址连续所以在这个区间内可以进行随机,链表存储地址不一定时连续的,所以不能随机访问(因为知道第一个一直往后去找值
head.next.next.xxx);查找速度由于存储地址原因也快于链表。
此时可能你会问:JavaScript 当中的数组也是动态的呀,也可以随意添加和删除元素呀。确实如此,不过 JavaScript 提供的原生方法虽然用起来方便,但是性能很低。
yocto-queue介绍
定义:yocto-queue是一个小型队列数据结构
适用场景:用yocto-queue代替数组,当你遇到在大数组上执行大量的push和shift方法时。原因是shift方法的执行是线性复杂度的,而yocto-queue入队操作是常数复杂度。
enqueue():向队列尾部添加新元素dequeue():移除队列的第一项clear():清除队列里的所有元素size():返回队列里元素的数量
/*
它是如何工作的:
`this.#head`是一个`Node`的实例,它记录了它的当前值,并嵌套了另一个`Node`的实例,它记录了它之后的值。当一个值被提供给`.enqueue()`时,代码需要遍历`this.#head`,不断深入以找到最后的值。然而,遍历每一个单项是很慢的。这个问题的解决方法是将最后一个值的引用保存为`this.#tail`,这样它就可以引用它来添加新的值。
*/
class Node {
value;
next;
// 链表 next 存储后继节点的地址。
constructor(value) {
this.value = value;
}
}
export default class Queue {
#head; // 头指针
#tail; // 尾指针
#size; // 数组长度
constructor() {
// new Queue() 将会重置
this.clear();
}
enqueue(value) {
const node = new Node(value);
if (this.#head) {
// 存在头指针时 将尾指针 #tail.next #tail本身 都赋值
this.#tail.next = node;
this.#tail = node;
} else {
// 第一次新增时候 头尾指针指向同一个值
this.#head = node;
this.#tail = node;
}
// 长度+1
this.#size++;
}
dequeue() {
const current = this.#head;
if (!current) {
// 没有就返回 undefined
return;
}
this.#head = this.#head.next;
this.#size--;
return current.value;
}
clear() {
// 可以使其触发 JS 的 GC
this.#head = undefined;
this.#tail = undefined;
this.#size = 0;
}
get size() {
return this.#size;
}
// 这里是给队列加上迭代器,并且用生成器去替代
* [Symbol.iterator]() {
let current = this.#head;
// 判断头部指针是否还存在
while (current) {
// yield后面的值是当前迭代返回的值
yield current.value;
// 头部指针向后移一位
current = current.next;
}
}
}
Symbol.iterator
可能我们并不常见,但是其实我们有天天接触
let obj = { x: 1, y: 2, z: 3 }
console.log([...obj]) // TypeError: obj is not iterable
Symbol.iterator为每一个对象定义了默认的迭代器。该迭代器可以被 for…of 循环使用。
*
第一次看到 * 还没明白什么语法,看了其他笔记分享才突然发现这是一个生成器。
调用生成器函数会产生一个生成器对象,其一开始处于暂停状态,该对象也实现了Iterator接口,通过调next()使其转为开始或者恢复执行状态。生成器函数在遇到yield关键字前会正常执行,遇到该关键字后,执行会停止,函数作用域的状态被保留 —— 有点像函数的 中间返回语句,它能让函数返回一个值出去,但是函数仍能继续执行。随后通过在生成器对象上调用next方法恢复执行。
像 await async 就是 promise + iterator 来实现的
\