跳到主要内容
日常写 JS 最常用的 10 个 ES6 语法,附避坑和综合练习 | 极客日志
JavaScript 大前端
日常写 JS 最常用的 10 个 ES6 语法,附避坑和综合练习 以可运行示例讲解 let/const、箭头函数、模板字符串、解构、扩展运算符、对象简写、Promise/async-await、Set/Map、Class、模块化等 10 个高频 ES6 特性的用法与常见坑点,每个语法点都附有可直接执行的代码。后半部分提供三个综合实战练习和五道问答题,覆盖异步数据处理、购物车封装、模块化工具编写等场景,并总结了解构默认值、浅拷贝陷阱、async 错误处理等关键细节。
片刻 发布于 2026/6/27 更新于 2026/7/2 2 浏览写 JS 久了,let/const 作用域、箭头函数 this 指向、模板字符串换行这些地方还是容易混淆。这里整理了 10 个高频核心语法,每个都附带可直接运行的代码示例和常见的坑,方便快速回忆。
1. let 和 const:用块级作用域告别变量泄漏
var 只有全局和函数作用域,一不小心就会变量提升或者重复声明导致问题。let 和 const 都是块级作用域,不能重复声明,也没有变量提升。
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 );
坑:以为 const 对象就完全不能改,后来发现属性随便改,只有重新赋值 user = {} 才报错。
2. 箭头函数:更短的函数写法,但 this 很特殊
箭头函数没有自己的 this,它会从外层作用域继承。这让它在回调函数或定时器里特别好用,但用在对象方法或需要动态 this 的地方就容易出问题。
const person = {
name : ,
: ( ) {
( ( ) {
. ( . );
}, );
},
};
person. ();
person2 = {
: ,
: ( ) {
( {
. ( . );
}, );
},
};
person2. ();
= ( ) => a + b;
= a => a * ;
"张三"
sayName
function
setTimeout
function
console
log
this
name
100
sayName
const
name
"张三"
sayName
function
setTimeout
() =>
console
log
this
name
100
sayName
const
add
a, b
const
fn
2
坑:箭头函数没有 arguments 对象,需要改用剩余参数 ...args。
3. 模板字符串:终于不用 + 号拼接了 用反引号包裹,${} 里可以放变量或表达式,还支持原生换行,比以前的拼接清爽太多。
const name = "张三" ;
const age = 20 ;
const str = `姓名:${name} ,年龄:${age}
多行字符串,不用再写 \n
表达式也能算:${1 + 2 } ` ;
console .log (str);
坑:模板字符串里的空格和换行会原样保留,如果不需要,可以写成一行或者用 trim()。
4. 解构赋值:快速从数组和对象里取数据 解构可以一下子从数组或对象里提取需要的字段,还能设默认值、起别名。
const arr = [1 , 2 , 3 ];
const [a, b] = arr;
const [x, , z] = arr;
const user = { name : "张三" , age : 20 };
const { name, age } = user;
const { name : userName } = user;
function printUser ({ name, age = 18 } ) {
console .log (`${name} ,${age} 岁` );
}
printUser (user);
printUser ({ name : "李四" });
坑:对象解构是按属性名匹配,写错名或者属性不存在就是 undefined。如果要解构嵌套对象,还得给外层一个默认值,防止直接报错。
5. 扩展运算符:复制和合并的利器 三个点 ... 可以展开数组或对象,用来合并、复制很方便,但记住这是浅拷贝。
const arr1 = [1 , 2 ];
const arr2 = [3 , 4 ];
const mergeArr = [...arr1, ...arr2];
const copyArr = [...arr1];
const obj1 = { a : 1 , b : 2 };
const obj2 = { ...obj1, b : 3 , c : 4 };
const copyObj = { ...obj1 };
坑:延伸运算符只拷贝一层,嵌套的对象/数组还是共享引用,要彻底拷贝得用 JSON.parse(JSON.stringify()) 或者 lodash 的 cloneDeep。
6. 对象属性方法和动态属性名 当变量名和属性名一样时可以省略值,方法可以直接写 sayHi() {},还能用方括号动态生成属性名。
const name = "张三" ;
const age = 20 ;
const user = { name, age };
const obj = {
sayHi ( ) {
console .log ("Hi" );
}
};
const key = "status" ;
const obj2 = {
[key]: "active" ,
[`get_${key} ` ]() {
return this [key];
}
};
console .log (obj2.status );
console .log (obj2.get_status ());
7. Promise 和 async-await:让异步代码像同步 Promise 把回调封装成链式调用,而 async-await 是它的语法糖,能用同步的写法处理异步,彻底告别回调地狱。
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 ();
坑:await 后面的 Promise 如果 reject 了,又没有 try-catch 包住,整个 async 函数会直接中断,后续代码不执行,错误也容易被吞掉。记得一定要 try-catch 或者在 await 后面跟 .catch() 处理。
8. Set 和 Map:处理唯一值和任意键 Set 专门存唯一值,用来去重特方便。Map 的键可以是任意类型,不像普通对象键只能是字符串。
const arr = [1 , 2 , 2 , 3 ];
const uniqueArr = [...new Set (arr)];
const map = new Map ();
const objKey = { id : 1 };
map.set (objKey, "张三" );
console .log (map.get (objKey));
9. Class:更清晰的面向对象写法 以前的构造函数和原型让人头大,Class 只是语法糖,但看起来舒服很多,继承也简单。
class Person {
constructor (name, age ) {
this .name = name;
this .age = age;
}
sayHi ( ) {
console .log (`Hi, ${this .name} ` );
}
}
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 ();
10. 模块化:export 和 import 隔离代码 有了模块,变量不会污染全局,一个文件就是一个模块,用 export 导出,import 导入。
export const PI = 3.14 ;
export function add (a, b ) {
return a + b;
}
export default function mul (a, b ) {
return a * b;
}
常见坑点速查
const 定义的对象,属性随便改,只有重新赋值整个对象才报错。
箭头函数没有 arguments,用 ...args 代替。
模板字符串会保留空格和换行,必要时用 trim()。
解构对象时如果属性不存在就是 undefined,可以加默认值,或先判空。
扩展运算符是浅拷贝,深拷贝要用其他方式(JSON.parse(JSON.stringify()) 慎用,有失效场景)。
async-await 必须配合 try-catch 或者 .catch() 处理错误,否则静默失败。
综合场景练习 下面是几个结合实际应用的练习题,覆盖了上面大部分语法,可以练练手。
场景1:异步数据处理与重组 模拟请求两个异步接口:用户列表和订单列表。要求用 async-await 并行请求,然后合并数据:为每个用户加上对应订单,并计算去重后的金额总和,最后只保留有有效订单的用户。
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 function handleUserAndOrders ( ) {
const [users, orders] = await Promise .all ([fetchUsers (), fetchOrders ()]);
return users
.map (user => {
const userOrders = orders.filter (
order => order.userId === user.id && order.amount > 0
);
if (!userOrders.length ) return null ;
const uniqueAmounts = new Set (userOrders.map (o => o.amount ));
const totalAmount = [...uniqueAmounts].reduce ((sum, amt ) => sum + amt, 0 );
return { ...user, orders : userOrders, totalAmount };
})
.filter (Boolean );
}
handleUserAndOrders ().then (console .log );
场景2:购物车类封装 实现一个 ShoppingCart 类,用 Map 存储商品,提供添加(可累加数量)、删除、计算总价、获取购物车信息字符串的功能。要求用对象简写、模板字符串、解构等语法。
class ShoppingCart {
constructor ( ) {
this .goodsMap = new Map ();
}
addGoods (goods ) {
const { id, name, price } = goods;
if (this .goodsMap .has (id)) {
const old = this .goodsMap .get (id);
this .goodsMap .set (id, { ...old, count : old.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 (g => `${g.name} (数量:${g.count} ,单价:${g.price} )` )
.join ("、" );
return `购物车:${goodsStr} ,总价:${this .getTotalPrice()} ` ;
}
}
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:模块化工具函数封装 写一个 formatUtil.js 模块,导出常量 DEFAULT_FORMAT、命名函数 formatDate、formatMoney 和默认函数 formatData,使用 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" );
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;
import formatData, { formatDate, formatMoney } 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?
作用域:var 是函数/全局作用域,let/const 是块级作用域。
变量提升:var 有提升,let/const 有暂时性死区。
重复声明:var 允许,let/const 不允许。
const 必须初始化,且引用不可变(但属性可改)。
优先用 const 是因为语义更清晰,减少意外修改变量的风险,符合最小权限原则。
2. 箭头函数和普通函数的核心差异?哪些场景不能用箭头函数?
箭头函数没有自己的 this,继承外层;普通函数的 this 由调用决定。
没有 arguments,用 ...args。
不能作为构造函数,没有 prototype。
不适用场景:需要动态 this 的事件处理、对象方法、需要 new 的构造函数、生成器函数。
3. 解构赋值典型场景和避免 undefined 的方法?
场景:接口数据提取、函数参数简化、变量交换。
避免 undefined:设置默认值 const { age = 0 } = obj;判空 const { info } = data || {};可选链结合空值合并 const age = data?.info?.age ?? 0;嵌套解构时给外层默认值 const { info: { age } = {} } = data。
浅拷贝只复制第一层,嵌套对象共享引用。
深拷贝方式:JSON.parse(JSON.stringify())(简单但有局限,无法处理函数、日期、正则、循环引用);手写递归(灵活但复杂,需要处理各种类型);lodash.cloneDeep(稳定,但增加包大小)。
async-await 是 Promise 的语法糖,async 函数返回 Promise,await 等待 Promise 结果。
必须用 try-catch 或者 .catch() 捕获错误,否则 await 的 reject 会静默错误中断后续执行,错误可能不被察觉。
以上基本涵盖了日常开发中最常用的 ES6 语法,把每个例子的代码跑一遍,碰到相关问题心里就有底了。
相关免费在线工具 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