跳到主要内容ES6 核心语法全解析:let/const、箭头函数、异步处理 | 极客日志JavaScriptNode.js大前端
ES6 核心语法全解析:let/const、箭头函数、异步处理
ES6 + 核心语法全解析(极简可运行代码 + 避坑 + 快速回顾) 前言 学 ES6 语法时总记混 let/const 作用域、箭头函数 this 指向、解构赋值传参规则,还踩过'const 定义对象改属性报错''模板字符串换行空格'的坑,整理了 10 个高频核心语法的「问题 + 思路 + 极简例子」,每个例子都能直接复制运行,方便自己后续快速唤醒记忆,也能让新手看懂核心用法。 --- 一、核心…
安卓系统22K 浏览 ES6 + 核心语法全解析(极简可运行代码 + 避坑 + 快速回顾)
前言
学 ES6 语法时总记混 let/const 作用域、箭头函数 this 指向、解构赋值传参规则,还踩过'const 定义对象改属性报错''模板字符串换行空格'的坑,整理了 10 个高频核心语法的「问题 + 思路 + 极简例子」,每个例子都能直接复制运行,方便自己后续快速唤醒记忆,也能让新手看懂核心用法。
一、核心思路 / 概念
ES6(ECMAScript 2015)及后续版本是 JavaScript 的重大升级,核心是:比如用 解决 的全局 / 函数作用域混乱问题,用箭头函数简化回调写法并固定 指向,用解构 / 扩展运算符快速操作数组 / 对象,用 解决回调地狱。所有语法都围绕'写更少的代码,做更多的事',且完全兼容日常开发(如 uni-app、前端项目)。
解决旧语法痛点 + 简化代码
let/const
var
this
Promise/Async-Await
二、实操步骤 + 例子
2.1 语法 1:let/const(替代 var,解决作用域问题)
问题
var 只有全局 / 函数作用域,容易变量提升、重复声明导致 bug(比如循环里的变量泄露)。
核心思路
let 是块级作用域变量(可重新赋值),const 是块级作用域常量(引用不可变),两者都不能重复声明、不存在变量提升。
极简可运行例子
var a = 1;
if (true) {
var a = 2;
}
console.log(a);
let b = 1;
if (true) {
let b = 2;
}
console.log(b);
const PI = 3.14;
const user = { name: "张三" };
user.name = "李四";
console.log(user.name);
2.2 语法 2:箭头函数(简化回调 + 固定 this 指向)
问题
传统函数的 this 指向随调用场景变化(比如定时器里 this 指向 window),回调函数写法繁琐。
核心思路
箭头函数无自身 this(继承外层作用域的 this),语法更简洁,适合简单回调 / 纯函数。
极简可运行例子
const person = {
name: "张三",
sayName: function () {
setTimeout(function () {
console.log(this.name);
}, 100);
},
};
person.sayName();
const person2 = {
name: "张三",
sayName: function () {
setTimeout(() => {
console.log(this.name);
}, 100);
},
};
person2.sayName();
const add = (a, b) => a + b;
console.log(add(1, 2));
const fn = a => a * 2;
console.log(fn(3));
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);
2.4 语法 4:解构赋值(快速提取数组 / 对象数据)
问题
从数组 / 对象中取数据需要逐个赋值,代码冗余(比如 const name = user.name; const age = user.age;)。
核心思路
按数组 / 对象的结构'拆解',直接提取所需数据,支持默认值、重命名。
极简可运行例子
const arr = [1, 2, 3];
const [a, b] = arr;
console.log(a, b);
const [x, , z] = arr;
console.log(x, z);
const user = { name: "张三", age: 20 };
const { name, age } = user;
console.log(name, age);
const { name: userName } = user;
console.log(userName);
function printUser({ name, age = 18 }) {
console.log(`${name},${age}岁`);
}
printUser(user);
printUser({ name: "李四" });
2.5 语法 5:扩展运算符(快速合并 / 复制数组 / 对象)
问题
传统合并数组用 concat,复制数组用 slice,合并对象用 Object.assign,写法繁琐。
核心思路
用 ... 展开数组 / 对象,实现一键合并、复制(浅拷贝),替代传统方法。
极简可运行例子
const arr1 = [1, 2];
const arr2 = [3, 4];
const mergeArr = [...arr1, ...arr2];
console.log(mergeArr);
const copyArr = [...arr1];
console.log(copyArr);
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, b: 3, c: 4 };
console.log(obj2);
const copyObj = { ...obj1 };
console.log(copyObj);
2.6 语法 6:对象简写(减少冗余代码)
问题
对象属性名和变量名相同时,需要重复写 key: value;方法需要写 function 关键字。
核心思路
变量名 = 属性名时可省略 key:,方法可省略 function,支持动态属性名。
极简可运行例子
const name = "张三";
const age = 20;
const user = { name, age };
console.log(user);
const obj = {
sayHi() {
console.log("Hi");
},
};
obj.sayHi();
const key = "status";
const obj2 = {
[key]: "active",
[`get_${key}`]() {
return this[key];
},
};
console.log(obj2.status);
console.log(obj2.get_status());
2.7 语法 7:Promise/Async-Await(解决回调地狱)
问题
多层异步回调(比如先请求用户数据,再请求用户订单)会形成'回调地狱',代码嵌套深、难维护。
核心思路
Promise 将异步操作封装为对象,用 then/catch 链式调用;Async-Await 是 Promise 的语法糖,用同步写法写异步代码。
极简可运行例子
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "张三" });
}, 1000);
});
}
fetchData()
.then(data => {
console.log(data);
return data.name;
})
.then(name => console.log(name))
.catch(error => console.error(error));
async function loadData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
}
loadData();
2.8 语法 8:Set/Map(解决数组去重 / 对象键限制)
问题
数组去重需要遍历 + 判断,对象的键只能是字符串 / Symbol,无法用对象做键。
核心思路
Set 是唯一值集合(一键去重),Map 是键值对集合(键可以是任意类型)。
极简可运行例子
const arr = [1, 2, 2, 3];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr);
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, "张三");
console.log(map.get(objKey));
console.log(map.has(objKey));
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();
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();
s.sayGrade();
2.10 语法 10:模块化(export/import,解决全局变量污染)
问题
多个 JS 文件共用变量时会造成全局污染,代码无法隔离复用。
核心思路
用 export 导出模块内的变量 / 函数 / 类,用 import 导入到其他文件,实现代码隔离和复用。
极简可运行例子
export const PI = 3.14;
export function add(a, b) {
return a + b;
}
export default function mul(a, b) {
return a * b;
}
import mul, { PI, add } from './module.js';
console.log(PI);
console.log(add(1, 2));
console.log(mul(2, 3));
三、踩坑记录 + 解决方案
- ❌ 坑 1:
const 定义对象后改属性报错 → ✅ 解决:const 锁的是'引用地址',不是值,对象 / 数组属性可正常修改,只有重新赋值(user = {})才会报错;
- ❌ 坑 2:箭头函数里用
arguments → ✅ 解决:箭头函数无 arguments 对象,需用剩余参数 ...args 替代;
- ❌ 坑 3:模板字符串里的换行 / 空格会被保留 → ✅ 解决:如果不需要空格,可把代码写在一行,或用
trim() 去除首尾空格;
- ❌ 坑 4:解构赋值时变量名写错 → ✅ 解决:对象解构按'属性名'匹配,数组解构按'顺序'匹配,写错会得到
undefined;
- ❌ 坑 5:扩展运算符深拷贝对象 → ✅ 解决:扩展运算符是浅拷贝,嵌套对象 / 数组需用
JSON.parse(JSON.stringify()) 或专门的深拷贝方法;
- ❌ 坑 6:
Async-Await 未用 try/catch 捕获错误 → ✅ 解决:await 的异步错误必须用 try/catch 捕获,否则会静默失败。
四、总结
- 核心价值:ES6 + 的所有语法都是'解决旧痛点 + 简化代码',优先用
let/const 替代 var,用箭头函数简化回调,用解构 / 扩展运算符操作数据,用 Async-Await 写异步代码;
- 高频用法:日常开发中
let/const、模板字符串、解构 / 扩展运算符、Async-Await 是使用频率最高的 5 个语法,必须掌握;
- 避坑关键:记住
const 的'引用不可变'、箭头函数的 this 继承规则、扩展运算符的浅拷贝特性,能避免 80% 的 ES6 使用坑;
- 适用场景:这些语法完全适配 uni-app、Vue/React 等前端框架,直接复制例子就能在项目中使用。
ES6+ 核心语法综合练习题(题目 + 答案合一版)
一、综合场景题(3 道,侧重实战综合应用)
场景题 1:异步数据处理与重组(综合箭头函数 / 解构 / 扩展运算符 / Async-Await/Set)
题目需求
模拟前端请求「用户列表」和「用户订单列表」两个异步接口,完成以下操作:
- 用
Async-Await 并行请求两个接口(模拟接口用 Promise 封装,延迟 1 秒返回数据);
- 合并用户数据与订单数据,为每个用户添加
orders 字段(匹配 userId);
- 过滤出「有订单的用户」,并对用户的订单金额去重后求和;
- 最终返回格式:
[{ 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 }
]);
}, 1000));
}
题目要求
- 必须使用
Async-Await 处理异步,禁止使用 then/catch 链式调用;
- 订单金额去重必须用
Set 实现;
- 合并数据时需用解构 / 扩展运算符简化代码;
- 过滤条件:订单金额 > 0 且 有订单的用户。
参考答案
async function handleUserAndOrders() {
const [users, orders] = await Promise.all([fetchUsers(), fetchOrders()]);
const result = users.map(user => {
const userOrders = orders.filter(order => order.userId === user.id && order.amount > 0);
if (userOrders.length === 0) return null;
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);
return result;
}
handleUserAndOrders().then(res => console.log(res));
场景题 2:购物车类封装(综合 Class/Map/ 对象简写 / 模板字符串 / 解构)
题目需求
封装一个 ShoppingCart 类,实现购物车核心功能:
- 用
Map 存储商品(键:商品 id,值:{id, name, price, count});
- 实现方法:
addGoods(goods):添加商品(若已存在则累加数量,商品格式:{id, name, price});
removeGoods(goodsId):删除指定 id 的商品;
getTotalPrice():计算购物车商品总价(价格 × 数量求和);
getCartInfo():返回购物车信息字符串,格式:"购物车:商品 1(数量:2,单价:100)、商品 2(数量:1,单价:200),总价:400"。
- 类的方法需使用「对象简写语法」,字符串拼接需用模板字符串。
测试用例
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());
console.log(cart.getCartInfo());
cart.removeGoods(2);
console.log(cart.getCartInfo());
参考答案
class ShoppingCart {
constructor() {
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 {
this.goodsMap.set(id, { id, name, price, count: 1 });
}
}
removeGoods(goodsId) {
this.goodsMap.delete(goodsId);
}
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());
console.log(cart.getCartInfo());
cart.removeGoods(2);
console.log(cart.getCartInfo());
场景题 3:模块化工具函数封装(综合模块化 / 解构 / 扩展运算符 /const/ 箭头函数)
题目需求
封装一个 formatUtil.js 工具模块,实现以下功能:
- 导出常量
DEFAULT_FORMAT(值:{ date: "YYYY-MM-DD", money: "¥0.00" });
- 导出命名函数
formatDate:接收日期对象 / 时间戳,返回指定格式的日期字符串(默认用 DEFAULT_FORMAT.date);
- 导出命名函数
formatMoney:接收数字,返回指定格式的金额字符串(默认用 DEFAULT_FORMAT.money);
- 导出默认函数
formatData:接收数据对象({date, money}),合并默认配置,返回格式化后的对象;
- 题目要求:
- 所有变量用
const 声明,函数用箭头函数;
- 函数参数需用解构 + 默认值简化代码;
- 合并配置时需用扩展运算符。
调用示例(app.js)
import formatData, { formatDate, formatMoney, DEFAULT_FORMAT } from './formatUtil.js';
console.log(formatDate(new Date(2026, 0, 27)));
console.log(formatMoney(100));
console.log(formatData({ date: new Date(2026, 0, 27), money: 200 }));
console.log(formatData({ money: 300 }, { money: "$0.00" }));
参考答案
formatUtil.js 模块代码
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");
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)));
console.log(formatMoney(100));
console.log(formatData({ date: new Date(2026, 0, 27), money: 200 }));
console.log(formatData({ money: 300 }, { money: "$0.00" }));
二、问答题(5 道,侧重理解与避坑)
问答题 1
题目:let/const 与 var 的核心区别有哪些?为什么实际开发中推荐优先使用 const 而非 let?
- 核心区别
- 作用域:
var 是函数 / 全局作用域,let/const 是块级作用域({} 包裹的区域,如 if/for/while);
- 变量提升:
var 存在变量提升(可先使用后声明),let/const 存在暂时性死区(声明前使用会报错,无变量提升);
- 重复声明:
var 允许同一作用域内重复声明,let/const 不允许;
- 初始值:
const 声明时必须初始化,var/let 可后续赋值;
- 全局属性:
var 声明的全局变量会挂载到 window/global 上,let/const 不会。
- 优先使用 const 的原因
- 语义化更强:
const 明确表示变量引用不可变,代码可读性更高,其他开发者能快速知道该变量不会被重新赋值;
- 减少意外 bug:避免开发中无意修改变量值,若后续发现需要修改,再将
const 改为 let 即可,属于'最小权限原则';
- 符合函数式编程思想:强调数据不可变,减少副作用,让代码更稳定、易维护;
- 对引用类型友好:
const 仅锁引用地址,不锁值(对象 / 数组属性可正常修改),不影响日常使用。
问答题 2
题目:箭头函数和普通函数的核心差异体现在哪些方面?请列举至少 3 点,并说明哪些场景绝对不能使用箭头函数(举例说明)。
- 核心差异(至少 3 点)
this 指向:箭头函数无自身 this,继承外层作用域的 this;普通函数的 this 指向调用者(谁调用指向谁,全局调用指向 window/undefined);
arguments 对象:箭头函数无 arguments 对象,需用剩余参数...args替代;普通函数有 arguments 对象(存储实参的类数组);
- 构造函数:箭头函数不能作为构造函数(不能用 new 调用,无 prototype 原型属性);普通函数可以作为构造函数;
- 原型属性:箭头函数没有 prototype 属性;普通函数有 prototype 属性,可通过原型扩展方法;
- 绑定方式:箭头函数的 this一旦确定无法修改(call/apply/bind 无法改变);普通函数的 this 可通过 call/apply/bind 手动修改。
- 绝对不能使用箭头函数的场景(举例)
- 生成器函数:箭头函数不能使用 yield 关键字,无法作为生成器函数。
- 需要动态 this 的场景(如事件回调、DOM 操作):箭头函数的 this 无法指向触发事件的 DOM 元素;
const btn = document.querySelector('button');
btn.onclick = () => {
console.log(this)
};
- 类的实例方法 / 原型方法:箭头函数会导致每个实例创建新的函数,浪费内存,且 this 不指向实例;
class Person {
name = "张三";
sayName = () => {
console.log(this.name)
};
}
- 对象的方法:箭头函数的 this 继承全局,而非对象本身,导致取值失败;
const obj = {
name: "张三",
sayName: () => {
console.log(this.name)
}
};
obj.sayName();
问答题 3
题目:解构赋值是 ES6 的高频用法,请列举 3 个解构赋值的典型业务场景,并说明:解构对象时如果目标属性不存在,如何避免获取到 undefined?
- 解构赋值的 3 个典型业务场景
- 补充:嵌套数据解构(如 JSON 数据、复杂对象)、遍历解构(如 Map / 数组的键值对遍历)也属于高频场景。
- 解构对象避免获取到 undefined 的方法
- 解构前判断对象是否存在:先判断源对象是否为 null/undefined,再解构,避免基础报错;
const user = null;
const { name } = user || {};
- 可选链 + 默认值结合:先通过可选链判断属性是否存在,不存在则用 ||/?? 设置默认值;
const user = { name: "张三" };
const age = user?.info?.age ?? 0;
- 嵌套解构的默认值:若解构嵌套对象,需为外层对象也设置默认值,避免外层属性不存在时报错;
const { info: { age = 0 } } = { name: "张三" };
const { info: { age = 0 } = {} } = { name: "张三" };
- 设置属性默认值:解构时直接为属性设置默认值,属性不存在时使用默认值;
const { name = "未知用户", age = 0 } = { name: "张三" };
- 数组解构与变量交换:无需临时变量,快速交换两个变量的值,适合排序、交换场景;
let a = 1, b = 2;
[a, b] = [b, a];
- 接口数据解构:快速提取接口返回的核心数据,简化代码,避免多次点语法取值;
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;
}
方式 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);
问答题 5
题目:Async-Await 的本质是什么?它和 Promise 的关系是什么?使用 Async-Await 时,为什么必须用 try/catch 捕获错误?如果不捕获会有什么问题?
- Async-Await 的本质
Async-Await 是ES7 推出的语法糖,底层完全基于Promise实现,其核心作用是将异步代码以同步的写法呈现,解决了 Promise 链式调用(then/catch)的嵌套问题,让异步代码的可读性和维护性更高。
- 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 实现。
- 必须用 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;
});
console.log(res);
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 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