跳到主要内容
ES6 核心语法全解析:避坑指南与实战代码 | 极客日志
JavaScript Node.js 大前端
ES6 核心语法全解析:避坑指南与实战代码 ES6 语法升级解决了旧版本作用域混乱与回调地狱问题。内容涵盖 let/const、箭头函数、解构赋值等 10 个核心语法点,配合避坑指南与实战练习题。重点解析 const 引用不可变、箭头函数 this 指向及异步处理规范,适合快速回顾或新手入门。
BigDataPan 发布于 2026/4/8 0 浏览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 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. = ;
. (user. );
name
"李四"
console
log
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:模板字符串(解决字符串拼接繁琐) 痛点
传统字符串拼接需要 + 号,多行字符串需要 转义,易出错。
核心思路
用反引号 ` 包裹字符串,${变量/表达式}插值,支持原生多行,无需转义。
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 声明,函数用箭头函数;
函数参数需用解构 + 默认值简化代码;
合并配置时需用扩展运算符。
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 种实现方式,以及每种方式的优缺点。
浅拷贝的定义 :浅拷贝仅复制对象/数组的第一层属性 ,对于嵌套的对象/数组 ,不会递归复制,而是直接复制其内存引用地址 。修改拷贝后对象的嵌套属性,原对象的嵌套属性也会被修改 ,因为二者指向同一块内存空间。
const obj1 = { a : 1 , b : { c : 2 } };
const obj2 = { ...obj1 };
obj2.b .c = 3 ;
console .log (obj1.b .c );
深拷贝的 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 捕获错误?如果不捕获会有什么问题?
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);
}
相关免费在线工具 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