【源码共读】第32期 | yocto-queue 队列 链表

297 阅读6分钟

前言

学习目标

  1. 学习当前库的使用场景和实现思想
  2. 学习 Symbol.iterator 的使用场景
  3. 学习链表、队列和数组的区别,时间复杂度
  4. class私有属性的应用

阅读思路

  1. 克隆下来项目
  2. 先了解项目目录
    • readme: 项目使用相关的
    • package.json项目开发配置、运行相关
    • ...
  3. 了解test.js文件,通过测试用例看库重要的方法和属性如何使用和测试
  4. 阅读源码实现
  5. 针对不了解的知识点查资料学习...

yocto-queue

  • 使用场景
    • 针对要处理数组中大量数据的时候,当用数组的shift和push处理大量数组数据的时候,可以使用这个库代替,优势在于减少了减少了时间复杂度,数组时间复杂度是O(n),而链表是O(1)
  • 相关概念
    • 队列:先进先出 O(1)
    • 数组:O(n)
    • 链表:一种动态数据结构,由多个结点组成。每个节点包含一个值和一个指向下一个结点的指针;没有固定的大小限制,可以随时根据需要添加或删除元素。
    • 与数组的区别:没有固定的大小限制,可以随时根据需要添加或删除元素;没有连续内存空间的要求,比数组更灵活
  • test.js了解
    • 三个方法+一个属性+一个特征:enqueue、denqueue、clear + size + iterable迭代器特征
    • 从new Queue()看队列的实现应该是一个Queue类,里面包含属性、方法、可以解构成数组
    • 关于最后一个测试用例iterable,什么是iterable?怎样具有iterable特性?
      • 从测试用例中看出可迭代特性,具备展开语法,除此之外还可以用for...of、yield*、结构赋值等
  • 代码
    • 本质: 每个链表节点包含一个当前值和指向下一个节点的指针,比较巧妙的是,利用迭代器的value和next,作为值和指针,结合每次新增数据到队列的时候实例化的Node节点形成和当前的next属性建立起链表连接的方式,并且还不需要连续的空间和固定大小,因为每个实例化都建立一个引用,实际值是存储在堆中,而每次通过生成器可以把下一个链表结点的赋值给当前节点;当指针指向下一个的时候,上一个不用的会在指定时间被js的垃圾回收机回收;
    • while的使用场景:
      • 一般情况下for和while都可以混用;但是在环环嵌套的情况下,似乎while比for更适合,for针对扁平化,在循环之前就有明确的循环次数的更适合;
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); // 每次new的实例是不相同的

		if (this.#head) {
			this.#tail.next = node; //为了和head的next挂上钩
			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;
		}
	}
}

Symbol.iterator

  • 什么是iterator?
    • 迭代器(iterator)是一种设计模式,它提供了一种有效的方法来遍历一个容器对象(例如数组、链表、树等)中的元素,而不暴露容器对象的内部结构。
  • 迭代器规范是什么?
    • Iterator Protocol 规范定义了两个重要的属性和一个方法:
    • [Symbol.iterator] 属性:一个无参数函数,它返回迭代器对象本身。这个属性使得对象具有可迭代性,即可以使用 for...of 循环遍历对象中的元素。 next() 方法:一个无参数函数,它返回一个包含两个属性的对象:value 和 done。value 表示当前迭代器返回的值,done 表示迭代器是否已经完成迭代。每次调用 next() 方法,迭代器会返回下一个数据项,直到 done 为 true,表示迭代结束。
  • Symbol.iterator是专门为访问一个对象的内部迭代器(Iterators)而制作的特殊用途的符号。因此你可以用它来检索一个遍历数组对象的函数

iterator.png

  • 可迭代对象特点:
    • 可以访问[Symbol.iterator]方法
    • 调用[Symbol.iterator]方法一定返回一个迭代器
    • 可以调用返回的迭代器的next方法,对对象进行迭代
  • 迭代器和数组的区别?
    • 数据类型:数组是一种数据结构,而迭代器是一种数据访问方式,迭代器可以用于迭代各种不同的数据类型,而不仅仅是数组。
    • 数据访问方式: 数组通过索引,迭代器只能通过next()方法逐个访问元素
    • 存储方式:数组存储在一个连续的内存中,迭代器在需要的时候动态生成下一个元素,可用于遍历非常大的或无限的数据集合;
    • 可变性:数组是可变的(增删改),迭代器只读,不能直接修改其中元素
    • 语法:迭代器需要for...of 或next()来访问和操作元素,数组通过下标或数组方法
    • 总结:迭代器是更通用的数据访问方式,可用于遍历各种不同类型的数据集合,数组是一种特定的数据结构,适合存储和处理有序的数据集合;

class类私有属性

  • #head; #tail; #size;是class的私有属性定义;
  • class的私有化属性和私有化方法如何定义?
  • 私有属性与公共属性的定义方式几乎是一样的,只是需要在属性名称前面添加#符号:
  • 为什么 JS 不能学习其他语言,使用private来定义私有属性和私有方法?为什么要使用奇怪的#符号?
    • 私用属性和公共属性的引用方式是不同的;
    • 私有属性与公共属性的引用方式一样的话,会导致我们每次都需要去检查属性是公共的还是私有的,这会造成严重的性能问题。

总结

  1. 对于大数据的处理除了数组,又了解到了一种更好的链表队列方式的思想;
  2. 结合场景深入理解了Symbol.iterator的使用场景;
  3. 数组、链表、队列对比学习,学到了链表和队列结合使用的场景;
  4. 学到了class类私有属性的使用场景;
  5. 以上仅为个人看法,如有问题欢迎大家在评论区帮助修正~

参考链接