前端面试题整理-前端异步编程

ops/2024/12/23 23:54:53/

1. 进程、线程、协程的区别

在并发编程领域,进程、线程和协程是三个核心概念,它们在资源管理、调度和执行上有着本质的不同。

首先,进程是操作系统进行资源分配和调度的独立单位(资源分配基本单位),每个进程拥有自己的内存空间,这使得进程间相互隔离,从而提高了系统的稳定性。然而,这种隔离也带来了进程间通信的复杂性,需要通过特定的机制如管道、共享内存等来实现。进程的创建和销毁涉及到系统资源的分配和回收,因此开销相对较大。适用于需要高隔离性和独立资源的应用,比如浏览器的多进程架构,每个标签页运行在独立的进程中。

接着,线程作为进程的执行单元(CPU调度基本单位),共享进程的资源,包括内存空间,这使得线程间的通信更为简便。线程的创建和销毁开销相对较小,但仍然需要操作系统的调度,因此线程的并发执行能力受到操作系统调度策略和硬件资源的限制。适用于需要高效并发处理的场景,如Web服务器的请求处理、前端的异步操作(如Web Workers)。

最后,协程是一种更轻量级的并发机制,它允许程序在不同的执行点挂起和恢复,通常由程序内部进行调度,而不是依赖操作系统。协程特别适合处理I/O密集型任务,因为它们可以在等待I/O操作时挂起,从而提高资源利用率。协程的创建和销毁开销非常小,这使得它们在处理大量并发任务时非常有用,尤其是在Python、Go等语言中,协程被广泛用于提高并发性能。适用于需要大量并发但不需要多线程的场景,如JavaScript中的异步编程(Promise、async/await)、Python的异步I/O。

2. 说说他们的通信方式

进程、线程和协程之间的通信方式各有特点,下面分别介绍它们的通信机制:

(1) 进程通信(Inter-Process Communication, IPC)

  • 管道(Pipes):允许一个进程的输出成为另一个进程的输入。常用于父子进程间的单向或双向通信,简单且高效。
  • 命名管道(Named Pipes):类似于管道,但是它们在文件系统中有名字,可以跨会话使用。
  • 消息队列(Message Queues):允许进程以消息的形式交换数据,消息被存储在队列中直到被接收。支持异步通信,允许消息在进程间传递,适合复杂的消息传递场景。
  • 信号(Signals):一种由操作系统提供的异步通信机制,用于通知进程某个事件已经发生,适合简单的通知机制。
  • 共享内存(Shared Memory):允许两个或多个进程共享一个给定的存储区。速度最快的通信方式,进程共享一块内存区域,但需要同步机制来避免竞争条件。
  • 套接字(Sockets):支持不同主机之间的进程通信,可以是TCP/IP或UDP/IP协议。不仅用于网络通信,也可以用于本地进程间通信,灵活且强大。
  • 信号量(Semaphores)互斥锁(Mutexes):用于控制对共享资源的访问,防止多个进程同时访问同一资源。

(2) 线程间通信

  • 共享内存:由于线程共享相同的内存空间,它们可以直接通过读取和修改共享变量来通信,效率高。
  • 互斥锁(Mutexes):用于同步线程对共享资源的访问,防止数据竞争。
  • 条件变量(Condition Variables):允许线程在某个条件为真之前挂起,并在条件变为真时被唤醒。线程可以等待特定条件满足后继续执行,适合复杂的同步场景。
  • 信号量(Semaphores):用于控制对共享资源的访问,也可以用于线程间的同步,适合计数资源的同步。
  • 屏障(Barriers):同步机制,用于等待一定数量的线程都到达某个点后再继续执行。

(3) 协程间通信

  • 通道(Channels):在支持协程的语言中(如Go),通道是一种同步通信机制,允许协程通过发送和接收数据来通信,通常用于协程间的同步和数据交换。类似于管道,用于协程间传递消息或数据,直观且易于使用。
  • 共享变量:类似于线程,协程也可以通过共享变量来通信,但需要小心处理同步问题,以避免竞态条件。
  • 事件循环(Event Loop):在JavaScript中,协程(如通过async/await实现的异步函数)通常依赖于事件循环来管理异步操作,事件循环负责调度和执行异步任务。
  • Future/Promise:用于处理异步操作的结果,提供一种优雅的方式来处理协程间的结果传递。

3. js 是线程还是进程

在浏览器环境中,JavaScript 是单线程的。它在一个线程中执行代码,处理事件和更新用户界面。JavaScript 使用事件循环来管理异步操作。事件循环允许 JavaScript 处理异步任务,如网络请求或定时器,而不会阻塞主线程。虽然 JavaScript 本身是单线程的,但可以通过 Web Workers 创建多线程环境。Web Workers 允许在后台线程中运行代码,从而避免阻塞主线程。这种设计使得 JavaScript 能够高效地管理用户界面交互和后台任务。

4. js 事件循环机制

原理

JS是单线程的,为了防止代码阻塞,把任务分成同步任务和异步任务。
同步任务放入JS引擎直接执行,原地等待结果,其任务放入执行栈中按顺序执行;而异步任务需要放入宿主环境(浏览器、Node),不用原地等待结果,比较耗时,其任务放在任务队列中。
首先,执行栈中的同步任务会按顺序执行。当执行栈为空时,事件循环会检查任务队列。执行所有的微任务。如果调用栈为空,则从任务队列中取出一个宏任务并执行。执行完毕后,事件循环再次检查任务队列,重复这个过程。
在 JavaScript 中,任务可以分为同步任务、宏任务(Task),和微任务(Microtask)。以下是一些常见的例子:

同步任务

这些任务会立即执行,按顺序依次完成,不会被中断。

  • 普通的变量声明和赋值
  • 函数调用
  • console.log
宏任务(Task)

这些任务会被添加到任务队列中,等到主线程空闲时执行。

  • 整个脚本(初始执行)(指从头到尾执行一段 JavaScript 代码。它是事件循环中第一个被处理的宏任务)
  • setTimeout
  • setInterval
  • I/O 操作(读取文件或发送网络请求)
  • UI 渲染事件
  • setImmediate(Node.js)
微任务(Microtask)

这些任务会在当前宏任务执行结束后立即执行,在下一个宏任务开始前完成。

  • Promise 的回调函数(then, catch, finally
  • process.nextTick(Node.js)
  • MutationObserver
  • async/awaitawait之后的代码都看作微任务!(因为 await 会暂停函数的执行,并让出事件循环。在 await 的 Promise 解决后,函数会继续执行,后续代码作为微任务在当前宏任务完成后执行。)
执行顺序
  1. 执行同步任务。
  2. 当前宏任务执行完毕后,执行所有微任务。
  3. 执行下一个宏任务。

这种机制确保了同步任务优先执行,微任务在宏任务之间迅速处理,保持高效的异步操作。

举例

(1) 同步函数执行顺序

对于同步函数 f1()f2()

function f1(){for(let i=0; i<200; i++){}
}function f2(){for(let i=0; i<300; i++){}
}f1();
f2();

执行顺序是严格按照代码的顺序进行的:

  1. f1() 完成后才会执行 f2()
  2. 因为它们都是同步代码,所以没有异步调度的干扰。
(2) 异步函数执行顺序

对于异步函数 promise1()promise2()

async function promise1(){await xx;console.log(1);
}async function promise2(){await xx;console.log(2);
}promise1();
promise2();

假设 xx 是一个返回 Promise 的操作,执行顺序如下:

  1. promise1()promise2() 被调用,返回的 Promise 进入微任务队列。
  2. await xx 处,函数会暂停,控制权返回到事件循环。
  3. 一旦主线程的同步代码执行完毕,事件循环会处理微任务队列中的任务。
  4. await xx 完成后,console.log(1)console.log(2) 分别被放入微任务队列。
  5. 因为 promise1() 先调用,所以 console.log(1) 会先执行,接着是 console.log(2)

因此,输出结果是:

1
2
  • await 会暂停函数的执行,直到 Promise 解决为止。
  • async/await 使得异步代码看起来像同步代码,但实际执行顺序依赖于事件循环和微任务队列。
  • 微任务(例如 await 的处理)会在当前宏任务结束后立即执行。
(3) 混合任务
async function promise1() {console.log('A');await Promise.resolve(console.log('C'));console.log(1);
}async function promise2() {console.log('B');await Promise.resolve(console.log('D'));console.log(2);
}promise1();
promise2();

在这个代码片段中:
同步任务:

  • console.log('A')
  • console.log('C')
  • console.log('B')
  • console.log('D')
    这些会按照顺序立即执行。

微任务:

  • console.log(1)(作为 promise1 中的 await 后的代码)
  • console.log(2)(作为 promise2 中的 await 后的代码)
    这些会在当前宏任务完成后,作为微任务执行。

宏任务:

  • 每个 promise1()promise2() 的调用都是一个宏任务,但在这个上下文中,主要关注的是事件循环的执行顺序。

执行顺序:

  • 同步任务先执行,打印 ACBD
  • 然后执行微任务队列中的 console.log(1)console.log(2)

5. js 中的 async / defer 属性?平时用那种方式多?(参考掘金《「2021」高频前端面试题汇总之HTML篇》)

在这里插入图片描述
其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。

  • 没有deferasync 加载和执行都会阻塞html的解析

deferasync属性都是去异步加载外部的JS脚本文件,它们都不会阻塞页面的解析,区别:

  • defer 异步加载JS文件,不会立即执行,不阻塞html解析,会在解析完成后按顺序执行
  • async 异步加载和执行JS文件,不会阻塞html解析,下载完就执行,没有执行顺序

平时用那种方式多?
defer多。使用 defer 进行优化,不阻塞 html 解析;执行会按照顺序来,保证了正确性。

6. 说说异步编程中的 async / await

  • async: 用于定义一个异步函数,函数返回一个 Promise。即使没有显式返回 Promise,函数也会自动将返回值包装成一个 Promise

  • await: 用于暂停异步函数的执行,等待一个 Promise 完成,并返回其解析值。await 只能在 async 函数中使用。
    用法:

    async function fetchData() {
    try {
    const response = await fetch(‘https://api.example.com/data’);
    const data = await response.json();
    console.log(data);
    } catch (error) {
    console.error(‘Error:’, error);
    }
    }

工作原理:

  • 当函数被调用时,会立即返回一个 Promise
  • 函数内部遇到 await 时,会暂停执行,直到 Promise 解决或拒绝。
  • await 后的表达式会被转换为 Promise.resolve()

优势:

  • 简化代码: 使异步代码看起来更像同步代码,易于理解和维护。
  • 错误处理: 可以使用 try/catch 语句来处理异步操作中的错误。

注意事项:

  • await 只能在 async 函数中使用。
  • 多个 await 操作会导致顺序执行,可能影响性能。可以使用 Promise.all() 来并行执行多个异步操作。

示例对比:
Promise then/catch:

fetch('https://api.example.com/data').then(response => response.json()).then(data => console.log(data)).catch(error => console.error('Error:', error));

Async/Await:

async function fetchData() {try {const response = await fetch('https://api.example.com/data');const data = await response.json();console.log(data);} catch (error) {console.error('Error:', error);}
}

结论:
async/await 是基于 Promise 的语法糖(在编程语言中,为了提高可读性和简洁性而提供的语法特性),使得异步代码更简洁和易读,是现代 JavaScript 开发中的重要工具。

7. await 会阻塞js线程加载执行吗

await 不会阻塞 JavaScript 线程的执行。它只是暂停当前异步函数的执行,等待 Promise 完成(被解决或拒绝)。在此期间,JavaScript 事件循环仍然可以处理其他任务,如用户交互、渲染等。

async function example() {console.log('Start');const result = await new Promise(resolve => setTimeout(() => resolve('Done'), 1000));console.log(result);
}example();
console.log('This runs while waiting');// 输出
Start
This runs while waiting
Done

8. 说说web worker

待看文章:一文彻底学会使用web worker

什么是 Web Worker

Web Worker 是一种在浏览器中创建后台线程的方法,用于执行复杂或耗时的 JavaScript 任务,而不会阻塞主线程。这有助于保持用户界面的流畅性。

为什么使用 Web Worker?
  1. 提高性能: 在处理密集计算任务时,Web Worker 可以防止页面卡顿。
  2. 增强用户体验: 通过避免长时间的 UI 阻塞,用户界面可以保持响应。
Web Worker 的使用场景
  • 复杂计算: 比如图像处理、数据分析、加密操作等。
  • 长时间运行任务: 如大数据处理、文件解析等。
示例

在一个项目中,我需要对用户上传的图片进行滤镜处理。由于处理过程较为复杂,我使用了 Web Worker 来避免阻塞主线程。

// worker.js
self.onmessage = function(event) {const imageData = event.data;// 进行复杂的图像处理const processedData = applyFilter(imageData);self.postMessage(processedData);
};// 主线程
const worker = new Worker('worker.js');worker.onmessage = function(event) {const processedData = event.data;// 显示处理后的图像displayImage(processedData);
};worker.postMessage(originalImageData);
注意
  • 无法访问 DOM: Web Worker 不能直接操作 DOM。
  • 通信成本: 主线程和 Worker 之间通过消息传递进行通信,可能会有一定的延迟。

http://www.ppmy.cn/ops/144422.html

相关文章

深入理解 Linux wc 命令

文章目录 深入理解 Linux wc 命令1. 基本功能2. 常用选项3. 示例3.1 统计文件的行、单词和字符数3.2 仅统计行数3.3 统计多个文件的总和3.4 使用管道统计命令输出的行数 4. 实用案例4.1 日志分析4.2 快速统计代码行数4.3 统计单词频率 5. 注意事项6. 总结 深入理解 Linux wc 命…

前端打印(html)

目录 1.window.print() 2.使用插件print.js 1.window.print() <template> <div id"contenteBox">内容</div> <button click"printContent">打印</button> </template> <script> export default{ data(){ retu…

图书借阅管理系统|SpringBoot|HTML|web网站|Java【源码+数据库文件+包部署成功+答疑解惑问到会为止】

代码包运行启动成功&#xff01;不管你有没有运行环境&#xff0c;哪怕你是刚买的新电脑&#xff0c;也包启动运行成功&#xff01;有不懂的地方随便问&#xff01;问到会为止&#xff01; 【功能介绍】 该系统有两种角色&#xff1a; 管理员&#xff0c;读者。 1.管理员可以添…

第十五届蓝桥杯Scratch01月stema选拔赛—排序

排序 具体要求&#xff1a; 1). 点击绿旗&#xff0c;在舞台上出现4张点数不同的扑克牌&#xff0c;牌上的点数是随机的&#xff08;4-9点&#xff09;&#xff0c;如图所示&#xff1b; 完整题目可点击下方链接查看&#xff1a; 排序_scratch_嗨信奥-玩嗨信息奥林匹克竞赛-…

PHP基础

PHP代码标记 标准标记&#xff1a;<?php ?> PHP注释 单行&#xff1a;// # 多行&#xff1a;/* */ 两种浏览器输出文本的方式&#xff1a;echo 和 print echo <?php header("Content-Type:text/html;charsetutf-8"); // 输出字符串 ec…

Redis篇--常见问题篇2--缓存雪崩(过期时间分散,缓存预热,多级缓存)

1、概述 缓存雪崩是指在短时间内&#xff0c;大量的缓存同时失效或过期&#xff0c;导致大量的请求穿透到后端数据库或服务&#xff0c;从而引发系统负载骤增&#xff0c;甚至可能导致系统崩溃。这种情况通常发生在缓存的过期时间设置不合理时&#xff0c;所有缓存的过期时间相…

SLAAC如何工作?

SLAAC如何工作&#xff1f; IPv6无状态地址自动配置(SLAAC)-常见问题 - 苍然满关中 - 博客园 https://support.huawei.com/enterprise/zh/doc/EDOC1100323788?sectionj00shttps://www.zhihu.com/question/6691553243/answer/57023796400 主机在启动或接口UP后&#xff0c;发…

【Java】mac安装Java17(JDK17)

文章目录 下载java17一、安装二、环境变量 下载java17 官网下载&#xff1a;https://www.oracle.com/java/technologies/downloads 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、安装 直接安装后&#xff0c;安装完后目录会存放在下面目录下 /…