js基础知识

1. ES6

1.1. promise.all、promise.allSettled、promise.race对比

Promise.all(), Promise.allSettled(), 和 Promise.race() 都是 JavaScript 中用于处理多个 Promise 的实用函数,但它们各自有不同的行为和用途。下面是对这三个函数的对比:

1. Promise.all()
  • 功能Promise.all() 接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新的 Promise 在所有输入的 Promise 都成功解决时才会解决,返回一个数组,其中的元素是各个 Promise 解决后的值。
  • 行为:如果输入的任何一个 Promise 被拒绝 (rejected),那么 Promise.all() 返回的 Promise 会立即被拒绝,且不会等待其他 Promise 完成。这意味着 Promise.all() 实际上是有“短路”行为的。
  • 使用场景:当你需要确保一系列异步操作全部成功完成时使用,例如,从多个 API 端点获取数据,且只有当所有数据都可用时才继续后续操作。

2. Promise.allSettled()

  • 功能Promise.allSettled() 同样接收一个 Promise 数组作为参数,但与 Promise.all() 不同的是,它会等待所有的 Promise 完成,无论它们是解决还是被拒绝。
  • 行为:它返回一个 Promise,这个 Promise 在所有输入的 Promise 都已完成(无论是解决还是被拒绝)时解决,返回一个数组,其中的元素是各个 Promise 的最终状态对象(包括 statusvaluereason)。
  • 使用场景:当你需要收集一系列异步操作的所有结果,无论它们是否成功,以便进行统一处理或错误管理时使用。

3. Promise.race()

  • 功能Promise.race() 接收一个 Promise 数组作为参数,返回一个新的 Promise,这个新的 Promise 会在输入的任意一个 Promise 解决或被拒绝时立即解决或被拒绝。
  • 行为:它不关心其他 Promise 是否完成,只关注第一个完成的 Promise。如果第一个完成的 Promise 被解决,那么 Promise.race() 返回的 Promise 也会被解决;如果被拒绝,同样如此。
  • 使用场景:当你需要在多个异步操作中选择最快完成的那个,或者有一个超时机制时使用。例如,你可能想在一定时间内获取数据,但如果超时则放弃尝试。

概念总结

  • Promise.all() 用于等待所有 Promise 成功解决。
  • Promise.allSettled() 用于等待所有 Promise 结束,无论结果如何。
  • Promise.race() 用于响应最快完成的 Promise。

1.2 ES6 模块化

ES6(ECMAScript 2015)模块化是一种在JavaScript中组织和管理代码的新方法。在ES6之前,JavaScript没有内置的模块系统,开发者通常会使用一些社区标准如CommonJS(主要用于Node.js环境)、AMD(异步模块定义,用于浏览器环境)或CMD(Sea.js使用的模块定义)来实现模块化。

ES6模块化的主要特性包括:

  1. 导入和导出(import/export)

    • export 语句用于导出模块的成员,可以是变量、函数、类或其他表达式。
    • import 语句用于从其他模块导入成员,可以是具体的导出成员或默认导出成员。
  2. 默认导出与默认导入

    • 每个模块可以有一个默认导出,通过 export default 语句定义。
    • 默认导出可以通过任何名字导入,使用 import 语句时不需要加花括号 {}
  3. 命名导出与命名导入

    • 多个成员可以被命名导出,使用 export 关键字前缀。
    • 这些成员可以通过带有花括号 {}import 语句导入,并且可以重命名。
  4. 静态分析

    • ES6 模块支持静态分析,这意味着在编译阶段就能确定模块之间的依赖关系,以及输入和输出的变量。
  5. 模块作用域

    • 每个模块都有自己的作用域,这意味着模块内部定义的变量和函数不会泄露到全局作用域。
  6. 动态导入(虽然不在原始的ES6规范中,但后来被添加):

    • 动态导入允许在运行时按需加载模块,使用 import() 函数。

示例:

假设我们有两个模块文件,math.jsapp.js

math.js:

export function add(x, y) {
  return x + y;
}

export function subtract(x, y) {
  return x - y;
}

// 默认导出
export default function multiply(x, y) {
  return x * y;
}

app.js:

import { add, subtract } from './math.js';
import multiply from './math.js'; // 导入默认导出的multiply函数

console.log(add(1, 2)); // 3
console.log(subtract(5, 3)); // 2
console.log(multiply(2, 3)); // 6

ES6 模块化提供了更好的封装性和可维护性,减少了全局命名空间的污染,并使代码更易于理解和调试。在现代的前端开发中,ES6 模块已经成为标准的代码组织方式。在服务器端,如Node.js,也已经完全支持ES6模块。不过需要注意的是,使用ES6模块在Node.js中需要指定文件扩展名,并且从Node.js v14.5.0开始,你可以通过设置"type": "module"package.json中启用对ES6模块的支持。

基础原理

事件循环

JavaScript的事件循环(Event Loop)是其异步处理机制的核心部分。在JavaScript中,所有的执行都是单线程的,这意味着在任何给定的时间点,JavaScript只能做一件事情。然而,JavaScript可以通过事件循环来实现非阻塞的异步操作,这样就不会因为等待I/O操作(如网络请求、文件读写等)而停止执行其他代码。

事件循环主要基于以下两个概念:

  1. 调用栈(Call Stack)
    调用栈是一个后进先出(LIFO)的数据结构,它保存了函数调用的上下文。每当一个函数被调用时,它的上下文就会被压入调用栈,当该函数执行完毕后,它的上下文会从调用栈中弹出。

  2. 任务队列(Task Queue)
    当JavaScript引擎遇到异步操作时,比如setTimeoutsetInterval,这些操作会被挂起,并将一个回调函数放入任务队列中。一旦异步操作完成,相应的回调函数会被放入任务队列等待执行。

事件循环的工作流程如下:

  1. 执行上下文创建
    JavaScript引擎开始执行时,会创建全局执行上下文并将其压入调用栈。

  2. 解析和执行代码
    引擎开始从上到下解析和执行代码,遇到函数调用时,会将其压入调用栈;遇到异步操作时,不会立即执行,而是将相关的回调函数放入任务队列。

  3. 检查调用栈
    引擎会持续检查调用栈,如果调用栈为空,则会检查任务队列。

  4. 执行任务队列中的任务
    如果调用栈为空且任务队列中有待处理的任务,事件循环会从任务队列中取出第一个任务,将其放入调用栈中执行。这个过程会重复进行,直到任务队列为空。

  5. 微任务与宏任务
    除了上述的宏任务(如setTimeout),JavaScript还有微任务(microtasks),例如Promise的回调。微任务会在当前宏任务结束之后,但在控制权交给下一个宏任务之前执行。这通常意味着它们具有更高的优先级。

  6. 循环
    这个过程会一直重复,直到所有任务都被处理完毕,或者JavaScript引擎被显式地关闭。

通过理解事件循环,你可以更好地设计和优化异步代码,避免潜在的阻塞和性能问题。
在JavaScript中,任务可以分为两种类型:宏任务(macrotasks)和微任务(microtasks)。宏任务和微任务的区别在于它们在事件循环中的执行时机和优先级。

宏任务 (Macrotasks)

宏任务包括常见的同步代码执行、setTimeoutsetInterval、I/O操作、UI渲染等。每个宏任务都会占用事件循环的一个完整轮次。这意味着,当一个宏任务开始执行时,它会运行直到完成,期间不会让位给其他宏任务或微任务。只有当当前宏任务完全执行完毕后,事件循环才会检查是否有其他待处理的宏任务或微任务。

微任务 (Microtasks)

微任务,例如Promise的回调、MutationObserverprocess.nextTick(在Node.js环境中)、以及queueMicrotask() API,它们的执行时机是在当前宏任务结束之后,但在控制权交还给下一个宏任务之前。这意味着,即使有多个微任务,它们也会在当前宏任务的生命周期内按顺序执行完。

示例:

假设你有以下JavaScript代码:

console.log('Start');

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

new Promise((resolve) => {
  console.log('Promise');
}).then(() => {
  console.log('Then');
});

console.log('End');

输出结果将是:

Start
Promise
End
Then
Timeout

解释如下:

  1. 同步代码console.log('Start')首先执行。
  2. setTimeout注册了一个宏任务,但不会立即执行。
  3. Promise创建并立即执行,打印"Promise",然后在微任务阶段执行.then回调。
  4. 最后的console.log('End')是同步代码,所以紧接着执行。
  5. 接下来,由于当前宏任务已经结束,所有的微任务(这里是.then回调)被执行,打印"Then"。
  6. 最后,setTimeout的回调作为下一个宏任务执行,打印"Timeout"。

因此,微任务具有较高的优先级,可以在当前宏任务结束后立即执行,而不需要等待新的宏任务周期。这使得微任务非常适合用于需要在当前执行环境结束前完成的操作,例如更新DOM或执行某些异步操作的结果处理。

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值