【若川视野 x 源码共读】第32期 | yocto-queue 队列 链表

132 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

1. 学习目标

  • 学习链表和数组的区别,时间复杂度
  • yocto-queue 的适用场景?
  • 对 Symbol.iterator 的理解

2. 参考资料

3. 学习过程

1. 数组和链表的区别

数组和链表是两种不同的数据存储方式。

image.png

  • 数组是一组具有相同数据类型的变量的集合,这些变量称之为集合的元素,每个元素都有一个编号,称之为下标,可以通过下标来区别并访问数组元素,数组元素的个数叫做数据的长度。
  • 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,链表的特性是在中间任意位置插入和删除元素都非常快,不需要移动其它元素。

image.png

image.png

2. yocto-queue 的适用场景?

You should use this package instead of an array if you do a lot of Array#push() and Array#shift() on large arrays, since Array#shift() has linear time complexity O(n)  while Queue#dequeue() has constant time complexity O(1) . That makes a huge difference for large arrays.

queue is an ordered list of elements where an element is inserted at the end of the queue and is removed from the front of the queue. A queue works based on the first-in, first-out (FIFO) principle.

翻译如下:

  • 你应该使用这个包,当你要对一个大数组做频繁的push和shift操作时,shift方法是线性的时间复杂度的,也就是O(n)而Queue#dequeue方法是固定的时间复杂度,也就是O(1),这时使用队列就很有意义了。

  • 队列就是一个有序的列表,添加元素向队列部尾部添加,移除元素从头部移除,它遵循先进先出的原则,也就是(FIFO)原则。

3. yocto-queue 如何使用

npm install yocto-queue
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());
//=> '🌈'

4. yocto-queue 的源码

/*
How it works:
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
*/

class Node {
	value;
	next;

	constructor(value) {
		this.value = value;
	}
}

export default class Queue {
	#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;
		}
	}
}

5. 对 Symbol.iterator 的理解

Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用。 在 yocto-queue 源码中,

* [Symbol.iterator]() {
    let current = this.#head;

    while (current) {
            yield current.value;
            current = current.next;
    }
}

通过上述实现,我们可以通过for...of获取到队列中的值

const queue = new Queue();
queue.enqueue('🦄');
queue.enqueue('🌈');
console.log([...queue]); // 输出:['🦄', '🌈']

总结

先做一个简单的了解,暂时还没有这种场景的需求。

参考笔记: