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

前端面经-JS篇(四)--回调地狱、promise异步编程、Proxy 与 Reflect 、模块化

 一、回调地狱

回调地狱(Callback Hell),也称为回调地狱,是指在 JavaScript 中处理多个嵌套的异步回调函数时,代码结构变得非常难以阅读和维护的现象。

为什么会出现回调地狱?

回调地狱通常出现在需要执行多个异步操作的场景中,特别是当这些异步操作之间有依赖关系时。为了在每个异步操作完成后执行下一个操作,你通常需要将回调函数嵌套在另一个回调函数中。随着操作增多,代码结构会变得越来越复杂,逐渐形成嵌套的“地狱”层次。

示例:回调地狱

以下是一个典型的回调地狱示例,假设你有多个异步操作需要依次执行:

asyncOperation1(function(result1) {asyncOperation2(result1, function(result2) {asyncOperation3(result2, function(result3) {asyncOperation4(result3, function(result4) {console.log(result4);});});});
});

 在这个例子中,每次异步操作都需要嵌套在上一个操作的回调函数中。如果有很多异步操作,嵌套层数会迅速增加,导致代码变得非常难以维护,且容易出现错误。

回调地狱的特点:

  1. 嵌套层级深:每个回调都需要嵌套在上一个回调函数内,形成多层嵌套。

  2. 代码不易读:随着嵌套层数增加,代码的可读性和可维护性大大降低。

  3. 错误处理困难:当异步操作失败时,需要仔细地在每个回调函数中处理错误,如果有多个层次的回调,错误的捕获和处理变得复杂。

  4. 调试困难:在复杂的嵌套回调中,错误的定位和调试变得更加困难。

二、promise异步编程

Promise 是 JavaScript 中处理异步操作的一种机制,它提供了一种方式来处理那些需要较长时间执行的操作(如网络请求、文件读取等),使得这些操作能够在完成时通知你结果,并且提供了一种更清晰、可控的方式来处理异步代码,避免了回调地狱(Callback Hell)的问题。

Promise 的基本概念

Promise 是一种表示 异步操作最终完成失败 的对象,且能够在异步操作完成时提供结果值(成功的结果或失败的原因)。

Promise 有三个状态:

  1. Pending(等待中):表示异步操作尚未完成。

  2. Fulfilled(已完成):表示异步操作已经成功完成,Promise 对象返回一个结果值。

  3. Rejected(已拒绝):表示异步操作失败,Promise 对象返回一个错误原因。

Promise 的使用

1. 创建 Promise

Promise 对象可以通过 new Promise 来创建。Promise 构造函数接受一个函数作为参数,这个函数有两个参数:resolvereject,分别用于处理成功的结果和失败的结果。

let promise = new Promise((resolve, reject) => {// 异步操作(例如:网络请求、文件读取等)let success = true; // 假设这个变量决定了操作是否成功if (success) {resolve("操作成功");} else {reject("操作失败");}
});
2. 使用 .then().catch() 方法
  • .then() 用于处理成功的结果。

  • .catch() 用于处理失败的结果。

promise.then(result => {console.log(result); // 如果 Promise 成功,输出:操作成功}).catch(error => {console.log(error);  // 如果 Promise 失败,输出:操作失败});
3. 链式调用

由于 thencatch 方法本身会返回一个新的 Promise,所以你可以链式调用多个 then(),按顺序处理异步操作。

new Promise((resolve, reject) => {resolve("第一个操作成功");
}).then(result => {console.log(result); // 输出:第一个操作成功return "第二个操作成功"; // 返回新的结果给下一个 then()}).then(result => {console.log(result); // 输出:第二个操作成功return "第三个操作成功"; // 返回新的结果}).then(result => {console.log(result); // 输出:第三个操作成功});
4. 异步操作的错误处理

当链中的任何 then() 出现错误时,错误会被传递到最近的 catch() 方法。

new Promise((resolve, reject) => {resolve("开始");
}).then(result => {console.log(result); // 输出:开始throw new Error("发生了错误"); // 主动抛出一个错误}).catch(error => {console.log(error.message); // 输出:发生了错误});
5. Promise.all()

Promise.all() 接受一个包含多个 Promise 对象的数组,返回一个新的 Promise,当所有传入的 Promise 都完成时,新的 Promise 就会变成 fulfilled,如果有一个 Promise 被 rejected,那么新的 Promise 就会立即 rejected,并且返回失败的原因。

let promise1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '第一个任务完成'));
let promise2 = new Promise((resolve, reject) => setTimeout(resolve, 200, '第二个任务完成'));
let promise3 = new Promise((resolve, reject) => setTimeout(resolve, 300, '第三个任务完成'));Promise.all([promise1, promise2, promise3]).then(results => {console.log(results); // 输出:["第一个任务完成", "第二个任务完成", "第三个任务完成"]}).catch(error => {console.log(error); // 如果其中一个 Promise 被拒绝,输出错误});
6. Promise.race()

Promise.race() 返回的 Promise 会在第一个 Promise 完成时立即返回结果,无论这个 Promise 是 fulfilled 还是 rejected,其他 Promise 会被忽略。

let promise1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '第一个任务完成'));
let promise2 = new Promise((resolve, reject) => setTimeout(resolve, 200, '第二个任务完成'));Promise.race([promise1, promise2]).then(result => {console.log(result); // 输出:第一个任务完成});
7. Promise.allSettled()

Promise.allSettled() 接受一个包含多个 Promise 对象的数组,返回一个新的 Promise,这个新的 Promise 会在所有的 Promise 都完成后 resolved,不论它们是 fulfilled 还是 rejected。

let promise1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '第一个任务完成'));
let promise2 = new Promise((resolve, reject) => setTimeout(reject, 200, '第二个任务失败'));Promise.allSettled([promise1, promise2]).then(results => {console.log(results); // 输出: // [//   { status: 'fulfilled', value: '第一个任务完成' },//   { status: 'rejected', reason: '第二个任务失败' }// ]});
8. Promise.finally()

finally() 方法在 Promise 执行完后,无论成功还是失败都会被调用,通常用于清理操作。

new Promise((resolve, reject) => {resolve("任务成功");
}).then(result => {console.log(result); // 输出:任务成功}).finally(() => {console.log("清理操作"); // 输出:清理操作});

总结

  • Promise 提供了一种优雅的方式来处理异步操作,避免了回调地狱问题。

  • then() 用于处理成功结果,catch() 用于处理失败结果,finally() 用于执行无论成功还是失败都要执行的操作。

  • 可以使用 Promise.all()Promise.race()Promise.allSettled() 等方法来处理多个 Promise 的执行。

如何避免回调地狱?

1、使用 Promise:通过 Promise 可以链式调用,使代码更加平坦和可读,避免多层嵌套。

asyncOperation1().then(result1 => asyncOperation2(result1)).then(result2 => asyncOperation3(result2)).then(result3 => asyncOperation4(result3)).then(result4 => console.log(result4)).catch(error => console.log(error));

 2、使用 async/awaitasync/await 是基于 Promise 的语法糖,它使得异步代码看起来像同步代码一样,极大提高了代码的可读性。

async function runOperations() {try {let result1 = await asyncOperation1();let result2 = await asyncOperation2(result1);let result3 = await asyncOperation3(result2);let result4 = await asyncOperation4(result3);console.log(result4);} catch (error) {console.log(error);}
}runOperations();

 3、模块化与函数拆分:将复杂的回调操作拆分成多个小函数,减少代码的嵌套层级,使得每个函数的职责更加明确。

三、 Proxy和Reflect

  • Proxy:为对象的基本操作(如读取、写入、删除属性)提供了一种拦截和自定义行为的机制,可以让开发者在操作对象时插入自定义逻辑。

  • Reflect:提供了一组用于操作对象的标准方法,简化了对象操作并避免了副作用。它通常与 Proxy 一起使用,作为 Proxy 的默认行为执行器。

为什么会引入 Proxy

  1. 拦截和自定义对象操作: JavaScript 的对象操作(如读取属性、设置属性、调用方法等)通常是隐式的,开发者无法直接拦截和修改这些操作。而有时在实际应用中,开发者需要自定义一些对象行为,比如实现属性访问的日志、验证属性值、对属性值进行处理等。Proxy 为此提供了一种机制,允许开发者在对象操作时插入自定义行为。

    例子: 假设你想在每次访问对象属性时记录日志。使用传统的方法,JavaScript 并没有提供直接的方式来做到这一点,但通过 Proxy,你可以轻松地拦截和定制这些操作。

  2. 简化复杂的操作: 在一些复杂场景下,比如需要做一系列连贯的对象拦截操作时,传统的 JavaScript 实现方式显得冗长、复杂且容易出错。Proxy 的引入可以大大简化这类任务,且代码更加简洁和易于管理。

  3. 动态对象控制: Proxy 允许你动态地控制对象的行为,使得你可以在运行时动态地指定对象的行为。通过 Proxy,你可以拦截对象的读取、写入、删除属性等操作,而无需在每个操作点显式地编写代码。

为什么会引入 Reflect

  1. 简化对象操作: Reflect 提供了一个规范化的 API,用于执行对象操作,目的是简化对对象的访问和修改。之前,JavaScript 提供了一些方法(如 Object.definePropertyObject.getOwnPropertyDescriptor 等),但是它们有时不够简洁和易用,尤其是在动态操作对象时,Reflect 让这些操作变得更加直观和统一。

  2. 标准化操作: 在使用 Proxy 时,开发者可能需要在拦截操作中手动执行默认行为(例如读取属性时获取目标对象的值)。Reflect 提供了一组与 Proxy 拦截器方法一一对应的 API,使得操作对象时能遵循标准、统一的方式,减少代码出错的几率。

    例子: 如果你拦截了对象的 get 操作,但需要在某些情况下调用默认的行为,你可以使用 Reflect.get() 来获取目标对象的属性值,而无需手动调用目标对象的原始方法。

  3. 增强 Proxy 的灵活性: ReflectProxy 密切相关,Reflect 提供了标准化的方法来执行对象的操作,通常用来在 Proxy 的 handler 函数中调用默认行为。Reflect 的引入使得 Proxy 更加强大且易于使用,能够让开发者更加灵活地控制对象操作,并且能保持代码的简洁性。

    例子:Proxy 中,你可以使用 Reflect 来执行目标对象的默认操作,使得你的代码更加简洁且符合 JavaScript 的标准。

  4. 一致性与避免副作用: Reflect 方法在执行对象操作时,不会修改原始对象的行为或状态,而是返回执行结果。这避免了手动处理错误和副作用的问题,使得开发者可以更容易理解和控制对象的行为。 

1、Proxy

Proxy 是一个用于创建对象代理的工具,可以通过 Proxy 来拦截和修改对对象的操作。Proxy 可以让你定义自定义的行为,来干预对象的基本操作,例如读取、写入、函数调用等。

创建 Proxy

Proxy 的构造函数接受两个参数:

  1. target:表示目标对象,即我们要代理的对象。

  2. handler:一个对象,定义了所有拦截行为(如读取、写入、删除属性等)的处理函数。

let handler = {get: function(target, prop, receiver) {console.log(`访问了属性: ${prop}`);return prop in target ? target[prop] : 37; // 如果属性不存在,返回 37}
};let proxy = new Proxy({}, handler);
proxy.name = "John"; // 赋值
console.log(proxy.name); // 读取,控制台输出 "访问了属性: name",并且输出 "John"
console.log(proxy.age);  // 读取,不存在的属性,控制台输出 "访问了属性: age",并且输出 37

Proxy 的拦截方法

Proxy 中的陷阱是定义如何拦截和修改目标对象操作的函数。常见的陷阱包括:

  • get(target, prop, receiver):拦截对对象属性的读取。

  • set(target, prop, value, receiver):拦截对对象属性的赋值。

  • has(target, prop):拦截 in 操作符。

  • deleteProperty(target, prop):拦截 delete 操作符。

  • apply(target, thisArg, args):拦截函数调用。

  • construct(target, args, newTarget):拦截 new 操作符。

常见的 Proxy 使用示例

1 get:拦截属性读取

get 捕捉对目标对象的属性访问。可以修改返回值,或者对未定义的属性进行特殊处理。

get(target, prop, receiver) {
    // target: 目标对象
    // prop: 要访问的属性名称
    // receiver: 用于执行该操作的代理对象
}

target(目标对象)
  • target 是你传递给 Proxy 的原始对象。它表示你想要代理的对象,所有的 getset 等操作最终都会作用于该对象。

  • target 让你可以访问目标对象的实际数据,可以对其进行操作。

prop(属性名称)
  • prop 是要访问的属性名称或键。在 Proxy 中,prop 是你在代理对象上进行操作时所用的属性。

  • 它是一个字符串或符号,表示你正在尝试访问的属性。 

receiver(代理对象)
  • receiver 是当前代理对象,指的是当前 Proxy 的实例。它是拦截操作的接收者。

  • 在大多数情况下,receiver 会指向代理对象本身,但它在某些情况下也会指向原始对象(特别是在使用 Proxy 的继承时)。

  • 在大多数情况下,receiver 只是代理对象的引用,因此你可能不常直接操作它。但它在某些复杂的场景中(如继承代理)很有用,特别是在多个代理链或继承代理时。

let target = {name: 'Alice',age: 30
};let handler = {get: function(target, prop) {if (prop in target) {return target[prop];} else {return `Property ${prop} does not exist`;}}
};let proxy = new Proxy(target, handler);console.log(proxy.name); // 'Alice'
console.log(proxy.age); // 30
console.log(proxy.nonExistent); // 'Property nonExistent does not exist'
2 set:拦截属性赋值

set 捕捉对目标对象属性的赋值,可以用于验证输入、转换数据等。

set(target, prop, value, receiver)(拦截属性赋值)
  • target:目标对象,指的是你要操作的原始对象。

  • prop:正在设置的属性名称或键。

  • value:赋给该属性的值。

  • receiver:代理对象。

let target = {name: 'Alice'
};let handler = {set: function(target, prop, value) {if (prop === 'name' && value === '') {console.log('Name cannot be empty!');} else {target[prop] = value;}return true;}
};let proxy = new Proxy(target, handler);proxy.name = 'Bob';  // 正常赋值
console.log(target.name); // 'Bob'proxy.name = '';  // 会触发警告
3 has:拦截 in 操作符

has 用于拦截 in 操作符,判断对象是否包含某个属性。

has(target, prop)(拦截 in 操作符)
  • target:目标对象,指的是你要操作的原始对象。

  • prop:要检查的属性名称。

let target = {name: 'Alice'
};let handler = {has: function(target, prop) {if (prop === 'name') {return true;} else {return false;}}
};let proxy = new Proxy(target, handler);console.log('name' in proxy); // true
console.log('age' in proxy);  // false
4 deleteProperty:拦截 delete 操作符

deleteProperty 捕捉删除属性的操作,可以通过此方法定义删除属性的行为。

let target = {name: 'Alice',age: 30
};let handler = {deleteProperty: function(target, prop) {if (prop === 'name') {console.log('Cannot delete name!');return false;  // 阻止删除}delete target[prop];return true;}
};let proxy = new Proxy(target, handler);delete proxy.name;  // 会输出 'Cannot delete name!' 并阻止删除
console.log(target.name); // 'Alice'delete proxy.age;   // 正常删除
console.log(target.age); // undefined
5 apply:拦截函数调用

apply 捕捉函数调用的操作,当通过 Proxy 调用函数时,会触发此陷阱。

apply(target, thisArg, args)(拦截函数调用)
  • target:目标函数。

  • thisArg:调用函数时的 this 值。

  • args:传递给函数的参数数组。

let target = function(x, y) {return x + y;
};let handler = {apply: function(target, thisArg, argumentsList) {console.log(`Arguments: ${argumentsList}`);return target(...argumentsList) * 2;  // 执行原函数并返回两倍的结果}
};let proxy = new Proxy(target, handler);console.log(proxy(2, 3)); // Arguments: 2,3 => 10
6 construct:拦截 new 操作符

construct 捕捉构造函数的调用,允许自定义构造对象的行为。

construct(target, args, newTarget)(拦截 new 操作符)
  • target:目标构造函数。

  • args:传递给构造函数的参数数组。

  • newTargetnew 操作符创建对象时使用的构造函数。

function Person(name, age) {this.name = name;this.age = age;
}let handler = {construct(target, args) {console.log(`Creating a new Person with arguments: ${args}`);return new target(...args);  // 执行原构造函数}
};let proxy = new Proxy(Person, handler);let p = new proxy('Alice', 30);
console.log(p.name); // Alice

这里,construct 用来捕捉通过 new 创建对象的行为,并自定义构造函数的执行逻辑。 

Proxy 的应用场景

1 验证和数据校验

Proxy 可以用来对对象属性的设置进行验证,确保数据的合法性。

let person = {name: '',age: 0
};let personProxy = new Proxy(person, {set(target, prop, value) {if (prop === 'name' && typeof value !== 'string') {throw new Error('Name must be a string');}if (prop === 'age' && typeof value !== 'number') {throw new Error('Age must be a number');}target[prop] = value;return true;}
});personProxy.name = 'John';  // Valid
personProxy.age = 25;       // Valid
// personProxy.name = 123;   // Throws Error: Name must be a string
2 访问控制

Proxy 可以用来实现访问控制,定义哪些属性可以被访问、修改或删除。

let user = {name: 'Alice',age: 30
};let handler = {get(target, prop) {if (prop === 'age') {return 'Restricted';}return target[prop];}
};let userProxy = new Proxy(user, handler);console.log(userProxy.name); // Alice
console.log(userProxy.age);  // Restricted

总结

Proxy 是一种强大的元编程工具,允许你拦截和修改对对象的基本操作。它使得我们可以在不改变原对象的情况下,自定义对象的行为,常用于数据校验、访问控制、日志记录等场景。结合 Reflect,可以在实现代理的同时保持默认操作的行为,提供更加灵活的对象操作。

2、Reflect

Reflect 是 ES6 引入的一个对象,它包含一组方法,用于操作 JavaScript 对象的基本行为。与 Proxy 一起,Reflect 提供了一种更加一致和简洁的方式来处理对象的操作。Reflect 主要用于在拦截操作时调用默认行为,确保与 Proxy 中的拦截方法相匹配,并提供了对这些操作的更加明确和一致的控制。

Reflect 的方法与 Proxy 中的陷阱方法一一对应,因此它们通常用于与 Proxy 结合使用,但 Reflect 也可以独立使用。

上面的话不好理解,其实就是说reflect就是简化的proxy,假如有一个数组arr=[1,2,3,4],我们想获取数组里面的值的时候是不是通过数组的下标0,1,2,3。那如果此时用户输入负数呢?负数是不是就是从后往前数arr[-1]应该是4,那我们怎么去写代码呢?

let arr=[1,2,3,4]
let handler = {get(target, prop) {prop=Number(prop)if(prop<0){prop=prop+target.length//将负数加上数组就变为正数 ;例如-1+4=3 此时索引下标等于3}//如果不为负数就只是用内置函数访问就可以了return Reflect.get(target,prop);  // 默认行为},};

Reflect 的基本概念

Reflect 是一个 内置对象,它提供了一些静态方法,这些方法与对象的操作相关,基本上是 Proxy 的默认行为(也就是常规的对象操作)。Reflect 的方法和操作符相似,但它们总是返回明确的结果,而不是自动修改目标对象。

Reflect 方法的特点:
  • 返回值始终是 布尔值返回目标对象的操作结果,而不是抛出异常。

  • Reflect 中的方法是 静态方法,不能通过实例化来调用,只能通过 Reflect.method 的方式调用。 

Reflect 常见方法

  • Reflect.get(target, prop)

    • 读取目标对象的指定属性值。

    • 返回目标对象中指定属性的值。如果属性不存在,则返回 undefined

  • Reflect.set(target, prop, value)

    • 设置目标对象的指定属性的值。

    • 返回 true 如果成功,false 如果失败。

  • Reflect.has(target, prop)

    • 检查目标对象是否包含指定的属性。

    • 返回 true 如果对象包含该属性,false 如果不包含。

  • Reflect.deleteProperty(target, prop)

    • 删除目标对象的指定属性。

    • 返回 true 如果成功删除,false 如果删除失败。

  • Reflect.apply(target, thisArg, args)

    • 调用目标函数,并传递参数。

    • 返回目标函数的执行结果。

  • Reflect.construct(target, args)

    • 使用目标构造函数创建一个新对象,并传递构造函数的参数。

    • 返回新创建的对象。

  • Reflect.getPrototypeOf(target)

    • 获取目标对象的原型。

    • 返回目标对象的原型对象。

  • Reflect.setPrototypeOf(target, prototype)

    • 设置目标对象的原型。

    • 返回 true 如果成功,false 如果失败。

  • Reflect.isExtensible(target)

    • 检查目标对象是否可扩展(即是否可以添加新的属性)。

    • 返回 true 如果可扩展,false 如果不可扩展。

  • Reflect.preventExtensions(target)

    • 禁止目标对象扩展,意味着不能再添加新的属性。

    • 返回 true 如果成功,false 如果失败。

  • Reflect.getOwnPropertyDescriptor(target, prop)

    • 获取目标对象的某个属性的描述符。

    • 返回属性的描述符对象,如果属性不存在,则返回 undefined

  • Reflect.defineProperty(target, prop, descriptor)

    • 定义目标对象的属性,并设置该属性的描述符。

    • 返回 true 如果成功,false 如果失败。

 Reflect 则是一个 API 的集合,它提供了对对象操作的默认行为,通常用于 简单、标准的操作,尤其是在 Proxy 中调用默认行为时。Reflect 的方法是静态的,可以直接操作对象。与 Proxy 中的拦截方法相比,Reflect 不会拦截操作,它只是返回目标对象的正常行为。例如:

Reflect 适用场景

Reflect 主要用于简化对对象操作的代码,尤其是在不需要复杂拦截逻辑时。例如,当我们需要 直接调用标准操作 时,可以使用 Reflect 来避免额外的代码复杂性。

 示例 1:使用 Reflect 进行简单的操作

let target = {name: 'Alice',age: 30
};// 使用 Reflect 执行标准操作
console.log(Reflect.get(target, 'name'));  // 'Alice'
Reflect.set(target, 'age', 35);            // 修改 age 为 35
console.log(target.age);  // 35

Proxy 与 Reflect 的结合

通常情况下,ProxyReflect 一起使用,可以让我们拦截对象的操作并调用默认行为。Proxy 中的陷阱方法(如 getset)可以通过调用 Reflect 来实现标准行为。

 示例 :结合使用 ProxyReflect

let target = {name: 'Alice',age: 30
};let handler = {get(target, prop, receiver) {console.log(`Accessing property: ${prop}`);return Reflect.get(...arguments);  // 使用 Reflect 调用默认行为},set(target, prop, value, receiver) {if (prop === 'name' && value === '') {console.log('Name cannot be empty!');return false;}return Reflect.set(...arguments);  // 使用 Reflect 调用默认行为}
};let proxy = new Proxy(target, handler);console.log(proxy.name);  // 'Alice'
proxy.age = 35;           // 设置 age 为 35
console.log(target.age);  // 35

总结:Proxy 和 Reflect 的选择

  • 使用 Proxy:当你需要拦截并修改对象的操作,或在某些操作上添加自定义逻辑时使用 ProxyProxy 适合复杂的、需要改变对象行为的场景。

  • 使用 Reflect:当你需要执行标准对象操作,并且不需要拦截或修改行为时,使用 Reflect 更加简洁。Reflect 可以用于与 Proxy 配合使用,也可以独立操作对象。

四、 JavaScript 模块化

模块化 是指将代码分割成小的、独立的部分(即模块),每个模块只负责自己的一部分功能,能够有效提高代码的可维护性、可复用性和团队协作效率。JavaScript 在早期并没有内建的模块系统,随着应用需求的增长,标准化模块化的支持逐步被引入。

模块化的优点

  • 可维护性:每个模块只关注自己的功能,代码更加清晰、易于管理。

  • 复用性:模块化的代码可以被重复使用,减少冗余代码。

  • 团队协作:不同开发人员可以在独立的模块中工作,提高团队开发效率。

模块化的方式

1.1 CommonJS(Node.js 模块化)

CommonJS 是最早用于 Node.js 的模块化规范,定义了如何加载和使用模块。在 CommonJS 中,模块通过 require 来加载,模块通过 module.exportsexports 导出。

  • 导出module.exportsexports

  • 导入require()

// math.js (模块)
module.exports.add = function(a, b) {return a + b;
};// app.js (主程序)
const math = require('./math');
console.log(math.add(2, 3));  // 5

优点

  • 适用于服务器端(Node.js 环境)。

  • 模块可以同步加载,适合文件系统中的模块加载。

缺点

  • 只能在 Node.js 或支持 CommonJS 的环境中使用。

  • 不适用于浏览器端的模块化。

1.2 ES6 模块化(ESM)

ES6 引入了官方标准的模块系统(ESM),它在浏览器和 Node.js 中都得到了广泛支持。ES6 模块使用 importexport 关键字来导入和导出模块。

  • 导出exportexport default

  • 导入import

// math.js (模块)
export function add(a, b) {return a + b;
}// app.js (主程序)
import { add } from './math';
console.log(add(2, 3));  // 5

优点

  • 是 JavaScript 官方标准,能够在浏览器和 Node.js 中都使用(现代浏览器和 Node.js 已经支持)。

  • 支持按需导入(代码分割),具有更好的优化空间。

  • 静态分析:ES6 模块允许编译器对依赖关系进行优化。

缺点

  • 在老版本浏览器中不支持(但可以通过 Babel 等工具进行转换)。

1.3 AMD(Asynchronous Module Definition)

AMD 是一个主要用于浏览器的模块加载规范,它支持异步加载模块。require.js 是最著名的实现。

  • 导出define()

  • 导入require()

// math.js (模块)
define(function() {return {add: function(a, b) {return a + b;}};
});// app.js (主程序)
require(['math'], function(math) {console.log(math.add(2, 3));  // 5
});

优点

  • 异步加载模块,适用于浏览器端,能够提高加载速度。

  • 支持按需加载,有利于减少首次加载的资源。

缺点

  • 语法复杂,使用起来不如 CommonJS 和 ES6 模块直观。

  • 主要用于浏览器端,不适用于 Node.js。

1.4 UMD(Universal Module Definition)

UMD 是一种兼容 CommonJS、AMD 和全局变量的模块化规范,它可以在不同环境下使用,兼容浏览器和 Node.js。

 定义模块:使用 define() 定义模块,并自动判断运行环境(CommonJS、AMD 或全局)。

(function (root, factory) {if (typeof define === 'function' && define.amd) {define(factory);  // AMD 模块} else if (typeof module === 'object' && module.exports) {module.exports = factory();  // CommonJS 模块} else {root.myModule = factory();  // 全局变量}
}(this, function () {return {add: function(a, b) {return a + b;}};
}));

优点

  • 兼容多种模块化规范,能够在不同环境中工作。

  • 适合库或框架的开发。

缺点

  • 由于兼容多种环境,代码较为复杂,使用时需要做兼容性处理。

 

相关文章:

前端面经-JS篇(四)--回调地狱、promise异步编程、Proxy 与 Reflect 、模块化

一、回调地狱 回调地狱&#xff08;Callback Hell&#xff09;&#xff0c;也称为回调地狱&#xff0c;是指在 JavaScript 中处理多个嵌套的异步回调函数时&#xff0c;代码结构变得非常难以阅读和维护的现象。 为什么会出现回调地狱&#xff1f; 回调地狱通常出现在需要执行…...

【oql】spark thriftserver内存溢出,使用oql查询导致oom的sql

eclipse memory analyzer (mat)软件内的OQL实现查询内促信息。 帮助信息&#xff1a;软件Help/Help Contents/Querying Heap Objects (OQL) 就是查询SparkExecuteStatementOperation 的statement 字段。 select objects s.statement from org.apache.spark.sql.hive.thriftser…...

算法设计与分析(基础)

问题列表 一、 算法的定义与特征&#xff0c;算法设计的基本步骤二、 算法分析的目的是什么&#xff1f;如何评价算法&#xff0c;如何度量算法的复杂性&#xff1f;三、 递归算法、分治法、贪婪法、动态规划法、回溯法的基本思想方法。四、 同一个问题&#xff0c;如TSP&#…...

爬虫学习——使用HTTP服务代理、redis使用、通过Scrapy实现分布式爬取

一、使用HTTP服务代理 由于网络环境、网站对用户的访问速度的限制等原因&#xff0c;使得爬取过程会出现IP被封禁&#xff0c;故使用代理可提高爬取速度。在Scrapy中提供了一个HttpProxyMiddleware专门用于进行爬虫代理设置。在使用该代理进行爬取操作时&#xff0c;需要先在ba…...

机器学习中的特征存储是什么?我需要一个吗?

本质上,特征存储是一个专用存储库,用于系统地存储和排列特征,主要用于数据科学家训练模型,并帮助已训练模型的应用程序进行预测。它是一个关键的聚合点,人们可以在此构建或修改从各种数据源提取的特征集合。此外,它还支持从这些特征组中创建和增强新的数据集,以满足处于…...

【C语言】C语言中的联合体与枚举类型

前言 在C语言中&#xff0c;联合体&#xff08;union&#xff09;和枚举&#xff08;enum&#xff09;是两种非常实用但又常被忽视的自定义数据类型。它们在内存管理、代码可读性以及程序设计的灵活性方面都有着独特的优势。今天&#xff0c;我们就来深入探讨一下联合体和枚举…...

Golang编程拒绝类型不安全

button-chen/containertypesafe-go: 使用泛型包装标准库的容器 list、ring、heap、sync.Pool 和 sync.Map&#xff0c;实现类型安全 简介 在 Go 中&#xff0c;标准库提供了多种容器类型&#xff0c;如 list、ring、heap、sync.Pool 和 sync.Map。然而&#xff0c;这些容器默认…...

炼锌废渣提取钴工艺流程

炼锌废渣中提取钴的工艺流程通常结合湿法冶金技术&#xff0c;针对废渣中钴与锌、铁、铜等金属的复杂共生特性&#xff0c;通过预处理、浸出、除杂、钴富集及提纯等步骤实现钴的高效回收。以下是典型工艺流程的详细说明&#xff1a; 一、预处理 炼锌废渣&#xff08;如锌浸出…...

Restful接口学习

一、为什么RESTful接口是数据开发的核心枢纽&#xff1f; 在数据驱动的时代&#xff0c;RESTful接口如同数据高速公路上的收费站&#xff0c;承担着数据交换的核心职责。数据工程师每天需要面对&#xff1a; 异构系统间的数据交互&#xff08;Hadoop集群 ↔ 业务系统&#xf…...

仿真每日一练 | ABAQUS应力松弛

应力松弛是弹性材料在应力作用下产生微塑性变形&#xff0c;并且逐渐积累&#xff0c;在保持应变或者位移不变的前提下&#xff0c;表现为应力逐渐下降的现象。今天介绍一个ABAQUS中应力松弛的相关案例&#xff0c;模型如下所示&#xff1a; 图1 模型认识 回顾一下ABAQUS的有限…...

智能电网第4期 | 电力设备全连接组网方案:从有线到无线无缝融合

随着新型电力系统建设的加速推进&#xff0c;电力设备通信网络正面临前所未有的挑战与机遇。在变电站自动化、输电线路监测、配电房智能化等场景中&#xff0c;传统通信方案已难以满足日益增长的连接需求&#xff1a; 环境复杂性&#xff1a;变电站强电磁干扰环境下需保障微秒级…...

Python 面向对象练习

不多bb了,直接上代码吧。 from pprint import pprint class Course:total_course []def __init__(self,name,id):self.name nameself.id idself.is_select FalseCourse.total_course.append(self)def __repr__(self):return (f"{__class__.__name__}("f"学…...

无感字符编码原址转换术——系统内存(Mermaid文本图表版/DeepSeek)

安全便捷无依赖&#xff0c;不学就会无感觉。 笔记模板由python脚本于2025-04-24 20:00:05创建&#xff0c;本篇笔记适合正在研究字符串编码制式的coder翻阅。 学习的细节是欢悦的历程 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不仅仅是知识的简单复述。 P…...

机器学习--线性回归模型

阅读本文之前&#xff0c;可以读一读下面这篇文章&#xff1a;终于有人把线性回归讲明白了 0、引言 线性回归作为统计学与机器学习的入门算法&#xff0c;以其简洁优雅的数学表达和直观的可解释性&#xff0c;在数据分析领域占据重要地位。这个诞生于19世纪的经典算法&#xf…...

HTML应用指南:利用GET请求获取微博签到位置信息

在当今数字化时代&#xff0c;社交媒体平台已成为人们日常生活中不可或缺的一部分。作为中国最受欢迎的社交平台之一&#xff0c;微博不仅为用户提供了一个分享信息、表达观点的空间&#xff0c;还通过其丰富的功能如签到服务&#xff0c;让用户能够记录自己生活中的点点滴滴。…...

如何检测Python项目哪些依赖库没有使用

要检测Python项目中哪些依赖库未被使用&#xff0c;可以采用以下方法&#xff1a; 1. 使用静态分析工具 vulture&#xff1a;静态分析工具&#xff0c;检测未使用的代码和导入 pip install vulture vulture your_project/pyflakes&#xff1a;检查未使用的导入语句 pip ins…...

数据仓库建设全解析!

目录 一、数据仓库建设的重要性 1. 整合企业数据资源 2. 支持企业决策制定 3. 提升企业竞争力 二、数据仓库建设的前期准备 1. 明确业务需求 2. 评估数据源 3. 制定项目计划 三、数据仓库建设的具体流程 1.需求分析​ 2.架构设计​ 3.数据建模​ 4.ETL 开发​ 5.…...

magic-api连接达梦数据库

引入依赖 然后手写驱动 <dependency><groupId>com.dameng</groupId><artifactId>DmJdbcDriver18</artifactId><version>8.1.1.193</version></dependency> jdbc:dm://127.0.0.1:5236?schemaSALES...

向量检索新选择:FastGPT + OceanBase,快速构建RAG

随着人工智能的快速发展&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;技术日益受到关注。向量数据库作为 RAG 系统的核心基础设施&#xff0c;堪称 RAG 的“记忆中枢”&#xff0c;其性能直接关系到大模型生成内容的精准度与…...

WHAT - 区分 Git PR 和 MR

文章目录 PR&#xff08;Pull Request&#xff09;MR&#xff08;Merge Request&#xff09;相同点总结 git pr 和 git mr 本质上都是「合并请求」的意思&#xff0c;但它们对应的是不同的平台术语。 PR&#xff08;Pull Request&#xff09; 平台&#xff1a;GitHub、Bitbuc…...

Axure复选框组件的深度定制:实现自定义大小、颜色与全选功能

在产品设计中&#xff0c;复选框作为用户与界面交互的重要元素&#xff0c;其灵活性直接影响到用户体验。本文将介绍如何利用Axure RP工具&#xff0c;通过高级技巧实现复选框组件的自定义大小、颜色调整&#xff0c;以及全选功能的集成&#xff0c;为产品原型设计增添更多可能…...

Datawhale AI春训营——用AI帮助老人点餐

详细内容见官网链接&#xff1a;用AI帮助老人点餐-活动详情 | Datawhale...

两段文本比对,高亮出差异部分

用法一:computed <div class"card" v-if"showFlag"><div class"info">*红色背景为已删除内容&#xff0c;绿色背景为新增内容</div><el-form-item label"与上季度比对&#xff1a;"><div class"comp…...

uniapp 仿小红书轮播图效果

通过对小红书的轮播图分析&#xff0c;可得出以下总结&#xff1a; 1.单张图片时容器根据图片像素定高 2.多图时轮播图容器高度以首图为锚点 3.比首图长则固高左右留白 4.比首图短则固宽上下留白 代码如下&#xff1a; <template><view> <!--轮播--><s…...

审计效率升级!快速匹配Excel报表项目对应的Word附注序号

财务审计报告一般包括&#xff1a;封面、报告正文、财务报表&#xff08;Excel工作簿&#xff09;以及对应的财务报表附注&#xff08;Word文档&#xff09;、事务所营业执照以及注册会计师证件。 在审计报告出具阶段&#xff0c;为各报表项目填充对应的Word附注序号&#xff…...

Python 中 `r` 前缀:字符串处理的“防转义利器”

# Python 中 r 前缀&#xff1a;字符串处理的“防转义利器” 在 Python 编程过程中&#xff0c;处理字符串时经常会遇到反斜杠 \ 带来的转义问题&#xff0c;而 r 前缀的出现有效解决了这一困扰。它不仅能处理反斜杠的转义&#xff0c;还在多种场景下发挥着重要作用。接下来&a…...

1️⃣6️⃣three.js_光源

16、光源 3D虚拟工厂在线体验 在 Three.js 中&#xff0c;环境光&#xff08;AmbientLight&#xff09;、点光源&#xff08;PointLight&#xff09;、平行光&#xff08;DirectionalLight&#xff09;、 聚光灯&#xff08;SpotLight&#xff09;、半球光&#xff08;Hemisph…...

AD16如何执行DRC检测

AD16如何执行DRC检测 DRC检测主要用来查看走线是否出现通断&#xff0c;以及是否出现短路。 1)、点击“Tools”---“Design Rule Check…” 2)、全部勾选 3)、勾选“Electrical”中的“Batch”选项&#xff0c;参与DRC检测 4)、勾选“Routing”中的“Batch”选项&#xff0c;…...

PostgreSQL性能优化实用技巧‌

PostgreSQL的性能优化需从‌索引设计、查询调优、参数配置、硬件资源‌等多维度入手。以下为实战中验证有效的优化策略&#xff0c;适用于高并发、大数据量等场 一、索引优化&#xff1a;精准加速查询‌ ‌1.选择正确的索引类型‌ ‌BRIN索引‌&#xff1a;对按时间或数值顺…...

Vue3 ref与props

ref 属性 与 props 一、核心概念对比 特性ref (标签属性)props作用对象DOM 元素/组件实例组件间数据传递数据流向父组件访问子组件/DOM父组件 → 子组件响应性直接操作对象单向数据流&#xff08;只读&#xff09;使用场景获取 DOM/调用子组件方法组件参数传递Vue3 变化不再自…...

SpringBoot | 构建客户树及其关联关系的设计思路和实践Demo

关注&#xff1a;CodingTechWork 引言 在企业级应用中&#xff0c;客户关系管理&#xff08;CRM&#xff09;是核心功能之一。客户树是一种用于表示客户之间层级关系的结构&#xff0c;例如企业客户与子公司、经销商与下级经销商等。本文将详细介绍如何设计客户树及其关联关系…...

SpringCloud——负载均衡

一.负载均衡 1.问题提出 上一篇文章写了服务注册和服务发现的相关内容。这里再提出一个新问题&#xff0c;如果我给一个服务开了多个端口&#xff0c;这几个端口都可以访问服务。 例如&#xff0c;在上一篇文章的基础上&#xff0c;我又新开了9091和9092端口&#xff0c;现在…...

Springboot3+ JDK21 升级踩坑指南

目录 GetMapping和 RequestBody 一起使用时&#xff0c;会把请求方式由GET变为POST 变更默认的httpClient feign 超时配置失效 GetMapping和 RequestBody 一起使用时&#xff0c;会把请求方式由GET变为POST 变更默认的httpClient 添加依赖 <dependency><groupId&g…...

Qt UDP组播实现与调试指南

在Qt中使用UDP组播(Multicast)可以实现高效的一对多网络通信。以下是关键步骤和示例代码: 一、UDP组播核心机制 组播地址:使用D类地址(224.0.0.0 - 239.255.255.255)TTL设置:控制数据包传播范围(默认1,同一网段)网络接口:指定发送/接收的物理接口二、发送端实现 /…...

idea连接远程服务器kafka

一、idea插件安装 首先idea插件市场搜索“kafka”进行插件安装 二、kafka链接配置 1、检查服务器kafka配置 配置链接前需要保证远程服务器的kafka配置里边有配置好服务器IP&#xff0c;以及开放好kafka端口9092&#xff08;如果有修改 过端口的开放对应端口就好&#xff09; …...

第十节:性能优化高频题-虚拟DOM与Diff算法优化

优化策略&#xff1a;同层比较、静态节点标记、最长递增子序列算法 Key的作用&#xff1a;精确识别节点身份 虚拟DOM与Diff算法深度优化策略解析 一、核心优化策略 同层比较机制 Diff算法仅对比同一层级的虚拟节点&#xff0c;避免跨层级遍历带来的性能损耗。 • 实现原理&am…...

vmware workstation的下载地址页面

Fusion and Workstation | VMware...

kubernetes》》k8s》》Dashboard

安装Dashboard 因为我的Kubernetes 版本是 v1.28.2 对应的 Dashboard V2.7.0 wget -O https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml # 因为默认是集群内访问的&#xff0c;需要追加 NodePort访问类型 vim recommended.yaml …...

软考:数值转换知识点详解

文章目录 1. 进制转换1.1 二进制&#xff08;Binary&#xff09;、八进制&#xff08;Octal&#xff09;、十进制&#xff08;Decimal&#xff09;、十六进制&#xff08;Hexadecimal&#xff09;之间的转换1.2 手动转换和计算方法1.3 使用编程语言进行进制转换 2. 数据类型转换…...

第15章:MCP服务端项目开发实战:性能优化

第15章:MCP服务端项目开发实战:性能优化 在构建和部署 MCP(Memory, Context, Planning)驱动的 AI Agent 系统时,性能和可扩展性是关键的考量因素。随着用户量、数据量和交互复杂度的增加,系统需要能够高效地处理请求,并能够平滑地扩展以应对更高的负载。本章将探讨 MCP…...

Windows申请苹果开发者测试证书Uniapp使用

注意事项 苹果设备,最好是iPhone XS以上,要不然下载不了Apple DeveloperopenSSL 要是V1版本的来生成证书,要不然HBuilder报错按步骤来,生成证书,生成标识符,添加测试设备,生成描述性文件注册苹果开发者账号 (如果有苹果账号直接登录) 苹果开发者官网 开通付费 点击右上…...

服务器数据恢复—NAS存储中raid5上层lv分区数据恢复案例

NAS数据恢复环境&#xff1a; QNAP TS-532X NAS设备中有两块1T的SSD固态硬盘和3块5T的机械硬盘。三块机械硬盘组建了一组RAID5阵列&#xff0c;两块固态硬盘组建RAID1阵列。划分了一个存储池&#xff0c;并通过精简LVM划分了7个lv。 NAS故障&#xff1a; 硬盘故障导致无法正常…...

uniapp跨平台开发---switchTab:fail page `/undefined` is not found

问题描述 在项目中新增了一个底部tab导航栏,点击底部tabBar,跳转失败,控制台打印错误信息switchTab:fail page /undefined is not found 排查思路 错误信息提示,switchTab跳转的页面路径变成了/undefined,排查新增的pages.json文件,发现pages,以及tabBar中的list均已经加入该导…...

详细讲解 QMutex 线程锁和 QMutexLocker 自动锁的区别

详细讲解 QMutex 线程锁和 QMutexLocker 自动锁的区别 下面我们详细拆解 Qt 中用于线程同步的两个核心类&#xff1a;QMutex 和 QMutexLocker。 &#x1f9f1; 一、什么是 QMutex&#xff1f; QMutex 是 Qt 中的互斥锁&#xff08;mutex&#xff09;类&#xff0c;用于防止多个…...

如何获取静态IP地址?完整教程

静态IP地址&#xff0c;因其固定不变的特性&#xff0c;在远程访问、服务器搭建、电商多开、游戏搬砖等场景中显得尤为重要。以下是获取静态IP地址的完整教程&#xff0c;涵盖家庭网络、企业网络和公网静态IP的配置方法&#xff1a; 一、什么是静态IP&#xff1f; 内网IP&…...

JavaScript 里创建对象

咱们来用有趣的方式探索一下 JavaScript 里创建对象的各种“魔法咒语”&#xff01; 想象一下&#xff0c;你是一位魔法工匠&#xff0c;想要在你的代码世界里创造各种奇妙的“魔法物品”&#xff08;也就是对象&#xff09;。你有好几种不同的配方和工具&#xff1a; 1. 随手…...

【华为HCIP | 华为数通工程师】821—多选解析—第十五页

多选794、以下关于高可用性网络特点的描述,正确的是哪些项? A、不会出现故障 B、不能频出现故障 C、一旦出现故障只通过人工干预恢复业务 D出现故障后能很快恢复 解析:高可用性网络拥有良好的可靠性,不间断转发NSF…...

Kaamel视角下的MCP安全最佳实践

在以AI为核心驱动的现代产品体系中&#xff0c;大模型逐渐从实验室走向生产环境&#xff0c;如何确保其在推理阶段的信息安全和隐私保护&#xff0c;成为各方关注的重点。Model Context Protocol&#xff08;MCP&#xff09; 作为一个围绕模型调用上下文进行结构化描述的协议&a…...

Kafka 命令行操作与 Spark-Streaming 核心编程总结

一、Kafka 命令行操作详解 1.创建 Topic 命令格式&#xff1a; kafka-topics.sh --create --zookeeper <zk节点列表> --topic <主题名> --partitions <分区数> --replication-factor <副本数> 参数说明&#xff1a; 分区数&#xff08;partitions…...

【华为OD机试真题】428、连续字母长度 | 机试真题+思路参考+代码解析(E卷)(C++)

文章目录 一、题目题目描述输入输出样例1样例2 一、代码与思路&#x1f9e0;C语言思路✅C代码 一、题目 参考&#xff1a;https://sars2025.blog.csdn.net/article/details/139492358 题目描述 ◎ 给定一个字符串&#xff0c;只包含大写字母&#xff0c;求在包含同一字母的子串…...