浏览器事件环(EventLoop)
1. 基础知识
1. js语言特点
1. js语言是单线程语言,主线程是单线程。如UI渲染,脚本加载是主线程任务。
2. js语言采用事件循环(EventLoop)机制。
2. 同步任务:
不被引擎挂起,在主线程等待执行的, 按照顺序执行的任务。主线程任务,也算宏任务。
如: UI渲染,js执行。同一时间只能执行一个。
3. 异步任务:
一般比较消耗时间,被引擎挂起,进入任务队列,等待主线程全部执行完才能执行的任务。
异步任务分两种:
1. 微任务:Promise的then方法是微任务;微任务在主线程同步任务结束后执行。
2.宏任务: setTimeout, setInterval, ajax, click事件等。异步任务的宏任务在下一个事件循环开始执行。
上面的任务都会开辟新的线程(非主线程,单独的线程)
5. 执行上下文和作用域
作用域:
js中是静态作用域。
分为全局作用域,函数作用域,块级作用域。
作用域一般在定义和声明声明阶段就已经确定了。
变量查找会根据作用域链依次查找。
执行上下文:
执行上下文是代码执行的时候,产生的执行环境,执行栈。也叫做调用栈。
当同步任务运行时,进入执行栈,运行完成销毁调用栈。
对于递归调用的函数来说,最后被调用的是最内层函数,那么它在调用栈的最后位置。
最先执行的也是最内层函数,执行完后,销毁调用栈所处的上下文环境。
6. 栈和队列
栈: 栈是一种“后入先出”的数据结构。
同步任务(主线程任务)进入执行栈,产生执行上下文,运行完销毁运行时所在的执行环境。
队列:队列是一种“先入先出”的数据结构。
js执行过程过程中,可能会出现诸如setTimeout等WebAPI产生的异步任务。
它们满足条件后的回调函数会进入队列。
2. 事件循环机制
主线程代码执行,同步任务进入执行栈,异步任务进入任务队列。
其中主执行栈中的任务其实也是宏任务。
有一个特殊函数window.requestAnimationFrame的回调函数在宏任务和微任务之间运行。

执行顺序:
1. 先清空主执行栈中的任务。
2.js引擎查看微任务队列有没有微任务,有的话,清空微任务队列。
3. 查看宏任务队列有没有任务,如果有,取出第一个,将其加入执行栈。
执行完成后,查看微任务队列是否有任务,有的话,清空微任务队列。
4. 重复3,直到清空所有的任务。
示例:
function a(){
console.log('a');
function b() {
console.log('b');
}
b();
}
a();
setTimeout(function() {
console.log('setTimeout1');
Promise.resolve().then(() => {
console.log('then1')
}).then(() => {
console.log('then2')
}).then(() => {
console.log('then3')
})
},1000)
setTimeout(function() {
console.log('setTimeout2')
},500)
console.log('end');
// 运行结果如下
a // 主执行栈
b // 主执行栈
end // 清空主执行栈
// 500ms后setTimeout2加入宏任务队列,主执行栈为空,将其加入主执行栈
setTimeout2 // 清空主执行栈
// 1000ms后setTimeout1加入宏任务队列,主执行栈为空,将其加入主执行栈
setTimeout1 // 清空主执行栈
// 将then1加入微任务队列
then1// 清空微任务队列
// 将then2加入微任务队列
then2 // 清空微任务队列
// 将then3加入微任务队列
then3 // 清空微任务队列
3. 异步操作(不等于异步任务)
异步操作只是手动的实现改变函数的调用顺序。不会进入任务队列。
常见的异步操作是回调函数/事件监听模式/发布订阅模式;
异步操作又可以分为串行异步/并行异步/串并行结合异步;
1. 回调函数
普通回调函数只是改变了函数的执行顺序,并不会异步任务,也不会进入任务队列。
function a(callback){
callback(); // 回调函数
}
function b(){}
a(b); // b是a的回调函数
2. 事件监听
事件监听函数也是指定任务的执行顺序,不会进入任务队列。
<button>click</button>
<script>
function a() {
let button = document.querySelector('button');
button.click(); //函数a触发监听事件
}
function b() {// 在a执行触发后,再执行
console.log("triggered by a");
// 最后需要取消监听
}
document.addEventListener('click', b);
</script>
3.发布订阅
任务执行后向“消息中心”发布(publish)消息, 其他任务订阅(subscribe)消息。
// 伪代码
function a() {
XXX.publish('done'); // 发布消息;XXX表示消息中心
}
function b() {}
// a发布后引发b执行
XXX.subscribe('done', b);// b订阅消息 // 如果b执行完,需要取消订阅!!!
XXX.unSubscribe('done', f2);
4. 串行异步--多个异步操作
有两种实现方式:
1)回调函数--逐级嵌套
下次执行等待上次执行结束,彼此之间有耦合。setTimeout是异步任务,会进入任务队列。
示例: 执行6次回调后执行final函数
<script>
function asyncFn(arg, callback) {
console.log("参数:", arg);
setTimeout(function(){
callback(arg*2);
}, 1000);
}
function final(value) {
console.log("the final value equals ", value)
}
asyncFn(1, function(result) {
asyncFn(result, function(result1) {
asyncFn(result1, function(result2) {
asyncFn(result2, function(result3){
asyncFn(result3, function(result4){
asyncFn(result4, function(result5){
final(result5);
})
})
})
})
})
})
console.log('00')
</script>
callback
执行结果如下:
参数:1
00
参数:2
参数:4
参数:8
参数:16
参数:32
the final value equals 64
2)自定义一个函数(递归函数)
<script>
let items = [...new Array(6)].fill(1);
function asyncFn(arg, callback) {
console.log("参数:", arg);
setTimeout(function(){
callback(arg*2);
}, 1000);
}
function final(value) {
console.log("the final value equals ", value)
}
// 串行执行异步操作
var paramValue = 1; // 初始化参数
function serious(items) {
if (items.shift()) {
asyncFn(paramValue, function(result) {
paramValue = result;
serious(items);
})
} else {
final(paramValue)
}
}
serious(items);
console.log('00')
</script>
serious
执行结果和上面一样。
两种方式的执行结果都是6秒。
5. 并行执行--多个异步
并行执行的异步函数,彼此之间不存在执行先后顺序问题,没有耦合。
几个异步函数同时执行,等待6个异步操作都执行完成后,执行final。
<script>
let items = [...new Array(6)].fill(1);
function asyncFn(arg, callback) {
console.log("参数:", arg);
setTimeout(function(){
callback(arg*2);
}, 1000);
}
function final(value) {
console.log("the final value equals ", value)
}
var result=[];
items.forEach((item, index) => [
asyncFn(index+1, function(res) {
result.push(res);
if (result.length === 6) { // 判断6个异步都执行完成
final(res);
}
})
])
</script>
执行结果如下:
参数:1
参数:2
参数:3
参数:4
参数:5
参数:6
the final value equals 12
并行执行完成耗时1秒
6. 串并行结合
串行异步操作耗时较多;并行效率,但是占用资源多,如果并行操作过多,很容易耗尽系统资源。
可以通过限制并行的个数来提高效率的同时,避免过分占用资源。
示例: 保证每次同时执行的异步任务是3个,等6个异步操作执行完成后执行final
<script>
function asyncFn(arg, callback) {
console.log("参数:", arg);
setTimeout(function(){
callback(arg*2);
}, 1000);
}
function final(value) {
console.log("the final value equals ", value)
}
let items = Array.from(new Array(6), (_,index)=> index+1);
let result=[];
let limit = 3; //最大3个
let running = 0; // 正在执行的个数
function both() {
while(running < limit && items.length > 0){
let item = items.shift();
asyncFn(item, function(result) {
running--;
if (items.length > 0) {
both();
} else if (running ===0 ) { // 执行完毕
final(result)
}
})
running++;
}
}
both();
</script>
both
执行结果同上。
耗时2秒
浏览器事件环(EventLoop)的更多相关文章
- 我已经迷失在事件环(event-loop)中了【Nodejs篇】
我第一次看到他事件环(event-loop)的时候,我是一脸懵,这是什么鬼,是什么循环吗,为什么event还要loop,不是都是一次性的吗? 浏览器中和nodejs环境中的事件环是有一些区别的,这里我 ...
- NodeJS事件环
1. 执行顺序说明 1. 清空主执行栈 2. 清空微任务队列 3. 运行一个timer队列的回调函数,询问微任务队列,如果有回调函数,清空. 4. 循环第3步,直到清空timer队列 5. 进入pol ...
- 麻烦把JS的事件环给我安排一下
上次大家跟我吃饱喝足又撸了一遍PromiseA+,想必大家肯定满脑子想的都是西瓜可乐...... 什么西瓜可乐!明明是Promise! 呃,清醒一下,今天大家搬个小板凳,听我说说JS中比较有意思的事件 ...
- 深入理解javascript中的事件循环event-loop
前面的话 本文将详细介绍javascript中的事件循环event-loop 线程 javascript是单线程的语言,也就是说,同一个时间只能做一件事.而这个单线程的特性,与它的用途有关,作为浏览器 ...
- 浏览器事件循环 & nodejs事件循环
第1篇:如何理解EventLoop——宏任务和微任务篇 宏任务(MacroTask)引入 在 JS 中,大部分的任务都是在主线程上执行,常见的任务有: 渲染事件 用户交互事件 js脚本执行 网络请求. ...
- 事件循环(EventLoop)的学习总结
前言 在学习eventloop之前,我们需要复习一下js的单线程和异步.虽说js是单线程的,但是在浏览器和Node中都做了相应的处理.如浏览器中的web workers(工作线程),Node中的chi ...
- javascript事件环微任务和宏任务队列原理
哈喽!大家好!我是木瓜太香,我又来嘞,今天来说说前端面试中经常别问到的 JS 事件环问题. JS 事件环 JS 程序的运行是离不开事件环机制的,这个机制保证在发生某些事情的时候我们有机会执行一个我们事 ...
- JavaScript单线程和浏览器事件循环简述
JavaScript单线程 在上篇博客<Promise的前世今生和妙用技巧>的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型.应很多网友的回复,在这篇文章中将继续展 ...
- JS 关闭 页面 浏览器 事件
JS监听关闭浏览器事件关键字: js监听关闭浏览器事件Onunload与OnbeforeunloadOnunload,onbeforeunload都是在刷新或关闭时调用,可以在<script&g ...
随机推荐
- 利用Python进行数据分析_Pandas_汇总和计算描述统计
申明:本系列文章是自己在学习<利用Python进行数据分析>这本书的过程中,为了方便后期自己巩固知识而整理. In [1]: import numpy as np In [2]: impo ...
- Python 【模块】
A 什么是模块 最高级别的程序组织单元(模块什么都能封装) 模块中,我们不但可以直接存放变量,还能存放函数,还能存放类 定义变量需要用赋值语句,封装函数需要用def语句,封装类需要用class语句,但 ...
- 利用sort对结构体进行排序
我定义了一个学生类型的结构体来演示sort排序对结构体排序的用法 具体用法看代码 #include<iostream> #include<string> #include< ...
- Skip List(跳跃表)原理详解与实现
ref : https://dsqiu.iteye.com/blog/1705530 本文内容框架: §1 Skip List 介绍 §2 Skip List 定义以及构造步骤 §3 Skip ...
- Session共享问题---理论
随着网站访问量增加,初期的一台服务器已经完全不能支持业务,这个时候我们就需要增加服务器设备,来抗住请求的增量,如下所示: 负载均衡的目的本来就是要为了平均分配请求,所以没有固定第一次访问和第二次访问的 ...
- delete删除数据造成归档日志增加,操作系统空间不足导致数据库hang住
业务需求,对日志表历史数据进行清理.历史表均很大,使用delete 操作删除90天前的数据. 第一部分:快速删除数据 SQL> alter table CC.F_LOG parallel ; S ...
- (二十四)JSP标签之基本标签(<jsp:标签名>)
一.常用标签 1.1 jsp中标签一共有8中,其中常用的有6中,本文将介绍这6种常用的标签. 1.2 6种标签 1. <jsp:include> <jsp:include>标签 ...
- (七)mybatis之多对一关系(复杂)
一.需求分析 需求:查询所有消费者信息,关联查询订单及商品信息,订单明细信息中关联查询查商品信息. 分析:一个消费者有多条订单,一条订单只有一个消费者但是有多条订单明细,一条订单明细只有一个商品信息. ...
- 【es6】将2个数组合并为一个数组
//第一种 一个数组中的值为key 一个数组中的值为value let arr1 = ['内存','颜色','尺寸']; let arr2 = [1,2,3]; let temp = arr1.map ...
- Python处理session最简单的方法
前言: 不管是在做接口自动化还是在做UI自动化,测试人员遇到的第一个问题都是卡在登录上. 那是因为在执行登录的时候,服务端会有一种叫做session的会话机制. 一个很简单的例子: 在做功能测试的时候 ...