前言
-
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
-
这是源码共读的第32期,链接:juejin.cn/post/709076… 。
学习目标
- 学习当前库的使用场景和实现思想
- 学习 Symbol.iterator 的使用场景
- 学习链表、队列和数组的区别,时间复杂度
- class私有属性的应用
阅读思路
- 克隆下来项目
- 先了解项目目录
- readme: 项目使用相关的
- package.json项目开发配置、运行相关
- ...
- 了解test.js文件,通过测试用例看库重要的方法和属性如何使用和测试
- 阅读源码实现
- 针对不了解的知识点查资料学习...
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)而制作的特殊用途的符号。因此你可以用它来检索一个遍历数组对象的函数
- 可迭代对象特点:
- 可以访问[Symbol.iterator]方法
- 调用[Symbol.iterator]方法一定返回一个迭代器
- 可以调用返回的迭代器的next方法,对对象进行迭代
- 迭代器和数组的区别?
- 数据类型:数组是一种数据结构,而迭代器是一种数据访问方式,迭代器可以用于迭代各种不同的数据类型,而不仅仅是数组。
- 数据访问方式: 数组通过索引,迭代器只能通过next()方法逐个访问元素
- 存储方式:数组存储在一个连续的内存中,迭代器在需要的时候动态生成下一个元素,可用于遍历非常大的或无限的数据集合;
- 可变性:数组是可变的(增删改),迭代器只读,不能直接修改其中元素
- 语法:迭代器需要for...of 或next()来访问和操作元素,数组通过下标或数组方法
- 总结:迭代器是更通用的数据访问方式,可用于遍历各种不同类型的数据集合,数组是一种特定的数据结构,适合存储和处理有序的数据集合;
class类私有属性
- #head; #tail; #size;是class的私有属性定义;
- class的私有化属性和私有化方法如何定义?
- 私有属性与公共属性的定义方式几乎是一样的,只是需要在属性名称前面添加#符号:
- 为什么 JS 不能学习其他语言,使用private来定义私有属性和私有方法?为什么要使用奇怪的#符号?
- 私用属性和公共属性的引用方式是不同的;
- 私有属性与公共属性的引用方式一样的话,会导致我们每次都需要去检查属性是公共的还是私有的,这会造成严重的性能问题。
总结
- 对于大数据的处理除了数组,又了解到了一种更好的链表队列方式的思想;
- 结合场景深入理解了Symbol.iterator的使用场景;
- 数组、链表、队列对比学习,学到了链表和队列结合使用的场景;
- 学到了class类私有属性的使用场景;
- 以上仅为个人看法,如有问题欢迎大家在评论区帮助修正~
参考链接
- class私有属性为什么用#相关链接: wap.sciencenet.cn/home.php?mo…