跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.js大前端

ES6 核心语法全解析:let 到 Async-Await 实战避坑指南

ES6 语法涵盖 let/const 作用域、箭头函数 this 指向、解构赋值等核心特性,旨在简化代码并解决旧版痛点。文章通过对比 var 与 let/const、演示 Promise 与 Async-Await 异步处理、结合 Set/Map 数据结构及 Class 类继承,提供可直接运行的极简示例。同时总结常见踩坑点如 const 引用不可变、扩展运算符浅拷贝等,并通过综合练习题强化异步数据重组、购物车封装及模块化工具函数的实战应用,帮助开发者快速掌握现代 JavaScript 核心用法。

片刻发布于 2026/3/15更新于 2026/6/422 浏览

ES6 + 核心语法全解析(极简可运行代码 + 避坑 + 快速回顾)

前言

写 JS 久了,常发现 let/const 的作用域、箭头函数的 this 指向容易混淆,模板字符串换行也常踩坑。这里整理了 10 个高频核心语法的「问题 + 思路 + 极简例子」,每个都能直接跑,方便随时唤醒记忆。


一、核心思路 / 概念

ES6(ECMAScript 2015)及后续版本是 JavaScript 的重大升级,核心是解决旧语法痛点 + 简化代码。比如用 let/const 解决 var 的全局/函数作用域混乱问题,用箭头函数简化回调写法并固定 this 指向,用解构/扩展运算符快速操作数组/对象,用 Promise/Async-Await 解决回调地狱。所有语法都围绕'写更少的代码,做更多的事',且完全兼容日常开发。


二、实操步骤 + 例子

2.1 语法 1:let/const(替代 var,解决作用域问题)
问题

var 只有全局/函数作用域,容易变量提升、重复声明导致 bug(比如循环里的变量泄露)。

核心思路

let 是块级作用域变量(可重新赋值),const 是块级作用域常量(引用不可变),两者都不能重复声明、不存在变量提升。

极简可运行例子
// 对比 var 的问题(var 无块级作用域)
var a = 1;
if (true) {
  var a = 2; // 覆盖全局 a
}
console.log(a); // 输出 2(坑:本意是块内变量,却改了全局)

// let 的正确用法(块级作用域)
let b = 1;
if (true) {
  let b = 2; // 块内新变量,不影响外部
}
console.log(b); // 输出 1(符合预期)

// const 的正确用法(引用不可变)
const PI = 3.14;
// PI = 3.1415; // ❌ 报错:不能重新赋值(核心坑点)
const user = { name: "张三" };
user.name = "李四"; // ✅ 允许改属性(const 只锁引用,不锁值)
console.log(user.name); // 输出李四
2.2 语法 2:箭头函数(简化回调 + 固定 this 指向)
问题

传统函数的 this 指向随调用场景变化(比如定时器里 this 指向 window),回调函数写法繁琐。

核心思路

箭头函数无自身 this(继承外层作用域的 this),语法更简洁,适合简单回调/纯函数。

极简可运行例子
// 传统函数的 this 坑
const person = {
  name: "张三",
  sayName: function () {
    setTimeout(function () {
      console.log(this.name); // 输出 undefined(this 指向 window)
    }, 100);
  },
};
person.sayName();

// 箭头函数解决 this 问题
const person2 = {
  name: "张三",
  sayName: function () {
    setTimeout(() => {
      console.log(this.name); // 输出张三(继承外层 this)
    }, 100);
  },
};
person2.sayName();

// 箭头函数简写规则
const add = (a, b) => a + b; // 单返回值可省略{}和 return
console.log(add(1, 2)); // 输出 3
const fn = a => a * 2; // 单参数可省略()
console.log(fn(3)); // 输出 6
2.3 语法 3:模板字符串(解决字符串拼接繁琐)
问题

传统字符串拼接需要 + 号,多行字符串需要 \n 转义,易出错。

核心思路

用反引号 ` 包裹字符串,${变量/表达式}插值,支持原生多行,无需转义。

极简可运行例子
// 传统拼接的麻烦
const name = "张三";
const age = 20;
const str1 = "姓名:" + name + ",年龄:" + age + "\n多行字符串";
console.log(str1);

// 模板字符串简化
const str2 = `姓名:${name},年龄:${age} 多行字符串(原生换行)表达式:${1 + 2}`;
console.log(str2); // 输出:姓名:张三,年龄:20 换行 多行字符串 换行 表达式:3
2.4 语法 4:解构赋值(快速提取数组/对象数据)
问题

从数组/对象中取数据需要逐个赋值,代码冗余(比如 const name = user.name; const age = user.age;)。

核心思路

按数组/对象的结构'拆解',直接提取所需数据,支持默认值、重命名。

极简可运行例子
// 数组解构
const arr = [1, 2, 3];
const [a, b] = arr; // 按顺序提取
console.log(a, b); // 输出 1 2
const [x, , z] = arr; // 跳过第二个元素
console.log(x, z); // 输出 1 3

// 对象解构(按属性名,不按顺序)
const user = { name: "张三", age: 20 };
const { name, age } = user; // 直接提取
console.log(name, age); // 输出张三 20
const { name: userName } = user; // 重命名(避免变量冲突)
console.log(userName); // 输出张三

// 函数参数解构(高频用法)
function printUser({ name, age = 18 }) { // 设置默认值
  console.log(`${name},${age}岁`);
}
printUser(user); // 输出张三,20 岁
printUser({ name: "李四" }); // 输出李四,18 岁(用默认值)
2.5 语法 5:扩展运算符(快速合并/复制数组/对象)
问题

传统合并数组用 concat,复制数组用 slice,合并对象用 Object.assign,写法繁琐。

核心思路

用 ... 展开数组/对象,实现一键合并、复制(浅拷贝),替代传统方法。

极简可运行例子
// 数组操作
const arr1 = [1, 2];
const arr2 = [3, 4];
const mergeArr = [...arr1, ...arr2]; // 合并数组
console.log(mergeArr); // 输出 [1,2,3,4]
const copyArr = [...arr1]; // 复制数组(浅拷贝)
console.log(copyArr); // 输出 [1,2]

// 对象操作
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, b: 3, c: 4 }; // 合并 + 覆盖属性
console.log(obj2); // 输出 {a:1,b:3,c:4}
const copyObj = { ...obj1 }; // 复制对象(浅拷贝)
console.log(copyObj); // 输出 {a:1,b:2}
2.6 语法 6:对象简写(减少冗余代码)
问题

对象属性名和变量名相同时,需要重复写 key: value;方法需要写 function 关键字。

核心思路

变量名 = 属性名时可省略 key:,方法可省略 function,支持动态属性名。

极简可运行例子
// 属性简写
const name = "张三";
const age = 20;
const user = { name, age }; // 等价于{name: name, age: age}
console.log(user); // 输出 {name: "张三", age: 20}

// 方法简写
const obj = {
  sayHi() { // 等价于 sayHi: function() {}
    console.log("Hi");
  }
};
obj.sayHi(); // 输出 Hi

// 动态属性名
const key = "status";
const obj2 = {
  [key]: "active", // 动态属性名:status
  [`get_${key}`]() { // 动态方法名:get_status
    return this[key];
  }
};
console.log(obj2.status); // 输出 active
console.log(obj2.get_status()); // 输出 active
2.7 语法 7:Promise/Async-Await(解决回调地狱)
问题

多层异步回调(比如先请求用户数据,再请求用户订单)会形成'回调地狱',代码嵌套深、难维护。

核心思路

Promise 将异步操作封装为对象,用 then/catch 链式调用;Async-Await 是 Promise 的语法糖,用同步写法写异步代码。

极简可运行例子
// 模拟异步请求(比如接口调用)
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ name: "张三" }); // 成功返回数据
      // reject(new Error("请求失败")); // 失败返回错误
    }, 1000);
  });
}

// Promise 链式调用(替代回调嵌套)
fetchData()
  .then(data => {
    console.log(data); // 输出 {name: "张三"}
    return data.name;
  })
  .then(name => console.log(name)) // 输出张三
  .catch(error => console.error(error)); // 捕获错误

// Async-Await(同步写法)
async function loadData() {
  try {
    const data = await fetchData(); // 等待异步完成
    console.log(data); // 输出 {name: "张三"}
  } catch (error) {
    console.error(error); // 捕获错误
  }
}
loadData();
2.8 语法 8:Set/Map(解决数组去重/对象键限制)
问题

数组去重需要遍历 + 判断,对象的键只能是字符串/Symbol,无法用对象做键。

核心思路

Set 是唯一值集合(一键去重),Map 是键值对集合(键可以是任意类型)。

极简可运行例子
// Set 去重(高频用法)
const arr = [1, 2, 2, 3];
const uniqueArr = [...new Set(arr)]; // 数组去重
console.log(uniqueArr); // 输出 [1,2,3]

// Map 使用(键可以是对象)
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, "张三"); // 对象作为键
console.log(map.get(objKey)); // 输出张三
console.log(map.has(objKey)); // 输出 true
2.9 语法 9:Class 类(简化原型链继承)
问题

传统原型链继承写法繁琐(比如 Person.prototype.sayHi = function() {}),易出错。

核心思路

Class 是原型链的语法糖,用 class/constructor/extends/super 实现面向对象编程,更贴近传统语言。

极简可运行例子
// 定义基础类
class Person {
  constructor(name, age) { // 构造函数(初始化属性)
    this.name = name;
    this.age = age;
  }
  sayHi() { // 实例方法
    console.log(`Hi, ${this.name}`);
  }
}
const p = new Person("张三", 20);
p.sayHi(); // 输出 Hi, 张三

// 继承
class Student extends Person {
  constructor(name, age, grade) {
    super(name, age); // 调用父类构造函数
    this.grade = grade;
  }
  sayGrade() {
    console.log(`${this.name}的年级:${this.grade}`);
  }
}
const s = new Student("李四", 18, "高三");
s.sayHi(); // 输出 Hi, 李四
s.sayGrade(); // 输出李四的年级:高三
2.10 语法 10:模块化(export/import,解决全局变量污染)
问题

多个 JS 文件共用变量时会造成全局污染,代码无法隔离复用。

核心思路

用 export 导出模块内的变量/函数/类,用 import 导入到其他文件,实现代码隔离和复用。

极简可运行例子
// 创建模块文件(module.js)
export const PI = 3.14; // 命名导出
export function add(a, b) {
  return a + b;
}
export default function mul(a, b) { // 默认导出(一个模块只能有一个)
  return a * b;
}

// 导入模块(app.js)
// import mul, { PI, add } from './module.js';
// console.log(PI); // 输出 3.14
// console.log(add(1, 2)); // 输出 3
// console.log(mul(2, 3)); // 输出 6

三、踩坑记录 + 解决方案

  1. ❌ 坑 1: const 定义对象后改属性报错 → ✅ 解决: const 锁的是'引用地址',不是值,对象/数组属性可正常修改,只有重新赋值(user = {})才会报错;
  2. ❌ 坑 2: 箭头函数里用 arguments → ✅ 解决: 箭头函数无 arguments 对象,需用剩余参数 ...args 替代;
  3. ❌ 坑 3: 模板字符串里的换行/空格会被保留 → ✅ 解决: 如果不需要空格,可把代码写在一行,或用 trim() 去除首尾空格;
  4. ❌ 坑 4: 解构赋值时变量名写错 → ✅ 解决: 对象解构按'属性名'匹配,数组解构按'顺序'匹配,写错会得到 undefined;
  5. ❌ 坑 5: 扩展运算符深拷贝对象 → ✅ 解决: 扩展运算符是浅拷贝,嵌套对象/数组需用 JSON.parse(JSON.stringify()) 或专门的深拷贝方法;
  6. ❌ 坑 6: Async-Await未用 try/catch 捕获错误 → ✅ 解决: await 的异步错误必须用 try/catch 捕获,否则会静默失败。

四、总结

  1. 核心价值: ES6+ 的所有语法都是'解决旧痛点 + 简化代码',优先用 let/const 替代 var,用箭头函数简化回调,用解构/扩展运算符操作数据,用 Async-Await 写异步代码;
  2. 高频用法: 日常开发中 let/const、模板字符串、解构/扩展运算符、Async-Await 是使用频率最高的 5 个语法,必须掌握;
  3. 避坑关键: 记住 const 的'引用不可变'、箭头函数的 this 继承规则、扩展运算符的浅拷贝特性,能避免 80% 的 ES6 使用坑;
  4. 适用场景: 这些语法完全适配 uni-app、Vue/React 等前端框架,直接复制例子就能在项目中使用。

ES6+ 核心语法综合练习题(题目 + 答案合一版)

一、综合场景题(3 道,侧重实战综合应用)

场景题 1:异步数据处理与重组(综合箭头函数/解构/扩展运算符/Async-Await/Set)
题目需求

模拟前端请求「用户列表」和「用户订单列表」两个异步接口,完成以下操作:

  1. 用 Async-Await 并行请求两个接口(模拟接口用 Promise 封装,延迟 1 秒返回数据);
  2. 合并用户数据与订单数据,为每个用户添加 orders 字段(匹配 userId);
  3. 过滤出「有订单的用户」,并对用户的订单金额去重后求和;
  4. 最终返回格式:[{ id: 1, name: "张三", orders: [/* 该用户订单 */], totalAmount: 去重后总金额 }, ...]。
模拟接口数据
// 模拟用户列表接口
function fetchUsers() {
  return new Promise(resolve => setTimeout(() => {
    resolve([
      { id: 1, name: "张三", age: 20 },
      { id: 2, name: "李四", age: 22 },
      { id: 3, name: "王五", age: 25 }
    ]);
  }, 1000));
}

// 模拟订单列表接口
function fetchOrders() {
  return new Promise(resolve => setTimeout(() => {
    resolve([
      { orderId: 1, userId: 1, amount: 100 },
      { orderId: 2, userId: 1, amount: 200 },
      { orderId: 3, userId: 1, amount: 200 }, // 重复金额
      { orderId: 4, userId: 2, amount: 150 },
      { orderId: 5, userId: 3, amount: 0 } // 金额为 0
    ]);
  }, 1000));
}
题目要求
  • 必须使用 Async-Await 处理异步,禁止使用 then/catch 链式调用;
  • 订单金额去重必须用 Set 实现;
  • 合并数据时需用解构/扩展运算符简化代码;
  • 过滤条件:订单金额 > 0 且有订单的用户。
参考答案
async function handleUserAndOrders() {
  // 并行请求两个接口,数组解构接收结果
  const [users, orders] = await Promise.all([fetchUsers(), fetchOrders()]);

  // 处理用户数据,过滤无有效订单的用户
  const result = users.map(user => {
    // 筛选当前用户的有效订单(userId 匹配 + 金额>0)
    const userOrders = orders.filter(order => order.userId === user.id && order.amount > 0);
    if (userOrders.length === 0) return null; // 无有效订单返回 null,后续过滤

    // Set 实现金额去重,扩展运算符转数组后求和
    const uniqueAmounts = new Set(userOrders.map(order => order.amount));
    const totalAmount = [...uniqueAmounts].reduce((sum, amount) => sum + amount, 0);

    // 扩展运算符合并用户基础数据,添加订单和总金额字段
    return { ...user, orders: userOrders, totalAmount };
  }).filter(Boolean); // 过滤 null 值,保留有有效订单的用户

  return result;
}

// 测试调用
handleUserAndOrders().then(res => console.log(res));
// 预期输出:
// [
//   { id: 1, name: "张三", age: 20, orders: [{ orderId:1, userId:1, amount:100 }, { orderId:2, userId:1, amount:200 }, { orderId:3, userId:1, amount:200 }], totalAmount: 300 },
//   { id: 2, name: "李四", age: 22, orders: [{ orderId:4, userId:2, amount:150 }], totalAmount: 150 }
// ]
场景题 2:购物车类封装(综合 Class/Map/ 对象简写/模板字符串/解构)
题目需求

封装一个 ShoppingCart 类,实现购物车核心功能:

  1. 用 Map 存储商品(键:商品 id,值:{id, name, price, count});
  2. 实现方法:
    • addGoods(goods):添加商品(若已存在则累加数量,商品格式:{id, name, price});
    • removeGoods(goodsId):删除指定 id 的商品;
    • getTotalPrice():计算购物车商品总价(价格×数量求和);
    • getCartInfo():返回购物车信息字符串,格式:"购物车:商品 1(数量:2,单价:100)、商品 2(数量:1,单价:200),总价:400"。
  3. 类的方法需使用「对象简写语法」,字符串拼接需用模板字符串。
测试用例
const cart = new ShoppingCart();
cart.addGoods({ id: 1, name: "手机", price: 2000 });
cart.addGoods({ id: 1, name: "手机", price: 2000 }); // 数量累加为 2
cart.addGoods({ id: 2, name: "耳机", price: 300 });
console.log(cart.getTotalPrice()); // 预期输出:4300
console.log(cart.getCartInfo()); // 预期输出:"购物车:手机(数量:2,单价:2000)、耳机(数量:1,单价:300),总价:4300"
cart.removeGoods(2);
console.log(cart.getCartInfo()); // 预期输出:"购物车:手机(数量:2,单价:2000),总价:4000"
参考答案
class ShoppingCart {
  constructor() {
    // 初始化 Map,键为商品 id,值为商品详细信息
    this.goodsMap = new Map();
  }

  // 添加商品:对象简写语法,解构取值简化代码
  addGoods(goods) {
    const { id, name, price } = goods;
    if (this.goodsMap.has(id)) {
      // 商品已存在,累加数量,扩展运算符保留原有属性
      const oldGoods = this.goodsMap.get(id);
      this.goodsMap.set(id, { ...oldGoods, count: oldGoods.count + 1 });
    } else {
      // 商品不存在,新增,数量默认 1
      this.goodsMap.set(id, { id, name, price, count: 1 });
    }
  }

  // 删除商品:根据 id 删除 Map 中的键值对
  removeGoods(goodsId) {
    this.goodsMap.delete(goodsId);
  }

  // 计算总价:遍历 Map 值,累加价格×数量
  getTotalPrice() {
    let total = 0;
    for (const goods of this.goodsMap.values()) {
      total += goods.price * goods.count;
    }
    return total;
  }

  // 获取购物车信息:模板字符串拼接,扩展运算符转数组后遍历
  getCartInfo() {
    const goodsStr = [...this.goodsMap.values()].map(goods => {
      return `${goods.name}(数量:${goods.count},单价:${goods.price})`;
    }).join("、");
    const totalPrice = this.getTotalPrice();
    return `购物车:${goodsStr},总价:${totalPrice}`;
  }
}

// 测试用例执行
const cart = new ShoppingCart();
cart.addGoods({ id: 1, name: "手机", price: 2000 });
cart.addGoods({ id: 1, name: "手机", price: 2000 });
cart.addGoods({ id: 2, name: "耳机", price: 300 });
console.log(cart.getTotalPrice()); // 4300
console.log(cart.getCartInfo()); // 购物车:手机(数量:2,单价:2000)、耳机(数量:1,单价:300),总价:4300
cart.removeGoods(2);
console.log(cart.getCartInfo()); // 购物车:手机(数量:2,单价:2000),总价:4000
场景题 3:模块化工具函数封装(综合模块化/解构/扩展运算符/const/ 箭头函数)
题目需求

封装一个 formatUtil.js 工具模块,实现以下功能:

  1. 导出常量 DEFAULT_FORMAT(值:{ date: "YYYY-MM-DD", money: "¥0.00" });
  2. 导出命名函数 formatDate:接收日期对象/时间戳,返回指定格式的日期字符串(默认用 DEFAULT_FORMAT.date);
  3. 导出命名函数 formatMoney:接收数字,返回指定格式的金额字符串(默认用 DEFAULT_FORMAT.money);
  4. 导出默认函数 formatData:接收数据对象({date, money}),合并默认配置,返回格式化后的对象;
  5. 题目要求:
    • 所有变量用 const 声明,函数用箭头函数;
    • 函数参数需用解构 + 默认值简化代码;
    • 合并配置时需用扩展运算符。
调用示例(app.js)
import formatData, { formatDate, formatMoney, DEFAULT_FORMAT } from './formatUtil.js';
console.log(formatDate(new Date(2026, 0, 27))); // 预期输出:"2026-01-27"
console.log(formatMoney(100)); // 预期输出:"¥100.00"
console.log(formatData({ date: new Date(2026, 0, 27), money: 200 })); // 预期输出:{ date: "2026-01-27", money: "¥200.00" }
console.log(formatData({ money: 300 }, { money: "$0.00" })); // 预期输出:{ date: "YYYY-MM-DD", money: "$300.00" }
参考答案
formatUtil.js 模块代码
// 常量声明:所有变量用 const,符合要求
const DEFAULT_FORMAT = { date: "YYYY-MM-DD", money: "¥0.00" };

// 格式化日期:箭头函数,参数默认值,处理日期对象/时间戳
const formatDate = (date, format = DEFAULT_FORMAT.date) => {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, "0"); // 补 0 为 2 位
  const day = String(d.getDate()).padStart(2, "0"); // 替换格式符为实际日期
  return format.replace("YYYY", year).replace("MM", month).replace("DD", day);
};

// 格式化金额:箭头函数,参数默认值,数字补两位小数
const formatMoney = (num, format = DEFAULT_FORMAT.money) => {
  const fixedNum = Number(num).toFixed(2); // 确保是两位小数
  return format.replace("0.00", fixedNum);
};

// 默认导出:格式化数据,箭头函数,解构 + 默认值,扩展运算符合并配置
const formatData = (data = {}, customFormat = {}) => {
  // 合并默认配置和自定义配置,自定义配置优先级更高
  const finalFormat = { ...DEFAULT_FORMAT, ...customFormat };
  const { date, money } = data;
  // 按需格式化日期和金额,未传则返回配置默认值
  return {
    date: date ? formatDate(date, finalFormat.date) : finalFormat.date,
    money: money ? formatMoney(money, finalFormat.money) : finalFormat.money
  };
};

// 命名导出 + 默认导出,符合模块化规范
export { DEFAULT_FORMAT, formatDate, formatMoney };
export default formatData;
app.js 调用代码
import formatData, { formatDate, formatMoney, DEFAULT_FORMAT } from './formatUtil.js';
// 测试调用
console.log(formatDate(new Date(2026, 0, 27))); // 2026-01-27
console.log(formatMoney(100)); // ¥100.00
console.log(formatData({ date: new Date(2026, 0, 27), money: 200 })); // { date: '2026-01-27', money: '¥200.00' }
console.log(formatData({ money: 300 }, { money: "$0.00" })); // { date: 'YYYY-MM-DD', money: '$300.00' }

二、问答题(5 道,侧重理解与避坑)

问答题 1

题目:let/const与var的核心区别有哪些?为什么实际开发中推荐优先使用const而非let?

参考答案:

  1. 核心区别
    • 作用域:var是函数/全局作用域,let/const是块级作用域({}包裹的区域,如 if/for/while);
    • 变量提升:var存在变量提升(可先使用后声明),let/const存在暂时性死区(声明前使用会报错,无变量提升);
    • 重复声明:var允许同一作用域内重复声明,let/const不允许;
    • 初始值:const声明时必须初始化,var/let可后续赋值;
    • 全局属性:var声明的全局变量会挂载到 window/global上,let/const不会。
  2. 优先使用 const 的原因
    • 语义化更强:const明确表示变量引用不可变,代码可读性更高,其他开发者能快速知道该变量不会被重新赋值;
    • 减少意外 bug:避免开发中无意修改变量值,若后续发现需要修改,再将 const改为 let即可,属于'最小权限原则';
    • 符合函数式编程思想:强调数据不可变,减少副作用,让代码更稳定、易维护;
    • 对引用类型友好:const仅锁引用地址,不锁值(对象/数组属性可正常修改),不影响日常使用。
问答题 2

题目:箭头函数和普通函数的核心差异体现在哪些方面?请列举至少 3 点,并说明哪些场景绝对不能使用箭头函数(举例说明)。

参考答案:

  1. 核心差异(至少 3 点)
    • this 指向:箭头函数无自身 this,继承外层作用域的 this;普通函数的 this 指向调用者(谁调用指向谁,全局调用指向 window/undefined);
    • arguments 对象:箭头函数无 arguments 对象,需用剩余参数...args替代;普通函数有 arguments 对象(存储实参的类数组);
    • 构造函数:箭头函数不能作为构造函数(不能用 new 调用,无 prototype 原型属性);普通函数可以作为构造函数;
    • 原型属性:箭头函数没有 prototype 属性;普通函数有 prototype 属性,可通过原型扩展方法;
    • 绑定方式:箭头函数的 this一旦确定无法修改(call/apply/bind 无法改变);普通函数的 this 可通过 call/apply/bind 手动修改。
  2. 绝对不能使用箭头函数的场景(举例)
    • 生成器函数:箭头函数不能使用 yield 关键字,无法作为生成器函数。
    • 需要动态 this 的场景(如事件回调、DOM 操作):箭头函数的 this 无法指向触发事件的 DOM 元素;
    const btn = document.querySelector('button');
    btn.onclick = () => {
      console.log(this)
    }; // this 指向 window,而非按钮元素
    
    • 类的实例方法/原型方法:箭头函数会导致每个实例创建新的函数,浪费内存,且 this 不指向实例;
    class Person {
      name = "张三";
      sayName = () => {
        console.log(this.name)
      }; // 每个实例都有一个 sayName,浪费内存
    }
    
    • 对象的方法:箭头函数的 this 继承全局,而非对象本身,导致取值失败;
    const obj = {
      name: "张三",
      sayName: () => {
        console.log(this.name)
      } // this 指向 window,输出 undefined
    };
    obj.sayName();
    
问答题 3

题目:解构赋值是 ES6 的高频用法,请列举 3 个解构赋值的典型业务场景,并说明:解构对象时如果目标属性不存在,如何避免获取到undefined?

参考答案:

  1. 解构赋值的 3 个典型业务场景
    • 补充:嵌套数据解构(如 JSON 数据、复杂对象)、遍历解构(如 Map/数组的键值对遍历)也属于高频场景。
  2. 解构对象避免获取到 undefined 的方法
    • 解构前判断对象是否存在:先判断源对象是否为 null/undefined,再解构,避免基础报错;
    const user = null;
    const { name } = user || {}; // user 为 null,用空对象替代,name 取 undefined
    
    • 可选链 + 默认值结合:先通过可选链判断属性是否存在,不存在则用 ||/?? 设置默认值;
    const user = { name: "张三" };
    const age = user?.info?.age ?? 0; // 可选链判断,空值合并运算符设置默认值
    
    • 嵌套解构的默认值:若解构嵌套对象,需为外层对象也设置默认值,避免外层属性不存在时报错;
    // 错误:info 为 undefined,解构 info.age 会报错
    const { info: { age = 0 } } = { name: "张三" };
    // 正确:为 info 设置默认空对象,避免报错
    const { info: { age = 0 } = {} } = { name: "张三" };
    
    • 设置属性默认值:解构时直接为属性设置默认值,属性不存在时使用默认值;
    const { name = "未知用户", age = 0 } = { name: "张三" }; // age 不存在,取默认值 0
    
    • 数组解构与变量交换:无需临时变量,快速交换两个变量的值,适合排序、交换场景;
    let a = 1, b = 2;
    [a, b] = [b, a]; // 交换 a 和 b 的值,a=2,b=1
    
    • 接口数据解构:快速提取接口返回的核心数据,简化代码,避免多次点语法取值;
    // 接口返回数据
    const res = { code: 200, data: { list: [1,2,3], total: 3 }, msg: "成功" };
    // 解构提取核心数据
    const { code, data: { list, total } } = res;
    
    • 函数参数解构:简化多参数的取值,支持默认值,替代繁琐的参数对象取值;
    // 传统写法
    function fn(options) {
      const name = options.name;
      const age = options.age;
    }
    // 解构写法
    function fn({ name, age = 18 }) {
      console.log(name, age)
    }
    fn({ name: "张三" });
    
问答题 4

题目:扩展运算符(...)实现的对象/数组拷贝是浅拷贝,请解释「浅拷贝」的定义,并说明深拷贝的 3 种实现方式,以及每种方式的优缺点。

参考答案:

  • 原理:将对象转为 JSON 字符串,再转回新的对象,实现内存地址的完全隔离;

  • 优点:写法简单,无需额外依赖,适合大部分简单场景;

  • 缺点:① 无法拷贝函数、正则表达式、Date 对象、undefined、Symbol;② 无法处理循环引用(对象自身引用/互相引用),会直接报错;③ 会丢失对象的原型链,拷贝后变为普通对象。

  • 原理:递归遍历对象的每一层属性,若为引用类型(对象/数组)则继续递归拷贝,若为基本类型则直接赋值;

  • 优点:可自定义拷贝规则,支持函数、Date、正则等类型,可处理循环引用,保留原型链;

  • 原理:库内部封装了完善的递归拷贝逻辑,处理了所有边界情况(类型判断、循环引用、原型链等);

  • 优点:成熟稳定,支持所有数据类型,处理了循环引用/原型链/特殊对象,无需自己写代码,开发效率高;

  • 缺点:若仅需简单深拷贝,引入库会增加项目体积(可按需引入,如 import cloneDeep from 'lodash/cloneDeep')。

缺点:代码复杂,需要处理各种边界情况(如 null/undefined/ 正则/Date/ 循环引用),开发成本高;

function deepClone(obj, map = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (map.has(obj)) return map.get(obj); // 处理循环引用
  const cloneObj = new obj.constructor(); // 保留原型链
  map.set(obj, cloneObj);
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], map);
    }
  }
  return cloneObj;
}

深拷贝的 3 种实现方式及优缺点

方式 1:JSON.parse(JSON.stringify(obj))(简易深拷贝)
方式 2:手写递归深拷贝
方式 3:第三方库实现(如 Lodash 的_.cloneDeep)

浅拷贝的定义浅拷贝仅复制对象/数组的第一层属性,对于嵌套的对象/数组,不会递归复制,而是直接复制其内存引用地址。修改拷贝后对象的嵌套属性,原对象的嵌套属性也会被修改,因为二者指向同一块内存空间。

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 }; // 浅拷贝
obj2.b.c = 3;
console.log(obj1.b.c); // 输出 3,原对象被修改
问答题 5

题目:Async-Await的本质是什么?它和Promise的关系是什么?使用Async-Await时,为什么必须用try/catch捕获错误?如果不捕获会有什么问题?

参考答案:

  1. Async-Await 的本质Async-Await是ES7 推出的语法糖,底层完全基于Promise实现,其核心作用是将异步代码以同步的写法呈现,解决了 Promise 链式调用(then/catch)的嵌套问题,让异步代码的可读性和维护性更高。
  2. Async-Await 与 Promise 的关系
    • async修饰的函数,返回值一定是 Promise 对象(无论函数内返回什么,都会被包装为 Promise.resolve (返回值));
    • await关键字只能在 async 函数中使用,且后面必须跟 Promise 对象(若跟非 Promise 值,会被包装为 Promise.resolve (值));
    • await的作用是等待 Promise 的状态变为 fulfilled(成功),并返回 Promise 的 resolve 值;若 Promise 状态为 rejected(失败),则会抛出错误;
    • Async-Await 是 Promise 的上层封装,不能脱离 Promise 独立使用,所有 Async-Await 的异步操作,最终都要通过 Promise 实现。
  3. 必须用 try/catch 捕获错误的原因当 await后面的 Promise 对象状态变为 rejected(如接口请求失败、异步操作报错)时,会抛出一个错误,该错误不会像普通 Promise 那样通过 catch 捕获,而是会静默中断async 函数的执行,若不通过 try/catch 捕获,错误会被'吞噬',导致后续代码无法执行,且无法定位问题。
    • 错误无法被感知:错误不会抛到全局,控制台仅会显示一个未捕获的 Promise 错误,难以定位错误的具体位置和原因;
    • 全局报错:若多个 async 函数嵌套,未捕获的错误会向上传递,最终导致全局未捕获错误,严重时会导致页面/应用崩溃;
    • 异步状态无法处理:无法对失败的异步操作做兜底处理(如接口请求失败后提示用户、刷新数据)。

代码执行中断:async 函数中,await 抛出错误后,错误位置后的代码会完全停止执行,导致业务逻辑中断;

async function fn() {
  await Promise.reject(new Error("请求失败"));
  console.log("后续代码"); // 不会执行,因为前面抛出了错误
}
fn();

不捕获错误的问题补充:除了 try/catch,也可通过在 await 后加 Promise.catch () 捕获单个异步错误:

async function fn() {
  const res = await fetchData().catch(err => {
    console.error(err);
    return null; // 兜底返回 null,避免代码中断
  });
  console.log(res);
}

目录

  1. ES6 + 核心语法全解析(极简可运行代码 + 避坑 + 快速回顾)
  2. 前言
  3. 一、核心思路 / 概念
  4. 二、实操步骤 + 例子
  5. 2.1 语法 1:let/const(替代 var,解决作用域问题)
  6. 问题
  7. 核心思路
  8. 极简可运行例子
  9. 2.2 语法 2:箭头函数(简化回调 + 固定 this 指向)
  10. 问题
  11. 核心思路
  12. 极简可运行例子
  13. 2.3 语法 3:模板字符串(解决字符串拼接繁琐)
  14. 问题
  15. 核心思路
  16. 极简可运行例子
  17. 2.4 语法 4:解构赋值(快速提取数组/对象数据)
  18. 问题
  19. 核心思路
  20. 极简可运行例子
  21. 2.5 语法 5:扩展运算符(快速合并/复制数组/对象)
  22. 问题
  23. 核心思路
  24. 极简可运行例子
  25. 2.6 语法 6:对象简写(减少冗余代码)
  26. 问题
  27. 核心思路
  28. 极简可运行例子
  29. 2.7 语法 7:Promise/Async-Await(解决回调地狱)
  30. 问题
  31. 核心思路
  32. 极简可运行例子
  33. 2.8 语法 8:Set/Map(解决数组去重/对象键限制)
  34. 问题
  35. 核心思路
  36. 极简可运行例子
  37. 2.9 语法 9:Class 类(简化原型链继承)
  38. 问题
  39. 核心思路
  40. 极简可运行例子
  41. 2.10 语法 10:模块化(export/import,解决全局变量污染)
  42. 问题
  43. 核心思路
  44. 极简可运行例子
  45. 三、踩坑记录 + 解决方案
  46. 四、总结
  47. ES6+ 核心语法综合练习题(题目 + 答案合一版)
  48. 一、综合场景题(3 道,侧重实战综合应用)
  49. 场景题 1:异步数据处理与重组(综合箭头函数/解构/扩展运算符/Async-Await/Set)
  50. 题目需求
  51. 模拟接口数据
  52. 题目要求
  53. 参考答案
  54. 场景题 2:购物车类封装(综合 Class/Map/ 对象简写/模板字符串/解构)
  55. 题目需求
  56. 测试用例
  57. 参考答案
  58. 场景题 3:模块化工具函数封装(综合模块化/解构/扩展运算符/const/ 箭头函数)
  59. 题目需求
  60. 调用示例(app.js)
  61. 参考答案
  62. formatUtil.js 模块代码
  63. app.js 调用代码
  64. 二、问答题(5 道,侧重理解与避坑)
  65. 问答题 1
  66. 问答题 2
  67. 问答题 3
  68. 问答题 4
  69. 方式 1:JSON.parse(JSON.stringify(obj))(简易深拷贝)
  70. 方式 2:手写递归深拷贝
  71. 方式 3:第三方库实现(如 Lodash 的_.cloneDeep)
  72. 问答题 5
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • MCP Apps:AI 助手的交互式界面新范式与架构解析
  • Python 零基础学习经验总结与入门技术指南
  • 基于 AI 的全栈开发新路径:自动生成 UI 设计稿与 H5 原型
  • 5 种主流深度生成模型对比:VAE、GAN、AR、Flow 与 Diffusion 原理及实现
  • 2026 年前端面试核心场景与工程化能力梳理
  • AI 提示词零基础入门与核心概念
  • C++ 继承核心机制详解
  • Win10 彻底关闭 Microsoft 365 Copilot 弹窗的 6 种方法
  • Unity3D 粒子系统核心模块实战:Velocity、Noise 与生命周期控制
  • C++ 继承机制详解:实现栈、同名隐藏与派生类默认成员函数
  • Amazon SageMaker 部署 AIGC 应用:训练优化及 Web 前端集成实践
  • Milvus 向量数据库实战:Attu 可视化安装与 Python 整合指南
  • 命令行大模型交互工具 MCPHost 配置与实战指南
  • OpenCode 快速上手指南:开源免费的 Claude Code 替代方案
  • MySQL 数据库基础核心概念与实战入门
  • 电商产品 AI 绘画:高效提示词撰写实战指南
  • Python 文件操作基础:模式、读写与指针控制
  • 算法基础:分治法核心思想与经典例题解析
  • Java synchronized 底层原理:从字节码到锁升级详解
  • 基于 Whisper 与 pyannote.audio 构建说话人分离转写系统

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online