前端一面之 同步 vs 异步

异步 vs 同步

先看一下 下面的 demo

console.log(100)
setTimeout(function () {
    console.log(200)
}, 1000)
console.log(300) 

执行结果

100
300
200
console.log(100)
alert(200)  // 1秒钟之后点击确认
console.log(300) 

这俩到底有何区别?——
第一个示例中间的步骤根本没有阻塞接下来程序的运行,
而第二个示例却阻塞了后面程序的运行。
前面这种表现就叫做 异步(后面这个叫做 同步 ),
即不会阻塞后面程序的运行。

异步和单线程

JS 需要异步的根本原因是 JS 是单线程运行的,即在同一时间只能做一件事,不能“一心二用”。

一个 Ajax 请求由于网络比较慢,请求需要 5 秒钟。
如果是同步,这 5 秒钟页面就卡死在这里啥也干不了了。
异步的话,就好很多了,5 秒等待就等待了,其他事情不耽误做,
至于那 5 秒钟等待是网速太慢,不是因为 JS 的原因。

讲到单线程,我们再来看个真题:

讲解一下下面代码的执行结果

var a = true;
setTimeout(function(){
    a = false;
}, 100)
while(a){
    console.log('while执行了')
} 

这是一个很有迷惑性的题目,不少候选人认为100ms之后,
由于a变成了false,所以while就中止了,
实际不是这样,因为JS是单线程的,
所以进入while循环之后,
没有「时间」(线程)去跑定时器了,
所以这个代码跑起来是个死循环!

异步任务

异步任务是在主线程执行的同时,
通过回调函数或其他机制委托给其他线程或事件来处理的任务。
在执行异步任务时,主线程不会等待任务完成,
而是继续执行后续代码。包括:
在这里插入图片描述

console.log('Start');

setTimeout(() => {
  console.log('Timeout callback');
}, 1000);

console.log('End');

在上述例子中,setTimeout 是一个异步任务,
它会在1秒后将回调函数推入任务队列
而主线程不会等待这个1秒,
而是继续执行后面的 console.log(‘End’)。
当主线程的同步任务执行完成后
它会检查任务队列,
将异步任务的回调函数推入执行栈,最终输出 ‘Timeout callback’。

任务队列类型

任务队列分为宏任务队列(macrotask queue)微任务队列(microtask queue)两种。
JavaScript 引擎遵循事件循环的机制,
在执行完当前
宏任务
后,
会检查微任务队列,执行其中的微任务,
然后再取下一个宏任务执行。
这个过程不断循环,形成事件循环。

  1. 宏任务队列

所有同步任务
I/O 操作, 文件读写 数据库读写等等
setTimeout、setInterval
setImmediate(Node.js环境)
requestAnimationFrame
事件监听回调函数

  1. 微任务(Microtasks)是一些较小粒度、高优先级的任务,包括:

Promise的then、catch、finally
async/await中的代码
Generator函数
MutationObserver
process.nextTick(Node.js 环境)

任务执行过程

首先,必须要明确,在JavaScript中,所有任务都在主线程上执行
任务执行过程分为同步任务和异步任务两个阶段
异步任务的处理经历两个主要阶段
Event Table(事件表)和 Event Queue(事件队列)。
Event Table存储了宏任务的相关信息
包括事件监听和相应的回调函数。
当特定类型的事件发生时,对应的回调函数被添加到事件队列中,等待执行。
例如,你可以通过addEventListener来将事件监听器注册到事件表上:

document.addEventListener('click', function() {
  console.log('Hello world!');
});

微任务与 Event Queue 密切相关。
当执行栈中的代码执行完毕后,JavaScript引擎会不断地检查事件队列
如果队列不为空就将队列中的事件一个个取出,并执行相应的回调函数。

任务队列的执行流程可概括为:
同步任务在主线程排队执行,异步任务在事件队列排队等待进入主线程执行。
遇到宏任务则推进宏任务队列,遇到微任务则推进微任务队列。
执行宏任务,执行完毕后检查当前层的微任务并执行。
继续执行下一个宏任务,执行对应层次的微任务,直至全部执行完毕。

console.log(1);

setTimeout(() => {
    console.log(2);
}, 0);

console.log(3);

new Promise((resolve) => {
    console.log(4);
    resolve();
    console.log(5);
}).then(() => {
    console.log(6);
});

console.log(7);

执行顺序解析:1 => 3 => 4 => 5 => 7 => 6 => 2。

  1. 创建Promise实例是同步的,所以1、3、4、5、7是同步执行的。
  2. then方法是微任务,放入微任务队列中,在当前脚本执行完毕后立即发生。
  3. 同步任务执行完毕后,执行微任务队列中的微任务。
  4. 最后,setTimeout放入宏任务队列,按照先进先出的原则执行。

拓展
以下是一个使用同步I/O操作的例子,这种操作会阻塞整个程序的执行

const fs = require('fs');

function readFileSync() {
    // 打印"Start"到控制台
    console.log('Start');

    // 同步读取文件的内容
    // 在文件读取完成之前,这行代码会阻塞事件循环,其他任务无法执行
    const data = fs.readFileSync('/opt/xiaodou.txt', 'utf8');
    // 打印读取到的文件内容到控制台
    console.log('File data:', data);
    // 打印"End"到控制台
    console.log('End');
}

readFileSync();

在这个例子中,fs.readFileSync是一个同步的文件读取操作,它会阻塞事件循环,直到文件读取完成。在读取文件的过程中,其他任务无法执行,整个程序的执行被阻塞。
这段代码的执行顺序是严格线性的,整个程序会在读取文件时被阻塞,直到读取操作完成。
为了对比,可以看一下使用异步I/O操作的代码,这样的代码不会阻塞事件循环:

const fs = require('fs');

function readFileAsync() {
    console.log('Start');

    // 读取文件的异步操作,这不会阻塞事件循环
    fs.readFile('/opt/xiaodou.txt', 'utf8', (err, data) => {
        if (err) {
            console.error('Error reading file:', err);
            return;
        }
        console.log('File data:', data);
    });

    console.log('End');
}

readFileAsync();

在这个例子中,fs.readFile是一个异步的文件读取操作,
它不会阻塞事件循环,程序可以继续执行其他任务。
现在对JavaScript中的同步是不是有一些理解:
导致整个程序的执行被阻塞是同步只是暂停当前函数执行是异步
使用await关键字时,它会暂停当前异步函数的执行,等待异步操作完成,
但不会阻塞事件循环,其他任务可以继续执行。

我们来看一下阻塞整个程序的代码,不仅会阻塞当前函数,还会阻塞整个事件循环,
影响所有其他任务的执行,
咱们现在在上面的同步函数中增加一个定时器,那么你猜猜定时器还能定时执行吗?

const fs = require('fs');

function readFileSync() {
    console.log('Start');

    // 设置一个定时器,计划在1秒后执行
    setTimeout(() => {
        console.log('Timer executed');
    }, 1000);

    // 同步读取文件的操作,这会阻塞事件循环
    const data = fs.readFileSync('/opt/xiaodou.txt', 'utf8');
    console.log('File data:', data);

    console.log('End');
}

readFileSync();

在这个例子中,fs.readFileSync是一个同步操作,
它会阻塞事件循环,假设文件在1秒后无法读取完成,
那么定时器无法在预定的1秒后执行。
只有当文件读取操作完成后,定时器才会有机会执行。
图解
在这里插入图片描述

这是一个简化的时序图,事件循环的主要步骤如下:
开始事件循环:JavaScript引擎开始执行代码。
同步任务队列:引擎首先检查同步任务队列(实际上是调用栈中的任务),执行队列中的任务。
执行同步任务:引擎开始执行同步任务,直到队列为空。
检查微任务队列:同步任务执行完毕后,引擎检查微任务队列。
执行微任务:如果有微任务(如Promise的.then()回调),引擎会执行这些微任务。
检查宏任务队列:微任务执行完毕后,引擎检查宏任务队列。
执行宏任务:如果有宏任务(如setTimeout回调),引擎会执行这些宏任务。执行完毕后再次检查微任务队列。
等待新任务:如果宏任务执行完毕,引擎会等待新的任务(如新的异步操作或宏任务)。
循环:引擎会不断地循环执行上述步骤。
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/794313.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

再度领跑的极兔速递不存在估值缺口?

随着618的尘埃落地,线上零售的最强辅助们也将陆续公布最新业务量数据了。 整体上,上半年国内快递业的需求量维持稳增势。据国家邮政局监测数据,截至6月30日,今年上半年我国快递业务量突破800亿件,比2023年提前59天。其…

echarts解决数据差异过大的问题

问题描述 使用echarts折线图和柱状图展示数据时,如果数据差异值较大,会导致显示图形差异过大,图表不美观。 如这一组数据[2000000, 200, 0.1, 20, 0, -10, -3000],渲染出来的效果如下图: 可以看到由于最大值和最小值差…

netscaler LDAP+RADIUS传统的双因素认证方式(之一)

如果使用传统的双因素认证方式,可以通过在Citrix ADC (NetScaler) 13.1上配置Gateway Virtual Server来实现LDAP和RADIUS的双因素认证。当前配置方式,采用Cateway vServer两个Basic Authtication Policy方式实现,以下是详细步骤: …

【Python】使用PyQt6创建简单的登录界面

使用PyQt6创建简单的登录界面 介绍 PyQt6是Python绑定的Qt库,可以用来开发跨平台的桌面应用程序。本教程将介绍如何使用PyQt6创建一个简单的登录界面,包括用户名和密码输入框以及登录按钮。当用户点击登录按钮时,程序会验证输入的凭据并显示…

Matplotlib入门

#折线图用来表示数据的变化 plt.plot(x,y) #直方图用来统计连续性数据 无间隔 plt.hist(data数组,组数) #条形图用来统计离散的数组 并且反映其变化 有间隔 plt.bar(x,y,width 0.3) plt.barh(y,x,height 0.3) #散点图用来xy轴之间的联系 趋势 plt.scatter(x,y) #导入p…

Python大数据分析——K近邻模型(KNN)

Python大数据分析——K近邻模型 数学部分模型思想模型步骤距离度量指标欧氏距离曼哈顿距离余弦相似度 K值选择 代码部分函数示例1——知识掌握程度示例2——预测发电量 数学部分 模型思想 如图所示,模型的本质就是寻找k个最近样本,然后基于最近样本做“…

Qwen-7B推理教程【Python调用+web端部署】

前提 操作系统为Ubuntu22.04.4 LTS安装Anaconda(本人安装教程如下) Ubuntu22.04.4 LTS系统/安装Anaconda【GPU版】-CSDN博客 安装python3.9/pytorch/torchvision(本人安装教程如下) Ubuntu22.04.4系统/安装python3.9/pytorch/…

51单片机嵌入式开发:9、 STC89C52RC 操作LCD1602技巧

STC89C52RC 操作LCD1602技巧 1 代码工程2 LCD1602使用2.1 LCD1602字库2.2 巧妙使用sprintf2.3 光标显示2.4 写固定长度的字符2.5 所以引入固定长度写入方式: 3 LCD1602操作总结 1 代码工程 承接上文,在原有工程基础上,新建关于lcd1602的c和h…

前端项目本地的node_modules直接上传到服务器上无法直接使用(node-sasa模块报错)

跑 jekins任务的服务器不能连接外网下载依赖包,就将本地下载的 node_modules直接上传到服务器上,但是运行时node-sass模块报错了ERROR in Missing binding /root/component/node_modules/node-sass/vendor/linux-x64-48/binding.node >> 报错信息类…

深度学习设计模式之代理模式

文章目录 前言一、介绍二、详细分析1.核心组成2.实现步骤3.代码示例4.优缺点优点缺点 5.使用场景 总结 前言 代理模式是结构型设计模式,主要是为其他对象提供一种代理以控制对这个对象的访问。 一、介绍 代理模式(Proxy Pattern)是一种常用…

快速在springboot项目中应用EasyExcel

一、介绍 EasyExcel 是阿里巴巴开源的简化Excel文件读取和写入的开源库。主要的特点如下: 简单易用的API:EasyExcel提供简单API,隐藏处理Excel文件的底层细节。注解支持:支持使用注解将Java对象映射到Excel列,便于Ja…

《梦醒蝶飞:释放Excel函数与公式的力量》11.3 ISTEXT函数

第11章:信息函数 第三节 11.3 ISTEXT函数 11.3.1 简介 ISTEXT函数是Excel中的一个信息函数,用于检查指定单元格中的内容是否为文本。如果单元格内容是文本,则返回TRUE;否则返回FALSE。ISTEXT函数在数据验证、条件格式化和逻辑判…

【UE5.1】Chaos物理系统基础——06 子弹破坏石块

前言 在前面我们已经完成了场系统的制作(【UE5.1】Chaos物理系统基础——02 场系统的应用_ue5)以及子弹的制作(【UE5.1 角色练习】16-枪械射击——瞄准),现在我们准备实现的效果是,角色发射子弹来破坏石柱。…

如何利用扩散实现结构功能动态调控?

如何利用扩散实现结构功能动态调控? 利用扩散机制在生物打印结构内部创建特定空间梯度的方法,主要分为两个方面: 1. 形成形态发生素梯度 形态发生素的作用:形态发生素是能够诱导细胞响应的信号分子,常用于生物打印结…

探索GitHub上的两个革命性开源项目

在数字世界中,总有一些项目能够以其创新性和实用性脱颖而出,吸引全球开发者的目光。今天,我们将深入探索GitHub上的两个令人惊叹的开源项目:Comic Translate和GPTPDF,它们不仅改变了我们处理信息的方式,还极…

Debezium日常分享系列之:Debezium 3.0.0.Alpha1 Released

Debezium日常分享系列之:Debezium 3.0.0.Alpha1 Released 一、重大改变Java 和 Maven 要求已更改 二、新的特征和提高MongoDB 三、更多内容 Debezium 3 的第一个预发布版本 3.0.0.Alpha1。这个版本虽然比正常的预版本要小,但高度关注几个关键点&#xff…

docker中mysql设置lower_case_table_names配置的坑

前沿 今天在使用flowable流程框架的时候,遇到一个问题。需要配置MySQL数据库以实现表名大小写不敏感。本以为这是一个简单的任务,却耗费了我两个多小时的时间。 docker容器中修改配置,重启不成功 我们前提是容器中的mysql中已经有很多数据…

Web安全:SQL注入

一、SQL注入三要素 1、用户可以对输入的参数值进行修改。 2、后端不对用户输入的参数值进行严格过滤。 3、用户修改后的参数值可以被带入后端中成功执行,并返回一定结果。 二、SQL注入原理 简单来说,用户输入的值会被插入到SQL语句中,然后…

Milvus 核心设计(1) ---- 数据一致性的等级及使用场景

目录 背景 Milvus的数据一致性 设置数据一致性等级 等级类型 PACELC定理 level 详细解释 Strong Bounded staleness Session Eventually 总结 背景 分布式上的可扩展性是个比较重要的concept。Chroma 核心之前写过了,他的最大优势在于轻量级且好用。Milvus相对Ch…

tkinter-TinUI-xml实战(11)多功能TinUIxml编辑器

引言 在TinUIXml简易编辑器中,我们通过TinUI搭建了一个简易的针对TinUIXml布局的编辑器,基本掌握了TinUIXml布局和TinUIXml的导入与导出。现在,就在此基础上,对编辑器进行升级。 本次升级的功能: 更合理的xml编辑与…