【前端|2 ES6 + 核心语法全解析】

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

前言

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


一、核心思路 / 概念

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

在线书籍📚:https://www.bookstack.cn/read/es6-3rd/sidebar.md

二、实操步骤 + 例子

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

问题

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

核心思路

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

极简可运行例子

javascript

运行

// 步骤1:对比var的问题(var无块级作用域) var a = 1; if (true) { var a = 2; // 覆盖全局a } console.log(a); // 输出2(坑:本意是块内变量,却改了全局) // 步骤2:let的正确用法(块级作用域) let b = 1; if (true) { let b = 2; // 块内新变量,不影响外部 } console.log(b); // 输出1(符合预期) // 步骤3: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),语法更简洁,适合简单回调 / 纯函数。

极简可运行例子

javascript

运行

// 步骤1:传统函数的this坑 const person = { name: "张三", sayName: function () { setTimeout(function () { console.log(this.name); // 输出undefined(this指向window) }, 100); }, }; person.sayName(); // 步骤2:箭头函数解决this问题 const person2 = { name: "张三", sayName: function () { setTimeout(() => { console.log(this.name); // 输出张三(继承外层this) }, 100); }, }; person2.sayName(); // 步骤3:箭头函数简写规则 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转义,易出错。

核心思路

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

极简可运行例子

javascript

运行

// 步骤1:传统拼接的麻烦 const name = "张三"; const age = 20; const str1 = "姓名:" + name + ",年龄:" + age + "\n多行字符串"; console.log(str1); // 步骤2:模板字符串简化 const str2 = `姓名:${name},年龄:${age} 多行字符串(原生换行) 表达式:${1 + 2}`; console.log(str2); // 输出:姓名:张三,年龄:20 换行 多行字符串 换行 表达式:3 

2.4 语法 4:解构赋值(快速提取数组 / 对象数据)

问题

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

核心思路

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

极简可运行例子

javascript

运行

// 步骤1:数组解构 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 // 步骤2:对象解构(按属性名,不按顺序) const user = { name: "张三", age: 20 }; const { name, age } = user; // 直接提取 console.log(name, age); // 输出张三 20 const { name: userName } = user; // 重命名(避免变量冲突) console.log(userName); // 输出张三 // 步骤3:函数参数解构(高频用法) function printUser({ name, age = 18 }) { // 设置默认值 console.log(`${name},${age}岁`); } printUser(user); // 输出张三,20岁 printUser({ name: "李四" }); // 输出李四,18岁(用默认值) 

2.5 语法 5:扩展运算符(快速合并 / 复制数组 / 对象)

问题

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

核心思路

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

极简可运行例子

javascript

运行

// 步骤1:数组操作 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] // 步骤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,支持动态属性名。

极简可运行例子

javascript

运行

// 步骤1:属性简写 const name = "张三"; const age = 20; const user = { name, age }; // 等价于{name: name, age: age} console.log(user); // 输出{name: "张三", age: 20} // 步骤2:方法简写 const obj = { sayHi() { // 等价于sayHi: function() {} console.log("Hi"); } }; obj.sayHi(); // 输出Hi // 步骤3:动态属性名 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-AwaitPromise的语法糖,用同步写法写异步代码。

极简可运行例子

javascript

运行

// 步骤1:模拟异步请求(比如接口调用) function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ name: "张三" }); // 成功返回数据 // reject(new Error("请求失败")); // 失败返回错误 }, 1000); }); } // 步骤2:Promise链式调用(替代回调嵌套) fetchData() .then(data => { console.log(data); // 输出{name: "张三"} return data.name; }) .then(name => console.log(name)) // 输出张三 .catch(error => console.error(error)); // 捕获错误 // 步骤3: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是键值对集合(键可以是任意类型)。

极简可运行例子

javascript

运行

// 步骤1:Set去重(高频用法) const arr = [1, 2, 2, 3]; const uniqueArr = [...new Set(arr)]; // 数组去重 console.log(uniqueArr); // 输出[1,2,3] // 步骤2: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实现面向对象编程,更贴近传统语言。

极简可运行例子

javascript

运行

// 步骤1:定义基础类 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, 张三 // 步骤2:继承 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导入到其他文件,实现代码隔离和复用。

极简可运行例子

javascript

运行

// 步骤1:创建模块文件(module.js) export const PI = 3.14; // 命名导出 export function add(a, b) { return a + b; } export default function mul(a, b) { // 默认导出(一个模块只能有一个) return a * b; } // 步骤2:导入模块(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: 去重后总金额 }, ...]
模拟接口数据

javascript

运行

// 模拟用户列表接口 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 且 有订单的用户。
参考答案

javascript

运行

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. 类的方法需使用「对象简写语法」,字符串拼接需用模板字符串。
测试用例

javascript

运行

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" 
参考答案

javascript

运行

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)

javascript

运行

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 模块代码

javascript

运行

// 常量声明:所有变量用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 调用代码

javascript

运行

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/constvar的核心区别有哪些?为什么实际开发中推荐优先使用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 元素;javascript运行

const btn = document.querySelector('button'); btn.onclick = () => { console.log(this) }; // this指向window,而非按钮元素 

类的实例方法 / 原型方法:箭头函数会导致每个实例创建新的函数,浪费内存,且 this 不指向实例;javascript运行

class Person { name = "张三"; sayName = () => { console.log(this.name) }; // 每个实例都有一个sayName,浪费内存 } 

对象的方法:箭头函数的 this 继承全局,而非对象本身,导致取值失败;javascript运行

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,再解构,避免基础报错;javascript运行

const user = null; const { name } = user || {}; // user为null,用空对象替代,name取undefined 

可选链 + 默认值结合:先通过可选链判断属性是否存在,不存在则用 ||/?? 设置默认值;javascript运行

const user = { name: "张三" }; const age = user?.info?.age ?? 0; // 可选链判断,空值合并运算符设置默认值 

嵌套解构的默认值:若解构嵌套对象,需为外层对象也设置默认值,避免外层属性不存在时报错;javascript运行

// 错误:info为undefined,解构info.age会报错 const { info: { age = 0 } } = { name: "张三" }; // 正确:为info设置默认空对象,避免报错 const { info: { age = 0 } = {} } = { name: "张三" }; 

设置属性默认值:解构时直接为属性设置默认值,属性不存在时使用默认值;javascript运行

const { name = "未知用户", age = 0 } = { name: "张三" }; // age不存在,取默认值0 

数组解构与变量交换:无需临时变量,快速交换两个变量的值,适合排序、交换场景;javascript运行

let a = 1, b = 2; [a, b] = [b, a]; // 交换a和b的值,a=2,b=1 

接口数据解构:快速提取接口返回的核心数据,简化代码,避免多次点语法取值;javascript运行

// 接口返回数据 const res = { code: 200, data: { list: [1,2,3], total: 3 }, msg: "成功" }; // 解构提取核心数据 const { code, data: { list, total } } = res; 

函数参数解构:简化多参数的取值,支持默认值,替代繁琐的参数对象取值;javascript运行

// 传统写法 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 / 循环引用),开发成本高;javascript运行

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

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

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-AwaitES7 推出的语法糖,底层完全基于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 抛出错误后,错误位置后的代码会完全停止执行,导致业务逻辑中断;javascript运行

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

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

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

Read more

踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录

踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录

目录 * WordPress中要点,域和托管 * 域名 * 托管 * 添加新页面 * 添加新文章 * 安装方式 * 1. 接口清单(API Design) * 2. Controller 层实现 * 3. Service 层实现 * 4. Mapper 层(MyBatis-Plus) * (1) 好友关系实体 * (2) Mapper接口 * 5. 统一返回结构 * 6. 接口测试示例 * **(1) 添加好友** * **(2) 查询好友列表** * **关键设计说明** * **扩展建议** * 为什么需要为数据库的 email 字段建立索引 * 1. 提高查询性能 * 2. 保证数据唯一性(当需要时) * 3. 支持高级查询特性 * 注意事项 * 实际应用示例 * 关于前端使用openapi报错原因 * 解决方案

19. Flutter与Web混合开发实践:打造跨平台的统一体验

19. Flutter与Web混合开发实践:打造跨平台的统一体验 引言 Flutter 是一种强大的跨平台开发框架,它不仅可以开发移动应用,还可以开发 Web 应用。随着 Flutter Web 的不断成熟,Flutter 与 Web 混合开发成为了一种新的趋势。作为一名把代码当散文写的 UI 匠人,我始终认为:好的技术应该是无缝的,它应该让开发者能够自由地在不同平台之间切换,而不需要为每个平台重新开发。Flutter 与 Web 混合开发,就是为了实现这种无缝的体验。 什么是 Flutter 与 Web 混合开发? Flutter 与 Web 混合开发是指在同一个项目中,同时使用 Flutter 和 Web 技术(如 HTML、CSS、JavaScript)来开发应用。这种开发方式可以结合

OpenClaw Scanner:开源利器出鞘,筑牢自主AI Agent安全防线——技术解析、实操指南与前瞻展望

随着生成式AI技术的飞速迭代,自主AI Agent(智能代理)已从实验室走向企业实际应用,成为提升工作效率、自动化复杂任务的重要工具。但与此同时,未授权、高权限自主AI Agent的无序部署,正逐渐成为企业网络安全的“隐形炸弹”。 在此背景下,Astrix Security于2026年2月正式推出OpenClaw Scanner——一款免费开源、零侵入的安全检测工具,专为精准识别企业环境中OpenClaw(曾用名MoltBot、ClawdBot)自主AI Agent的运行轨迹与潜在风险而生,其核心优势聚焦于“只读接入、本地运行、无端点执行、数据不出内网”,既兼顾检测效率,又最大限度保障企业数据安全,为企业应对自主AI Agent带来的安全挑战提供了轻量化、可落地的解决方案。 一、行业背景:自主AI Agent的崛起与OpenClaw的安全隐患凸显 近年来,自主AI Agent凭借“自主决策、跨场景交互、可扩展能力”三大核心特性,在企业办公自动化、代码开发、跨系统协同等场景中快速普及。这类智能代理能够自主理解任务需求、调用相关工具、

AI股票分析师(daily_stock_analysis)详细步骤:从镜像拉取、启动、测试到报告导出

AI股票分析师(daily_stock_analysis)详细步骤:从镜像拉取、启动、测试到报告导出 1. 镜像核心能力与使用价值 你是否想过,不用登录任何金融平台、不依赖外部API、也不用担心数据泄露,就能随时获得一份结构清晰、逻辑严谨的股票分析简报?AI股票分析师(daily_stock_analysis)镜像正是为此而生——它不是另一个云端SaaS工具,而是一套真正跑在你本地机器上的私有化金融分析助手。 这个镜像不处理真实行情数据,也不连接交易所接口,它的全部能力都建立在一个关键前提上:用专业Prompt引导本地大模型,生成符合分析师思维框架的虚构但可信的解读。这意味着,你输入MSFT,它不会查股价,但会基于训练知识,以专业口吻告诉你“近期表现如何”“潜在风险在哪”“未来展望怎样”。这种“结构化虚构生成”,恰恰是金融初学者理解分析逻辑、内容创作者快速产出投教素材、甚至产品经理验证产品话术的理想沙盒环境。 更重要的是,它把原本需要配置Docker、安装Ollama、下载模型、调试WebUI的一整套流程,压缩成一次命令、一次点击。你不需要知道gemma:2b是什么模型,