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

ES6 核心语法全解析:避坑指南与实战代码

综述由AI生成ES6 语法升级解决了旧版本作用域混乱与回调地狱问题。内容涵盖 let/const、箭头函数、解构赋值等 10 个核心语法点,配合避坑指南与实战练习题。重点解析 const 引用不可变、箭头函数 this 指向及异步处理规范,适合快速回顾或新手入门。

BigDataPan发布于 2026/4/8更新于 2026/6/250 浏览

ES6 + 核心语法全解析

前言

学 ES6 语法时,总容易记混 let/const 的作用域、箭头函数的 this 指向,或者解构赋值传参规则。我也踩过 'const 定义对象改属性报错''模板字符串换行空格处理不当' 的坑。这里整理了 10 个高频核心语法的「问题 + 思路 + 极简例子」,每个例子都能直接复制运行,方便后续快速唤醒记忆,也能让新手看懂核心用法。

一、核心思路

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

二、实操步骤 + 例子

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. = ; 
.(user.); 
"张三"
name
"李四"
// ✅ 允许改属性(const 只锁引用,不锁值)
console
log
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:模板字符串(解决字符串拼接繁琐)

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

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

代码示例

// 传统拼接的麻烦
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 种实现方式,以及每种方式的优缺点。

参考答案:

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

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = { ...obj1 }; // 浅拷贝
    obj2.b.c = 3;
    console.log(obj1.b.c); // 输出 3,原对象被修改
    
  • 深拷贝的 3 种实现方式及优缺点

    方式 1:JSON.parse(JSON.stringify(obj))(简易深拷贝)
    • 原理:将对象转为 JSON 字符串,再转回新的对象,实现内存地址的完全隔离;
    • 优点:写法简单,无需额外依赖,适合大部分简单场景;
    • 缺点:① 无法拷贝函数、正则表达式、Date 对象、undefined、Symbol;② 无法处理循环引用(对象自身引用/互相引用),会直接报错;③ 会丢失对象的原型链,拷贝后变为普通对象。
    方式 2:手写递归深拷贝
    • 原理:递归遍历对象的每一层属性,若为引用类型(对象/数组)则继续递归拷贝,若为基本类型则直接赋值;
    • 优点:可自定义拷贝规则,支持函数、Date、正则等类型,可处理循环引用,保留原型链;
    • 缺点:代码复杂,需要处理各种边界情况(如 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:第三方库实现(如 Lodash 的 _.cloneDeep)
    • 原理:库内部封装了完善的递归拷贝逻辑,处理了所有边界情况(类型判断、循环引用、原型链等);
    • 优点:成熟稳定,支持所有数据类型,处理了循环引用/原型链/特殊对象,无需自己写代码,开发效率高;
    • 缺点:若仅需简单深拷贝,引入库会增加项目体积(可按需引入,如 import cloneDeep from 'lodash/cloneDeep')。
问答题 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. 2.2 语法 2:箭头函数(简化回调 + 固定 this 指向)
  7. 2.3 语法 3:模板字符串(解决字符串拼接繁琐)
  8. 2.4 语法 4:解构赋值(快速提取数组 / 对象数据)
  9. 2.5 语法 5:扩展运算符(快速合并 / 复制数组 / 对象)
  10. 2.6 语法 6:对象简写(减少冗余代码)
  11. 2.7 语法 7:Promise/Async-Await(解决回调地狱)
  12. 2.8 语法 8:Set/Map(解决数组去重 / 对象键限制)
  13. 2.9 语法 9:Class 类(简化原型链继承)
  14. 2.10 语法 10:模块化(export/import,解决全局变量污染)
  15. 三、踩坑记录 + 解决方案
  16. 四、总结
  17. ES6+ 核心语法综合练习题(题目 + 答案合一版)
  18. 一、综合场景题(3 道,侧重实战综合应用)
  19. 场景题 1:异步数据处理与重组(综合箭头函数/解构/扩展运算符/Async-Await/Set)
  20. 场景题 2:购物车类封装(综合 Class/Map/ 对象简写/模板字符串/解构)
  21. 场景题 3:模块化工具函数封装(综合模块化/解构/扩展运算符/const/ 箭头函数)
  22. formatUtil.js 模块代码
  23. app.js 调用代码
  24. 二、问答题(5 道,侧重理解与避坑)
  25. 问答题 1
  26. 问答题 2
  27. 问答题 3
  28. 问答题 4
  29. 问答题 5
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • DeepSeek-R1-Distill-Llama-8B 部署教程:Docker Compose 多模型推理服务
  • AI Agent 生产级框架实战:架构设计与核心实现
  • 深入理解 HTML5 Web Workers:提升网页性能的核心技术
  • 秒杀场景下的 Redis 分布式锁优化与业务设计
  • CoPaw 个人助理部署与定制指南:从零开始打造专属 AI 数字搭档
  • 前缀和算法:和为 K 的子数组与和可被 K 整除的子数组
  • AVL 树原理与 C++ 实现
  • 大模型时代人形机器人感知:视觉 - 语言模型在机器人中的应用
  • OpenClaw:从认知到执行的行动型 AI 框架解析
  • JTextArea 与 JTable 自动滚动至最后一行的实现
  • AI 辅助开发:使用 DeepSeek 构建贪吃蛇游戏
  • ToDesk 集成 ToClaw:AI Agent 实现远程桌面自动化执行
  • Python 数据分析与可视化及 ChatGPT 职场效率提升技巧
  • Spring 排序机制:接口与注解的使用
  • Stable Diffusion 技术详解:LoRA 模型与 ControlNet 协同应用
  • AI 助手效率翻倍,VSCode Copilot 自定义指令实战
  • 通义万相 2.1 结合计算平台实现 AIGC 内容生成
  • Python 基于 DXGI 实现现代游戏窗口无闪烁截图方案
  • 基于 Microi 吾码低代码框架构建 Vue 高效应用
  • OpenClaw 对接飞书实现多机器人群聊配置

相关免费在线工具

  • 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