- 队列
- 队列基本操作
- 入队(enqueue):将元素添加到队列的尾部。
- 出队(dequeue):从队列的头部移除元素。
- 队首(front):获取队列头部的元素,但不移除它。
- 队尾(rear):获取队列尾部的元素,但不移除它。
- 判空(isEmpty):判断队列是否为空。
- 大小(size):获取队列中元素的数量。
- 队列基本操作
- JvaScript 任务队列
- 为什么 JavaScript 是单线程?
- 事件循环(Event Loop)与消息队列的关系:
- 宏任务队列(Macrotask Queue):
- 微任务队列(Microtask Queue):
- 事件循环:
- 事件循环的基本流程如下:
- 关于栈的前端算法题
- 最近的请求次数
队列
队列(Queue)是一种常见的数据结构,它类似于排队的形式,遵循 先进先出(FIFO) 的原则。在队列中,元素在尾部添加,而在头部移除。这就类似于排队时,先来的人先服务,后来的人排在后面等待服务。
队列,和栈一样,也是一种对数据的"存"和"取"有严格要求的线性存储结构。
与栈结构不同的是,队列的两端都"开口",要求数据 只能从一端进,从另一端出 ,如图 1 所示:
通常,称进数据的一端为 "队尾",出数据的一端为 "队头",数据元素进队列的过程称为 "入队",出队列的过程称为 "出队"。
不仅如此,队列中数据的进出要遵循 “先进先出” 的原则,即最先进队列的数据元素,同样要最先出队列。
拿图 1 中的队列来说,从数据在队列中的存储状态可以分析出,元素 1 最先进队,其次是元素 2,最后是元素 3。
此时如果将元素 3 出队,根据队列 “先进先出” 的特点,元素 1 要先出队列,元素 2 再出队列,最后才轮到元素 3 出队列。
栈和队列不要混淆,栈结构是一端封口,特点是"先进后出";而队列的两端全是开口,特点是"先进先出"。
因此,数据从表的一端进,从另一端出,且遵循 “先进先出” 原则的线性存储结构就是队列。
队列基本操作
队列通常包括以下几种基本操作:
入队(enqueue):将元素添加到队列的尾部。
出队(dequeue):从队列的头部移除元素。
队首(front):获取队列头部的元素,但不移除它。
队尾(rear):获取队列尾部的元素,但不移除它。
判空(isEmpty):判断队列是否为空。
大小(size):获取队列中元素的数量。
队列常常用于实现广度优先搜索(BFS)、线程池、缓存等应用场景,是计算机科学中非常重要的数据结构之一。
更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
JvaScript 任务队列
为什么 JavaScript 是单线程?
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
那么,为什么JavaScript 不能有多个线程呢 ?这样能提高效率啊。
JavaScript 的单线程,与它的用途有关。
作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
比如,假定JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
一种非阻塞的事件循环机制就产生了:
- 消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。
- 事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。
在JavaScript中,消息队列是事件驱动架构的核心部分,用于管理异步任务的执行顺序。
由于JavaScript在浏览器环境中被设计为单线程(即同一时间只能执行一个任务),为了处理异步操作(如网络请求、定时器、DOM事件等),它采用了一种非阻塞的事件循环机制。
事件循环(Event Loop)与消息队列的关系:
宏任务队列(Macrotask Queue):
- 也称为Task Queue或Job Queue,包含那些需要在 主线程空闲时执行的任务。
- 宏任务包括:
- 脚本整体代码、
- setTimeout、
- setInterval、
- I/O、
- UI渲染、
- AJAX回调函数等。
微任务队列(Microtask Queue):
- 又称Promise Job Queue,优先级高于宏任务队列。
- 微任务包括:
- Promise.then/catch/finally、
- MutationObserver、
- process.nextTick(Node.js环境)、
- async/await(内部基于Promise)等。
事件循环:
事件循环的基本流程如下:
-
初始阶段:JS引擎开始执行全局脚本(这也是一个宏任务)。
-
同步执行:从上到下逐行执行代码,遇到异步操作(如
fetch
请求或setTimeout
调用)则不会阻塞执行流,而是将回调函数放入相应的消息队列。 -
检查微任务队列:当前宏任务执行完毕后,立即查看微任务队列是否有待执行的任务。如果有,则按先进先出(FIFO)原则执行所有微任务。
-
渲染阶段:在执行完所有的微任务之后,浏览器会进行一次渲染更新(如果有必要的话)。
-
检查宏任务队列:然后轮询宏任务队列,取出第一个宏任务来执行。
-
重复循环:以上步骤不断循环,形成了所谓的“事件循环”。
因此,在JavaScript中,消息队列不是单一的一个队列,而是至少包含两个层次的任务队列(宏任务队列和微任务队列)。
这种结构确保了即使在单线程环境下,也能实现异步编程和高效的并发处理能力。
这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。
关于栈的前端算法题
最近的请求次数
写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请你实现 RecentCounter 类:
- RecentCounter() 初始化计数器,请求数为 0 。
- int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
示例 1:
输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]
解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3
提示:
1 <= t <= 109
保证每次对 ping 调用所使用的 t 值都 严格递增
至多调用 ping 方法 10^4 次
实现代码
var RecentCounter = function () {
// 新建一个队列,用于存储数据
this.stack = []
}
/**
* @param {number} t
* @return {number}
*/
RecentCounter.prototype.ping = function (t) {
// 把数据存储在队列中
this.stack.push(t)
while (this.stack[0] < t - 3000) {
// 如果满足条件,则出队
this.stack.shift(t)
}
return this.stack.length
};
/**
* Your RecentCounter object will be instantiated and called as such:
* var obj = new RecentCounter()
* var param_1 = obj.ping(t)
*/
参考文档
- https://c.biancheng.net/view/3352.html