[源码阅读]yocto-queue源码

116 阅读4分钟

前言

持续创作,加速成长!这是我参与「掘金日新计划 · 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:类似于push
  • dequeue:类似于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里面的item

    • value表示当前节点的值
    • 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()函数

    • headundefined
    • tailundefined
    • size0
  • 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)

总结收获

了解到如何使用链表来实现一个队列

困惑:我还是不太了解ava这种测试,或者说我不知道如何调ava,我感觉他就是在判断,实际输出和预期输出是不是相同?