当前位置: 首页 > article >正文

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

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.kler.cn/a/447430.html

相关文章:

  • mlr3机器学习AUC的置信区间提取
  • vue 文本域 展示的内容格式要和填写时保持一致
  • 【从零开始入门unity游戏开发之——C#篇08】逻辑运算符、位运算符
  • 【图像配准】方法总结
  • 【快速上手Docker 简单配置方法】
  • 【ArcGIS Pro】水文水资源、水生态与水环境
  • 【Token】校验、会话技术、登录请求、拦截器【期末实训】实战项目学生和班级管理系统\Day15-后端Web实战(登录认证)\讲义
  • ip_forward函数
  • gesp(二级)(7)洛谷:B3865:[GESP202309 二级] 小杨的 X 字矩阵
  • STM32-笔记7-继电器定时开闭
  • 雅思真题短语梳理(八)
  • 常用的JVM启动参数有哪些?
  • 电子发票汇总改名,批量处理电子发票问题
  • ChatGPT接口测试用例生成的流程
  • windows安装Elasticsearch及增删改查操作
  • 基于SpringBoot+Mysql实现的在线音乐系统平台功能实现一
  • postman测试导入文件
  • 【ETCD】【实操篇(四)】etcd常见问题快问快答FAQ
  • 2.5 io_uring
  • 黑马Java面试教程_P7_常见集合_P4_HashMap
  • homebrew,gem,cocoapod 换源,以及安装依赖
  • uniapp实现手写签名,并在app中将其转为base64格式的图片
  • springboot中的AOP以及面向切面编程思想
  • Vue.js前端框架教程8:Vue消息提示ElMessage和ElMessageBox
  • Win/Mac 如何实现测试 IP 和端口
  • ​在VMware虚拟机上设置Ubuntu与主机共享文件夹​