欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > JS的PromiseES6模块化Commonjs模块化 ✅

JS的PromiseES6模块化Commonjs模块化 ✅

2025/2/23 20:05:46 来源:https://blog.csdn.net/YUYUYU972/article/details/143952338  浏览:    关键词:JS的PromiseES6模块化Commonjs模块化 ✅

Promise

1、关于宏任务和微任务

首先,JavaScript自始至终都是一门单线程语言。其次,“宏任务”、“微任务”、“事件循环”这些名词都是异步代码里面才有的概念。

  • 常见的宏任务:setTimeout、setInterval、DOM事件、script标签中所有代码中执行(暂时写不出demo来验证这个点,或许可以试试点击一个按钮然后给html文档添加一个script标签?)
  • 常见的微任务:
    • Promise的then、catch、finally
    • MutationObserver(用来监听一个DOM的内容是否改变)的回调
    • queueMicrotask(浏览器提供的添加一个微任务的API)等

Js的执行顺序如下:

  1. 执行栈开始执行同步代码,直到栈为空。
  2. 执行微任务队列中的所有任务。
  3. 取出宏任务队列中的一个任务并执行。
  4. 重复2和3

Q:requestAnimationFrame的执行时机是什么时候?
A:在一轮事件循环中,微任务清空之后,下一帧渲染开始之前


2、手写一个Promise(很简陋的一个demo)

女马白勺,面试重头戏,走召。

先实现一个功能,异步执行一个回调函数:

const runAsyncTask = (callbackFun) => {if (typeof queueMicrotask === 'function') {// 浏览器有queueMicrotask这个API可以用queueMicrotask(callbackFun)} else if (typeof MutationObserver === 'function') {// 浏览器有MutationObserver这个API可以用const observer = new MutationObserver(callbackFun)const tempNode = document.createElement('div')observer.observe(divNode, { childList: true })divNode.innerText = 'haha!'} else {// 用setTimeout兜底setTimeout(callbackFun, 0)}
}

实现Promise:

class MyPromise {construct(func) {this.state = PENDING;this.handlers = [];this.result = undefined;// 闭包:res和rejconst resolve = result => {if(this.state !== PENDING) return;this.state = FULFILLED;this.result = result;this.handlers.forEach(handler => {runAsyncTask(handler.onFulFilled(result))})}const reject = error => {if(this.state !== PENDING) return;this.state = REJECTED;this.result = error;this.handlers.forEach(handler => {runAsyncTask(handler.onRejected(error))})}// 同步执行传递进来的函数func(resolve, reject)}	then(onFulFilled, onRejected) {const fulFilledHandler = typeof onFulFilled === 'function' ? onFulFilled : value => value;const rejectedHandler = typeof onRejected === 'function' ? onRejected : error => { throw error };return new MyPromise((res, rej) => {if(this.state === PENDING) {this.handlers.push({onFulFilled: (value) => {const v = fulFilledHandler(value);res(v);},onRejected: (error) => {const e = rejectedHandler(error);rej(e);}})} else if(this.state === RESOLVED) {// 挂载回调函数的时候Promise已经resolveconst v = fulFilledHandler(this.result);res(v);} else if(this.state === REJECTED) {// 挂载回调函数的时候Promise已经rejectconst e = rejectedHandler(this.result);rej(e);}})}catch(onRejected) {return this.then(null, onRejected);}
}

关于链式调用记住 “大部分异步的逻辑都是在回调链上的第一个Promise里面执行的,从第二个Promise开始,构造函数里面的同步代码要做的主要是承接上一个Promise的state并执行新的回调。”


3、await & async

  • await阻塞的是某一个async函数里代码流的执行(或者说挂起了某一个async函数),而非阻塞整个程序
  • async返回的是一Promise对象
  • async&await配合try&catch,可以在catch中捕获Promise的reject

见以下demo:

async function asyncFunc() {try {await new Promise((res) => {console.log("start")setTimeout(() => {res('success')}, 200)}).then(res => {console.log(res, 'then in 1')})return 'asyncFunc success'} catch (err) {console.log(err)throw new Error('asyncFunc error')}
}
const otherPromise = asyncFunc().then(res => {console.log(res) // asyncFunc success
}).catch(error => {console.error(error) // asyncFunc error
})

4、Promise常用的原型方法

  • all:当所有 Promise 都被解决时,返回一个新的 Promise,且该 Promise 的结果为一个数组,包含所有已解决 Promise 的结果。
  • allSettled:当所有 Promise 都被解决(包括被拒绝)时,返回的 Promise 将被解决,结果为一个数组,包含每个 Promise 的结果对象。
  • any:只要有一个 Promise 被解决,返回的 Promise 就会解决,结果为第一个被解决的 Promise 的值。
  • race:返回的 Promise 将在第一个被解决或拒绝的 Promise 解决时解决,返回的值或错误直接来自该被解决的 Promise。

ES6模块化 vs CommonJS模块化

每一次写这玩意都感觉云里雾里的☁️
CJS多用在后端(node),ESM多用在前端(浏览器),语法也不同,无需多言。

1、导出的东西不同

commonJs 导出的是副本
ESModule 导出的是指针


2、加载的方式不同

CJS的require本质上是一个运行时🔥函数🔥,它的调用会阻塞后续代码的执行,直到模块加载完毕这一点几乎决定了 CJS是在运行时按需加载&同步加载。
使用 require 加载模块时,Node.js 会逐步解析模块依赖关系,所有模块会被按需加载

而ESM在运行时是异步加载的,import不会阻塞后续代码的执行。

(注意:这里的“同步加载 vs 异步加载” 并不是 “静态导入 vs 动态导入”。不管是CJS还是ESM都能动态导入,甚至可以说CJS“只能动态导入?如果‘按需’也能约等于‘动态’的话”?

1️⃣ CJS为什么被称为同步加载?

一个模块的 require(“./module”) 语句会在程序运行时直接阻塞执行,直到 module 被完全加载并执行完毕。如果你在一个模块中有多个需要通过 require 加载的模块,它们的加载过程是线性的。每一个 require 调用都会等待模块的加载完成,然后才会继续执行后面的代码,这本质上表现出同步的特性。
即使在使用 CommonJS 的时候使用了动态导入,但是动态加载的运行方式仍然是在同步操作:

function loadModule(name) {const module = require(name)// 上面这一行require会阻塞后续代码的执行// todo...
}

再看一个demo,假设你在node环境下这样写:

// moduleA.js
console.log('Module A is loading...');
const b = require('./moduleB.js'); // 导入模块B
console.log('Module A has loaded.');// moduleB.js
console.log('Module B is loading...');
for (let i = 0; i < 1e9; i++) {} // 模拟一个耗时的操作
console.log('Module B has loaded.');// main.js
const a = require('./moduleA.js'); // 导入模块A
console.log('Main script is running.');

运行main的输出完全是线性的

Module B is loading...
// wait
Module B has loaded.
Module A is loading...
Module A has loaded.
Main script is running.

2️⃣ ESM为什么被称为异步加载?

上面的代码如果用ESM改写并运行在浏览器(Vite)中,效果截然不同:

// moduleA.js
import('./moduleB.js');
console.log('Module A is loading...');
console.log('Module A has loaded.');// moduleB.js
console.log('Module B is loading...');
for (let i = 0; i < 1e9; i++) {} // 模拟一个耗时的操作
console.log('Module B has loaded.');// main.js
import './moduleA.js'
console.log('Hello, World!');

浏览器输出:
在这里插入图片描述
ESM这种异步的原理是什么?以下回答来自Chat (还没写一个demo来验证,有点难)

// module.js
export const myVar = "Hello, ESM!";
// main.js
import { myVar } from "./module.js"; // 解析并加载 module.js
console.log(myVar); // 此时 module.js 已加载并完成
  1. 模块解析阶段:当 JavaScript 引擎开始解析 main.js 时,它会遇到 import { myVar } from ‘./module.js’;。在这个阶段,引擎会启动一个异步请求来加载 module.js。引擎会将这个模块的加载标记为“待处理”。
  2. 非阻塞特性:在发出对 module.js 的加载请求后,JavaScript 引擎并不会停止执行 main.js 后面的代码。相反,它会继续执行后续行代码(如果有其他不依赖于这个模块的代码的话)。
  3. 后续代码执行:只要不是直接依赖于 module.js 的代码,JavaScript 引擎会继续执行后续的代码。例如,如果存在其他普通的函数调用或逻辑,等到这些代码执行完成后才会去等待 module.js 加载完成。
  4. 完成模块加载后:一旦 module.js 加载并解析完成,引擎会完成导入操作,确保 myVar 变量已成功绑定,并且此时其他代码将会继续执行。

“静态”的ESM和“动态”的CJS

一个老生常谈的问题💡:为什么Rollup无法用CJS实现树摇?Rollup实现的树摇,是基于ESM的静态结构使其成为可能。
当我们说“ESM是静态的”,主要是指其导入和导出在编译阶段是可确定的。这意味着在代码运行之前, 构建工具 能够分析模块之间的依赖关系,而不是在运行时才发现,从而做出优化。
在 CommonJS 中,require 是一个运行时的函数。模块的加载和导入可以根据运行时条件动态决定,这使得构建工具在构建阶段无法解析哪些模块实际会被使用。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com