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

ES6 面试题及详细答案 80题 (62-80)-- 模块化与其他特性

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。

前后端面试题-专栏总目录

在这里插入图片描述

文章目录

  • 一、本文面试题目录
      • 62. ES6模块与CommonJS模块的区别是什么?
      • 63. 如何使用export导出模块成员?有哪些导出方式?
      • 64. import命令的作用是什么?如何导入模块?
        • 基本用法:
      • 65. 什么是默认导出(export default)?与命名导出有何区别?
        • 与命名导出的区别:
      • 66. 如何实现模块的动态导入(dynamic import)?
        • 基本用法:
        • 与`async/await`结合(更简洁):
        • 应用场景:
      • 67. 模块的循环依赖问题如何解决?
        • 1. ES6模块的自然处理
        • 2. CommonJS的处理与解决
        • 通用原则:
      • 68. 什么是迭代器(Iterator)?它有什么作用?
        • 迭代器的结构:
        • 作用:
      • 69. 可迭代对象(Iterable)有哪些?如何判断一个对象是否可迭代?
        • 常见的可迭代对象:
        • 判断对象是否可迭代:
      • 70. for...of循环与for...in循环的区别是什么?
      • 71. 什么是Proxy?它可以实现哪些功能?
        • 基本语法:
        • 可实现的功能:
      • 72. Reflect对象的作用是什么?它与Object方法有何区别?
        • 作用:
        • 与Object方法的区别:
      • 73. ES6的新增数据结构中,哪些可以用于解决内存泄漏问题?为什么?
        • 弱引用的特性:
        • 应用场景:
      • 74. 什么是对象的可枚举性(enumerable)?如何判断属性是否可枚举?
        • 特性说明:
        • 判断属性是否可枚举:
      • 75. 什么是Promise的穿透?如何避免?
        • 示例:
        • 如何避免:
      • 76. Generator函数如何实现异步操作?
        • 实现原理:
        • 示例(手动执行):
        • 自动执行(使用co模块):
      • 77. Generator函数与Promise结合使用有什么优势?
      • 78. 什么是数组的迭代方法?ES6新增了哪些?
        • ES6新增的数组迭代方法:
      • 79. 什么是对象的浅拷贝和深拷贝?ES6中如何实现?
        • 浅拷贝(Shallow Copy):
        • 深拷贝(Deep Copy):
        • ES6中实现方式:
      • 80. async/await相比Promise有哪些优势?
  • 二、80道ES6 面试题目录列表

一、本文面试题目录

62. ES6模块与CommonJS模块的区别是什么?

ES6模块(ESM)和CommonJS(CJS)是JavaScript的两种模块规范,核心区别如下:

特性ES6模块CommonJS模块
语法使用import/export使用require()/module.exports
加载时机编译时静态分析(预加载)运行时动态加载
输出方式输出值的引用(动态绑定)输出值的拷贝(静态绑定)
this指向模块顶层thisundefined模块顶层this指向当前模块
运行环境浏览器和Node.js(需配置)主要用于Node.js
循环依赖处理更优(基于引用)可能导致未完成的模块
异步加载支持import()动态异步加载同步加载(阻塞执行)

示例:

// ES6模块(导出)
export const a = 1;
export function fn() {}// ES6模块(导入)
import { a, fn } from './module.js';// CommonJS(导出)
module.exports = { a: 1, fn() {} };// CommonJS(导入)
const { a, fn } = require('./module.js');

63. 如何使用export导出模块成员?有哪些导出方式?

export用于将模块内的成员暴露给外部,主要有以下导出方式:

  1. 命名导出(单个导出)
    直接在声明前加export

    export const name = "模块";
    export function greet() { return "Hello"; }
    export class MyClass {}
    
  2. 命名导出(集中导出)
    先声明成员,再集中导出:

    const age = 18;
    function add(a, b) { return a + b; }export { age, add }; // 注意用大括号包裹
    
  3. 导出时重命名
    使用as关键字重命名导出的成员:

    const a = 10;
    export { a as num, add as sum }; // 外部需用num和sum导入
    
  4. 默认导出(export default)
    每个模块只能有一个默认导出,用于导出模块的主要成员:

    // 方式1:直接导出
    export default function() { return "默认导出"; }// 方式2:先声明后导出
    const defaultObj = { name: "默认" };
    export default defaultObj;
    

注意:命名导出和默认导出可同时使用,但默认导出只能有一个。

64. import命令的作用是什么?如何导入模块?

import命令用于从其他模块导入成员,实现模块间的依赖管理。

基本用法:
  1. 导入命名导出的成员
    使用{}指定要导入的成员名,需与导出时一致:

    // 导入单个或多个成员
    import { name, greet } from './module.js';// 导入时重命名(避免冲突)
    import { name as moduleName, greet as sayHello } from './module.js';
    
  2. 导入默认导出的成员
    无需大括号,可自定义名称:

    import myFunc from './module.js'; // 导入默认导出的函数
    import defaultObj from './other.js'; // 导入默认导出的对象
    
  3. 同时导入命名成员和默认成员

    import defaultFunc, { name, age } from './module.js';
    
  4. 导入整个模块(命名空间导入)
    使用* as将模块所有成员导入为一个对象:

    import * as Module from './module.js';
    console.log(Module.name); // 访问命名导出成员
    Module.greet(); // 调用命名导出方法
    

注意:

  • import命令具有提升性,会被提升到模块顶部执行。
  • 浏览器中使用时,脚本标签需添加type="module"
    <script type="module" src="main.js"></script>
    

65. 什么是默认导出(export default)?与命名导出有何区别?

默认导出(export default) 是模块的主要导出方式,用于指定模块的默认成员,每个模块只能有一个默认导出。

与命名导出的区别:
特性默认导出(export default)命名导出(export)
数量限制每个模块只能有1个每个模块可以有多个
导入语法无需大括号,可自定义名称必须用{},且名称需与导出一致(或用as重命名)
导出场景模块主要功能(如一个核心函数/类)模块的次要功能或多个成员
示例export default function() {}export const a = 1;
导入示例import myFunc from './m.js'import { a } from './m.js'

示例:

// 模块 file.js
// 默认导出(1个)
export default class User {constructor(name) { this.name = name; }
}// 命名导出(多个)
export const version = "1.0";
export function formatName(name) { return name.toUpperCase(); }// 导入模块
import User, { version, formatName } from './file.js';
const user = new User("Alice");
console.log(version); // "1.0"

66. 如何实现模块的动态导入(dynamic import)?

动态导入(import())是ES2020引入的特性,允许在运行时异步加载模块,返回一个Promise。

基本用法:
// 动态导入模块,返回Promise
import('./module.js').then((module) => {// 模块加载成功,使用导出的成员console.log(module.name);module.greet();}).catch((error) => {// 处理加载失败console.error("模块加载失败:", error);});
async/await结合(更简洁):
async function loadModule() {try {const module = await import('./module.js');module.doSomething();} catch (error) {console.error(error);}
}loadModule();
应用场景:
  1. 按需加载:只在需要时加载模块(如点击按钮后),减少初始加载时间。

    document.getElementById('btn').addEventListener('click', async () => {const module = await import('./heavy-module.js');module.execute();
    });
    
  2. 条件加载:根据不同条件加载不同模块。

    async function loadFeature(feature) {let module;if (feature === 'chart') {module = await import('./chart.js');} else {module = await import('./table.js');}module.render();
    }
    

注意:动态导入返回的是模块对象的Promise,与静态import的编译时加载不同。

67. 模块的循环依赖问题如何解决?

模块循环依赖指A模块依赖B模块,同时B模块依赖A模块的情况。ES6模块和CommonJS通过不同机制处理,解决方式如下:

1. ES6模块的自然处理

ES6模块基于“引用绑定”,循环依赖时:

  • 模块在加载过程中会先创建一个“未完成”的模块实例。
  • 依赖方可以访问已声明的成员,未声明的成员暂时为undefined

解决关键:将依赖使用放在成员声明之后

示例:

// a.js
import { b } from './b.js';
export const a = 1;
console.log('a.js中b的值:', b); // 可访问b(此时b可能为undefined,取决于执行顺序)// b.js
import { a } from './a.js';
export const b = 2;
console.log('b.js中a的值:', a); // 可访问a
2. CommonJS的处理与解决

CommonJS基于“值拷贝”,循环依赖时可能获取到未完成的模块导出。

解决方式:

  • 将依赖引用延迟到函数内部(避免模块加载时立即访问)。
    // a.js
    const { b } = require('./b.js');
    exports.a = 1;
    // 延迟访问b
    exports.logB = () => console.log(b);// b.js
    const { a, logB } = require('./a.js');
    exports.b = 2;
    console.log(a); // undefined(此时a尚未导出)
    logB(); // 调用时a已导出,可正常访问b
    
通用原则:
  1. 减少模块间的耦合,避免不必要的循环依赖。
  2. 将共享逻辑提取到第三方模块,打破循环。
  3. 延迟使用依赖(如在函数中访问,而非模块顶层)。

68. 什么是迭代器(Iterator)?它有什么作用?

迭代器(Iterator) 是ES6引入的一种接口机制,为各种数据结构提供统一的遍历方式。

迭代器的结构:

迭代器是一个对象,必须包含next()方法,该方法返回一个对象{ value: 当前值, done: 是否完成 }

  • value:当前遍历到的值。
  • done:布尔值,true表示遍历结束。
作用:
  1. 统一遍历接口:让不同数据结构(数组、Set、Map等)可以用相同的方式遍历。
  2. 支持for...of循环:所有可迭代对象(实现迭代器接口)都能被for...of遍历。
  3. 惰性计算:迭代器按需生成值,适合处理大数据或无限序列。

示例:自定义迭代器

// 生成1-5的迭代器
const createIterator = () => {let count = 1;return {next() {if (count <= 5) {return { value: count++, done: false };}return { value: undefined, done: true };}};
};const iterator = createIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
// ... 直到done为true

69. 可迭代对象(Iterable)有哪些?如何判断一个对象是否可迭代?

可迭代对象(Iterable) 是指实现了迭代器接口([Symbol.iterator]方法)的数据结构。

常见的可迭代对象:
  1. 原生数据结构:数组(Array)、字符串(String)、Set、Map。
  2. 类数组对象: arguments、NodeList。
  3. 自定义实现[Symbol.iterator]的对象。

示例:

// 数组是可迭代对象
for (const item of [1, 2, 3]) { console.log(item); }// 字符串是可迭代对象
for (const char of "hello") { console.log(char); }// Set是可迭代对象
for (const val of new Set([1, 2])) { console.log(val); }
判断对象是否可迭代:

检查对象是否有[Symbol.iterator]属性,且该属性是函数:

function isIterable(obj) {return obj != null && typeof obj[Symbol.iterator] === 'function';
}console.log(isIterable([1, 2])); // true(数组)
console.log(isIterable("test")); // true(字符串)
console.log(isIterable({})); // false(普通对象默认不可迭代)

自定义可迭代对象:

const myIterable = {items: [10, 20, 30],[Symbol.iterator]() {let index = 0;return {next: () => {if (index < this.items.length) {return { value: this.items[index++], done: false };}return { done: true };}};}
};// 可被for...of遍历
for (const item of myIterable) {console.log(item); // 10 → 20 → 30
}

70. for…of循环与for…in循环的区别是什么?

for...offor...in是两种不同的循环方式,核心区别如下:

特性for…of循环for…in循环
遍历对象可迭代对象(数组、Set、Map等)普通对象(遍历键名)
遍历内容遍历值(value)遍历键名(key)
适用场景遍历数据结构的元素遍历对象的属性
是否遍历原型链不遍历会遍历继承的可枚举属性
数组索引不关注索引,直接取元素遍历数组索引(字符串类型)
示例for (const item of arr) {}for (const key in obj) {}

示例:

const arr = [10, 20, 30];
const obj = { a: 1, b: 2 };// for...of遍历数组的值
for (const item of arr) {console.log(item); // 10 → 20 → 30
}// for...in遍历对象的键名
for (const key in obj) {console.log(key); // "a" → "b"
}// for...in遍历数组(不推荐,会遍历索引和可能的扩展属性)
Array.prototype.extra = "test";
for (const key in arr) {console.log(key); // "0" → "1" → "2" → "extra"(继承的属性)
}

最佳实践:

  • 遍历数组、Set、Map等用for...of
  • 遍历普通对象的属性用for...in,且通常配合hasOwnProperty过滤继承属性:
    for (const key in obj) {if (obj.hasOwnProperty(key)) {console.log(key); // 只输出自身属性}
    }
    

71. 什么是Proxy?它可以实现哪些功能?

Proxy 是ES6引入的特性,用于创建一个对象的代理,从而实现对对象的拦截和自定义操作(如属性访问、赋值、删除等)。

基本语法:
const proxy = new Proxy(target, handler);
  • target:被代理的目标对象。
  • handler:拦截器对象,定义各种操作的拦截方法。
可实现的功能:
  1. 属性访问拦截(get)
    拦截对象属性的读取操作。

    const obj = { name: "Alice" };
    const proxy = new Proxy(obj, {get(target, prop) {if (prop in target) {return target[prop];}return `属性${prop}不存在`; // 自定义不存在属性的返回值}
    });
    console.log(proxy.name); // "Alice"
    console.log(proxy.age); // "属性age不存在"
    
  2. 属性赋值拦截(set)
    拦截对象属性的赋值操作,可用于数据验证。

    const user = { age: 0 };
    const userProxy = new Proxy(user, {set(target, prop, value) {if (prop === "age" && (value < 0 || value > 150)) {throw new Error("年龄必须在0-150之间");}target[prop] = value;return true; // 表示赋值成功}
    });
    userProxy.age = 25; // 成功
    // userProxy.age = 200; // 报错
    
  3. 其他拦截操作

    • deleteProperty:拦截属性删除。
    • has:拦截in运算符。
    • apply:拦截函数调用。
    • construct:拦截new操作符。

示例(拦截函数调用):

const sum = (a, b) => a + b;
const sumProxy = new Proxy(sum, {apply(target, thisArg, args) {console.log(`调用sum(${args[0]}, ${args[1]})`);return target(...args) * 2; // 拦截并修改返回值}
});
console.log(sumProxy(1, 2)); // 输出:调用sum(1, 2) → 6(原结果3×2)

应用场景:数据验证、日志记录、权限控制、虚拟DOM实现等。

72. Reflect对象的作用是什么?它与Object方法有何区别?

Reflect 是ES6引入的内置对象,提供了一系列操作对象的方法,与Object的部分方法功能相似,但设计更统一。

作用:
  1. 统一对象操作接口:将对象的操作(如属性访问、赋值、删除等)集中到Reflect对象上。
  2. 替代Object的部分方法:提供更合理的返回值和参数设计。
  3. 配合Proxy使用Reflect方法的参数与Proxy拦截器的参数一致,便于在拦截中调用原始操作。
与Object方法的区别:
特性ReflectObject
返回值操作是否成功(如Reflect.set()返回布尔值)通常返回操作后的对象或undefined
参数顺序目标对象在前,属性名在后(如Reflect.get(obj, prop)部分方法参数顺序不同(如Object.getOwnPropertyDescriptor(obj, prop)
函数式风格全部为函数方法(如Reflect.has(obj, prop)替代prop in obj混合函数方法和运算符
报错处理操作失败返回false(不报错)操作失败可能抛出错误
与Proxy配合完美匹配Proxy拦截器的参数,便于转发操作需手动处理参数适配

示例:

const obj = { name: "Bob" };// 1. 属性赋值
console.log(Reflect.set(obj, "age", 20)); // true(操作成功)
console.log(obj.age); // 20// 2. 属性检查(替代in运算符)
console.log(Reflect.has(obj, "name")); // true// 3. 与Proxy配合
const proxy = new Proxy(obj, {get(target, prop) {// 用Reflect转发原始操作return Reflect.get(target, prop);}
});

73. ES6的新增数据结构中,哪些可以用于解决内存泄漏问题?为什么?

ES6中WeakSetWeakMap可用于解决内存泄漏问题,核心原因是它们对对象的引用是弱引用

弱引用的特性:
  • 不会阻止垃圾回收(GC):若对象仅被WeakSet/WeakMap引用,且无其他强引用,该对象会被GC回收,对应的键/值也会从集合中自动移除。
  • 无法枚举:没有size属性和遍历方法,避免依赖可能被回收的元素。
应用场景:
  1. 临时存储DOM节点
    避免删除DOM节点后,因仍被Set/Map引用而无法回收:

    const wm = new WeakMap();
    const div = document.createElement('div');
    wm.set(div, '临时数据'); // 弱引用DOM节点// 当div被移除且无其他引用时,会被GC回收,wm中对应的键值对也会消失
    
  2. 缓存对象数据
    缓存不会阻止原对象被回收:

    const cache = new WeakMap();
    function getCache(obj) {if (!cache.has(obj)) {cache.set(obj, computeData(obj)); // 缓存计算结果}return cache.get(obj);
    }
    

相比之下,Set和Map对对象的引用是强引用,即使对象在外部无引用,只要被Set/Map引用就不会被回收,可能导致内存泄漏。

74. 什么是对象的可枚举性(enumerable)?如何判断属性是否可枚举?

可枚举性(enumerable) 是对象属性的一个特性,决定该属性是否会被for...in循环、Object.keys()等方法遍历到。

特性说明:
  • 可枚举属性:会被遍历方法(如for...inObject.keys())包含。
  • 不可枚举属性:不会被遍历方法包含(如对象的toStringhasOwnProperty等继承属性)。

默认情况下:

  • 通过对象字面量定义的属性是可枚举的。
  • 通过Object.defineProperty定义的属性默认不可枚举。
判断属性是否可枚举:

使用Object.prototype.propertyIsEnumerable(prop)方法:

const obj = { a: 1 };
// 自定义属性默认可枚举
console.log(obj.propertyIsEnumerable('a')); // true// 继承的属性不可枚举
console.log(obj.propertyIsEnumerable('toString')); // false// 通过defineProperty定义不可枚举属性
Object.defineProperty(obj, 'b', {value: 2,enumerable: false // 显式设置不可枚举
});
console.log(obj.propertyIsEnumerable('b')); // false
console.log(Object.keys(obj)); // ['a'](b未被枚举)

应用场景:筛选对象的可枚举属性,或定义内部使用的不可枚举属性(避免被意外遍历)。

75. 什么是Promise的穿透?如何避免?

Promise的穿透指当then()方法未传入回调函数(或传入非函数值)时,Promise会将结果“穿透”到下一个then()的现象。

示例:
Promise.resolve(1).then() // 未传回调.then(2) // 传入非函数.then((value) => {console.log(value); // 输出1(结果穿透到此处)});

原理:then()方法期望接收函数参数,若未传入,会默认生成一个“透传”函数((value) => value),将结果传递给下一个then()

如何避免:
  1. 始终传入函数作为回调,即使是空函数:

    Promise.resolve(1).then(() => {}) // 显式传入空函数,阻止穿透.then((value) => {console.log(value); // undefined(不再穿透)});
    
  2. 使用catch()集中处理错误,避免错误穿透到意外的then()

    Promise.reject(new Error("出错")).then() // 错误穿透.catch((error) => {console.log(error.message); // 输出"出错"(正确捕获)});
    

穿透本身不是bug,而是Promise的设计特性,合理利用可简化代码(如跳过中间处理),但需注意避免意外行为。

76. Generator函数如何实现异步操作?

Generator函数通过yield暂停执行和next()恢复执行的特性,可将异步操作以同步的形式编写,实现异步流程控制。

实现原理:
  1. 将异步操作包装成返回Promise的函数。
  2. 在Generator函数中用yield调用异步操作,暂停等待结果。
  3. 通过自动执行器(如co模块)或手动调用next(),在异步操作完成后恢复执行,并将结果传入。
示例(手动执行):
// 异步操作函数
function fetchData(url) {return new Promise((resolve) => {setTimeout(() => {resolve(`数据:${url}`);}, 1000);});
}// Generator函数处理异步流程
function* asyncGenerator() {console.log("开始请求");const data1 = yield fetchData("url1"); // 暂停,等待异步结果console.log(data1);const data2 = yield fetchData("url2"); // 继续执行,等待下一个结果console.log(data2);return "完成";
}// 手动执行Generator
const gen = asyncGenerator();
gen.next().value // 启动第一个异步操作.then((result1) => {return gen.next(result1).value; // 将结果传入,执行到下一个yield}).then((result2) => {gen.next(result2); // 完成最后一步});
自动执行(使用co模块):
const co = require('co');
co(asyncGenerator).then((result) => {console.log(result); // "完成"
});

Generator函数让异步代码的逻辑更清晰,避免了Promise的链式调用嵌套。

77. Generator函数与Promise结合使用有什么优势?

Generator函数与Promise结合,结合了两者的优势,成为更优雅的异步编程方案:

  1. 同步化的异步代码
    Generator的yield可暂停等待Promise完成,让异步代码看起来像同步代码,可读性更高:

    // 纯Promise(链式调用)
    fetchData1().then(data1 => {return fetchData2(data1);}).then(data2 => {return fetchData3(data2);});// Generator+Promise(同步风格)
    function* gen() {const data1 = yield fetchData1();const data2 = yield fetchData2(data1);const data3 = yield fetchData3(data2);
    }
    
  2. 统一的错误处理
    可通过try/catch捕获所有yield的Promise错误,无需在每个then()后加catch()

    function* gen() {try {const data1 = yield fetchData1(); // 若失败,直接进入catchconst data2 = yield fetchData2(data1);} catch (error) {console.error("出错:", error); // 统一处理所有错误}
    }
    
  3. 灵活的流程控制
    可在next()方法中传入参数,动态调整异步流程:

    function* gen() {const action = yield prompt("请输入操作"); // 等待用户输入if (action === "save") {yield saveData(); // 根据输入执行不同异步操作} else {yield loadData();}
    }
    
  4. 天然支持迭代
    适合处理序列异步操作(如分页加载数据):

    function* loadPages() {let page = 1;while (true) {const data = yield fetchPage(page); // 循环加载分页数据if (!data) break;page++;}
    }
    

这种组合是async/await的前身,async/await可视为Generator+Promise的语法糖。

78. 什么是数组的迭代方法?ES6新增了哪些?

数组的迭代方法指用于遍历数组并对元素执行操作的方法,通常接收回调函数,返回新数组或其他值(不修改原数组)。

ES6新增的数组迭代方法:
  1. Array.prototype.find()
    返回数组中第一个满足回调函数条件的元素,无则返回undefined

    const numbers = [10, 20, 30, 40];
    const found = numbers.find(num => num > 25);
    console.log(found); // 30
    
  2. Array.prototype.findIndex()
    返回数组中第一个满足条件的元素的索引,无则返回-1

    const index = numbers.findIndex(num => num > 25);
    console.log(index); // 2(30的索引)
    
  3. Array.prototype.includes()
    判断数组是否包含指定值,返回布尔值(与indexOf相比,可识别NaN)。

    console.log(numbers.includes(20)); // true
    console.log(numbers.includes(50)); // falseconst arr = [NaN];
    console.log(arr.includes(NaN)); // true(indexOf(NaN)返回-1)
    
  4. Array.prototype.flat()
    将嵌套数组“扁平化”,返回新数组(默认扁平化一层)。

    const nested = [1, [2, [3]]];
    console.log(nested.flat()); // [1, 2, [3]](扁平化一层)
    console.log(nested.flat(2)); // [1, 2, 3](扁平化两层)
    
  5. Array.prototype.flatMap()
    先对每个元素执行map,再对结果执行flat(1)(相当于map+flat的组合)。

    const words = ["hello world", "es6 features"];
    const letters = words.flatMap(word => word.split(" "));
    console.log(letters); // ["hello", "world", "es6", "features"]
    

这些方法增强了数组的遍历和处理能力,使代码更简洁。

79. 什么是对象的浅拷贝和深拷贝?ES6中如何实现?

浅拷贝(Shallow Copy):

只复制对象的顶层属性,若属性值是对象(引用类型),则复制的是引用(修改拷贝后的对象会影响原对象)。

深拷贝(Deep Copy):

完全复制对象的所有层级,包括嵌套对象,拷贝后与原对象完全独立(修改互不影响)。

ES6中实现方式:
  1. 浅拷贝

    • 扩展运算符(...

      const obj = { a: 1, b: { c: 2 } };
      const shallowCopy = { ...obj };
      shallowCopy.b.c = 3;
      console.log(obj.b.c); // 3(嵌套对象受影响)
      
    • Object.assign()

      const shallowCopy = Object.assign({}, obj);
      
    • 数组浅拷贝

      const arr = [1, [2, 3]];
      const arrCopy = [...arr]; // 或 arr.slice()
      
  2. 深拷贝

    • 简易实现(JSON方法)
      适用于无函数、DateRegExp等特殊类型的对象。

      const obj = { a: 1, b: { c: 2 } };
      const deepCopy = JSON.parse(JSON.stringify(obj));
      deepCopy.b.c = 3;
      console.log(obj.b.c); // 2(不受影响)
      
    • 自定义递归函数
      处理特殊类型(需递归复制嵌套对象)。

      function deepClone(target) {if (typeof target !== 'object' || target === null) {return target; // 非对象直接返回}let clone = Array.isArray(target) ? [] : {};for (const key in target) {if (target.hasOwnProperty(key)) {clone[key] = deepClone(target[key]); // 递归拷贝}}return clone;
      }
      
    • 使用structuredClone()(ES2022):
      原生支持深拷贝,支持大部分内置类型。

      const deepCopy = structuredClone(obj);
      

80. async/await相比Promise有哪些优势?

async/await 是基于 Promise 的语法糖,它在保留 Promise 异步特性的同时,解决了 Promise 链式调用的一些痛点,主要优势如下:

  1. 代码结构更接近同步逻辑,可读性更高
    Promise 依赖链式调用(then()),多层嵌套层级较深时会形成“回调链”;而 async/await 用同步代码的写法表达异步流程,逻辑更直观。

    // Promise 链式调用
    fetchUser().then(user => {return fetchPosts(user.id).then(posts => {return fetchComments(posts[0].id).then(comments => {console.log(comments);});});});// async/await 写法
    async function getComments() {const user = await fetchUser();const posts = await fetchPosts(user.id);const comments = await fetchComments(posts[0].id);console.log(comments);
    }
    
  2. 错误处理更统一、简洁
    Promise 需要在每个 then() 后单独处理错误(或在链尾用 catch());async/await 可通过 try/catch 捕获所有异步操作的错误,包括同步代码错误。

    // Promise 错误处理
    fetchData().then(data => process(data)).catch(err => console.error("获取数据失败:", err)).then(result => saveResult(result)).catch(err => console.error("保存数据失败:", err));// async/await 错误处理
    async function handleData() {try {const data = await fetchData();const result = await process(data);await saveResult(result);} catch (err) {console.error("操作失败:", err); // 统一捕获所有步骤的错误}
    }
    
  3. 流程控制更灵活
    在异步流程中插入条件判断、循环等逻辑时,async/await 比 Promise 更自然,无需嵌套 then() 或使用额外变量。

    async function loadResources(needExtra) {const base = await loadBase();if (needExtra) {const extra = await loadExtra(); // 条件性异步操作return { ...base, ...extra };}return base;
    }
    
  4. 调试体验更友好
    调试 async/await 代码时,可以直接在 await 语句处断点,变量作用域清晰;而 Promise 的 then() 回调函数会形成独立作用域,调试时变量查看更复杂。

  5. 返回值处理更直接
    async/await 中,await 直接返回 Promise 的结果值,无需通过 then() 的回调参数获取;而 Promise 需要嵌套回调才能处理上一步的结果。

总之,async/await 并未替代 Promise,而是在其基础上提供了更优雅的语法,让异步代码的编写和维护成本显著降低,是目前 JavaScript 异步编程的最佳实践。

二、80道ES6 面试题目录列表

文章序号ES6 80道
1 ES6面试题及详细答案80道(01-05)
2 ES6面试题及详细答案80道(06-12)
3 ES6面试题及详细答案80道(13-21)
4 ES6面试题及详细答案80道(22-32)
5 ES6面试题及详细答案80道(33-40)
6 ES6面试题及详细答案80道(41-54)
7 ES6面试题及详细答案80道(55-61)
8 ES6面试题及详细答案80道(62-80)

文章转载自:

http://t9fOrsIZ.zgnng.cn
http://8zJE6xkF.zgnng.cn
http://WH449Orx.zgnng.cn
http://qfVzSPG6.zgnng.cn
http://QKzh3iwu.zgnng.cn
http://rKmoZBVG.zgnng.cn
http://jWdksIDI.zgnng.cn
http://GcmZMlXO.zgnng.cn
http://JEhRXt3i.zgnng.cn
http://p8brux28.zgnng.cn
http://B6wBcBJW.zgnng.cn
http://rgQEw29W.zgnng.cn
http://ZbwYn9ho.zgnng.cn
http://UYSF1ArJ.zgnng.cn
http://GTTOwgus.zgnng.cn
http://Q3xjl1HY.zgnng.cn
http://pckL0Dny.zgnng.cn
http://iynzO0DP.zgnng.cn
http://qdCRsqKM.zgnng.cn
http://OxpUAGhy.zgnng.cn
http://AwhR6I52.zgnng.cn
http://Rt4Q0OUk.zgnng.cn
http://NyB7UWyG.zgnng.cn
http://yqn0kdQv.zgnng.cn
http://xxCF9i85.zgnng.cn
http://TdjAlxLu.zgnng.cn
http://mRwuPyYQ.zgnng.cn
http://4rZx7Lx9.zgnng.cn
http://7CbQ8SYe.zgnng.cn
http://BydSrCYa.zgnng.cn
http://www.dtcms.com/a/384285.html

相关文章:

  • D005 vue+django音乐推荐大数据|推荐算法|多权限| 可视化|完整源码
  • 工厂自动化正从 “人工堆叠” 向 “设备替代” 快速转变
  • 栈-227.基本计算器II-力扣(LeetCode)
  • python 自动化从入门到实战-做一个超实用的二维码生成工具(5)
  • 今天开始学习新内容“服务集群与自动化”--crond服务、--syslog服务以及DHCP协议
  • LeetCode 379 - 电话目录管理系统(Design Phone Directory)
  • 《Python 自动化实战:从零构建一个文件同步工具》
  • 风险规则引擎-RPA 作为自动化依赖业务决策流程的强大工具
  • Vue: 模板引用 (Template Refs)
  • Web2 vs Web3
  • 上海交大3D体素赋能具身导航!BeliefMapNav:基于3D体素信念图的零样本目标导航
  • SAP-ABAP:SAP ABAP中的JSON序列化利器:/UI2/CL_JSON=>SERIALIZE完全指南实例详解
  • stm32 can错误处理问题
  • python 自动化从入门到实战-开发一个随机点名系统(6)
  • 如何用 GitHub Actions 为 FastAPI 项目打造自动化测试流水线?
  • godot+visual studio配置c#环境
  • 文件查找失败:‘module‘ at node_modules\sass\sass.node.js:7
  • (一)Vue.js 框架简介
  • Vue 中在 Vue 项目中引入 Cesium 并加载本地离线地图
  • Node.js ≥ 18 安装教程
  • 第四阶段C#通讯开发-4:网络通讯_网络协议
  • 如何实现测试环境隔离临时数据库(pytest+SQLite)
  • 像连接mysql一样连接mongodb
  • 从零开始搞定C++类和对象(下)
  • 企业级实战:构建基于Qt、C++与YOLOv8的模块化工业视觉检测系统
  • TexturePacker 打包 TextAtlas:按顺序排列
  • MyBatis 核心概念与实践指南:从代理模式到性能优化
  • 全链路性能优化实战:从Jmeter压测到系统调优
  • 《华为变革法:打造可持续进步的组织》读书笔记
  • VS Code 通用配置分享(Cursor / QCode / Trae 通用)