【前端|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

具身机器人的软件系统架构

具身机器人的软件系统架构

具身机器人作为能够与物理世界直接交互、具备环境感知与自主决策能力的智能系统,其软件架构的核心目标是实现“感知-决策-执行”的闭环协同,同时满足实时性、可靠性、可扩展性与模块化的设计要求。基于这一目标,主流的具身机器人软件系统通常采用分层架构设计,从上至下依次分为感知层、认知决策层、运动控制层,辅以通信层、驱动层和系统管理层作为支撑,各层通过标准化接口实现数据流转与功能协同。以下将详细拆解各层的核心功能、关键技术及典型模块。 一、核心分层架构:从感知到执行的闭环 分层架构的优势在于将复杂的系统功能解耦为独立模块,便于开发迭代、故障定位与功能扩展。各层既各司其职,又通过数据总线或中间件实现高效交互,形成完整的智能行为链条。 1. 感知层:物理世界的“数据入口” 感知层是机器人获取外部环境与自身状态信息的基础,核心任务是将传感器采集的原始数据转化为结构化的语义信息,为上层决策提供可靠输入。其核心要求是实时性、准确性与鲁棒性,需应对光照变化、动态障碍物、传感器噪声等复杂场景干扰。 主要模块及技术要点如下: * 多传感器数据采集模块:负责接入各类传感器数据,包括视觉传感器(单目

一、FPGA到底是什么???(一篇文章让你明明白白)

一句话概括 FPGA(现场可编程门阵列) 是一块可以通过编程来“变成”特定功能数字电路的芯片。它不像CPU或GPU那样有固定的硬件结构,而是可以根据你的需求,被配置成处理器、通信接口、控制器,甚至是整个片上系统。 一个生动的比喻:乐高积木 vs. 成品玩具 * CPU(中央处理器):就像一个工厂里生产好的玩具机器人。它的功能是固定的,你只能通过软件(比如按不同的按钮)来指挥它做预设好的动作(走路、跳舞),但你无法改变它的机械结构。 * ASIC(专用集成电路):就像一个为某个特定任务(比如只会翻跟头)而专门设计和铸造的金属模型。性能极好,成本低(量产时),但一旦制造出来,功能就永远无法改变。 * FPGA:就像一盒万能乐高积木。它提供了大量基本的逻辑单元(逻辑门、触发器)、连线和接口模块。你可以通过“编程”(相当于按照图纸搭建乐高)将这些基本模块连接起来,构建出你想要的任何数字系统——可以今天搭成一个CPU,明天拆了重新搭成一个音乐播放器。 “现场可编程”

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)(原命名为时间证明公式算法(TCC)) 本共识算法以「时间长河」为核心设计理念,通过时间节点服务器按固定最小时间间隔打包区块,构建不可篡改的历史数据链,兼顾区块链的金融属性与信用属性,所有优化机制形成完整闭环,无核心逻辑漏洞,具体总结如下: 一、核心机制(闭环无漏洞) 1. 节点准入与初始化:候选时间节点需先完成全链质押,首个时间节点由所有质押节点投票选举产生,彻底杜绝系统指定带来的初始中心化问题,实现去中心化初始化。 2. 时间节点推导与防作弊:下一任时间节点通过共同随机数算法从上一区块推导(输入参数:上一区块哈希、时间戳、固定数据顺序),推导规则公开可验证;时间节点需对数据顺序签名,任一节点发现作弊(篡改签名、操控随机数等),该节点立即失去时间节点资格并扣除全部质押。质押的核心目的是防止节点为持续获取区块打包奖励作弊,作弊损失远大于收益,确保共同随机数推导百分百不可作弊。 3. 节点容错机制:每个时间节点均配置一组合规质押节点构成的左侧顺邻节点队列(队列长度可随全网节点规

Flowise应用场景拓展:Web Scraping与文档问答一体化方案

Flowise应用场景拓展:Web Scraping与文档问答一体化方案 1. Flowise是什么:让AI工作流变得像搭积木一样简单 Flowise 是一个在2023年开源的可视化低代码平台,它的核心目标很实在:把原本需要写几十行LangChain代码才能实现的AI功能,变成拖拽几下就能跑起来的流程。你不需要记住DocumentLoader、TextSplitter、Embeddings这些术语,也不用反复调试向量库配置——所有这些能力都被封装成了一个个带图标的节点,像拼乐高一样连起来,工作流就完成了。 它不是另一个“概念验证型”工具,而是真正能落地的生产力平台。比如,你想把公司内部的PDF手册、Confluence页面、甚至微信公众号历史文章变成可随时提问的知识库,Flowise 提供了开箱即用的模板,点一下“导入”,选个文件或填个URL,再连上本地大模型,5分钟内就能得到一个能回答“我们报销流程是怎样的?”“新员工入职要准备哪些材料?”的问答机器人。 更关键的是,它不绑架你。你可以用OpenAI,也可以切到本地运行的Qwen2、Phi-3或Llama-3;可以存向量到内