
从日常开发里最常见的一个场景说起后端返回了一个订单列表你要把其中已支付的订单挑出来再把每单的金额格式化最后算一下总收入。放在 ES5 时代你得先for循环再if判断再建一个新数组push中间还容易漏掉return。现在用 ES6 的数组和对象高级方法三行代码搞定而且代码读起来就是筛选 → 映射 → 求和的声明式描述别人一眼能看懂你在干嘛。这篇文章聊的核心就是 ES6 里数组与对象的高阶操作map、filter、reduce、find、flat、Object.keys、Object.entries、解构赋值、可选链这些。它们解决的问题很具体怎么遍历更省心、怎么转换数据更直观、怎么从对象里安全取到想要的值、怎么把对象数组玩出花来。适合刚学完 JavaScript 基础语法、准备上手真实项目的人也适合写了好几年for循环想提升代码质量的开发者。我尽量把每个方法的适用场景、隐藏的坑、和旧写法的对比都讲透不讲空泛的概念全部落到能直接抄走的代码上。1. 为什么说数组和对象方法能改变你的编码方式先聊聊思路层面的东西。ES6 带来的不是几个新函数那么简单它把 JavaScript 的编程范式往声明式方向推了一大步。ES5 时代处理数组脑子里想的是怎么一步步做先建空数组、然后循环、判断、放进去。这叫做命令式编程每一步都是机器视角。ES6 的数组方法让你换个思路我要得到什么结果——筛选出符合条件的、把每一项映射成新结构、把整个数组合并成一个值。至于底层怎么循环你不用管方法内部已经处理好了。这两者的差别在写复杂逻辑时尤其明显。比如我要把用户列表里 18 岁以上的用户姓名取出来命令式写法const names []; for (let i 0; i users.length; i) { if (users[i].age 18) { names.push(users[i].name); } }用filter加map组合const names users .filter(user user.age 18) .map(user user.name);第二种写法没有中间变量没有循环控制没有我要往哪 push这种额外信息剩下的全是业务逻辑本身。代码量少了将近一半可读性反而更高。这里要澄清一个常见误解很多新手觉得用高级方法是炫技不如for循环实在。实际上这些方法在 V8 引擎里经过了深度优化map、filter的纯函数写法通常不会比手写循环慢在大多数业务场景下性能差异可以忽略不计。ES6 数组方法的设计初衷是让代码更贴近人的思维而不是给代码增加理解成本。对象方面同样如此。ES5 时代遍历对象要for...in加hasOwnProperty判断现在一个Object.entries拿到键值对数组再直接配合数组方法处理数据和结构的切换变得非常丝滑。这个思路的转变比记住某个 API 的语法重要得多。2. 数组高级方法逐个拆解数组方法是 ES6 里应用密度最高的部分几乎每个项目文件里都会出现几次。我把它们按功能分成五类每一类讲清楚干什么用的、怎么用、有哪些坑。2.1 map数组转换的第一选择map的作用是映射把原数组的每个元素按规则换成新值返回一个等长度的新数组。它不修改原数组这一点非常关键。const prices [10, 20, 30]; const withTax prices.map(price price * 1.1); // [11, 22, 33]原数组 prices 不变回调函数里除了当前值还能拿到索引和原数组本身map((value, index, array) ...)。索引这个参数在处理二维数组、需要拼接序号时很常用。用得最多的场景有两类一类是字段提取把对象数组变成简单数组。比如const products [{ name: 手机, price: 3000 }, { name: 耳机, price: 500 }]; const names products.map(p p.name); // [手机, 耳机]另一类是结构重组把一组数据转换成展示层需要的结构const users [{ firstName: 张, lastName: 三 }]; const fullNames users.map(u ({ fullName: ${u.firstName}${u.lastName}, initial: u.firstName[0] }));用map的一个重要心法是尽量保持纯函数。也就是说回调里不要修改外部的变量比如不要在map里console.log更不要在里面做顺便把某个全局值改了这种事。保持纯函数代码才好调试才好测试。有个容易出错的小地方map回调必须写return或者用箭头函数的简写形式单个表达式时自动返回。如果你写的是const result arr.map(item { item.checked true; });这段代码返回的是一个包含undefined的数组因为花括号里有语句但没有return。正确写法要么加return item要么用item ({ ...item, checked: true })返回一个新对象。2.2 filter筛选逻辑的宜人化filter从名字就能看出来它是做过滤的。回调函数返回true则保留该元素返回false则丢弃原数组同样不变。const nums [1, 2, 3, 4, 5]; const evens nums.filter(n n % 2 0); // [2, 4]实际开发里经常遇到筛选条件不止一个的情况。你可以把判断逻辑提取成单独的函数让代码更清爽const isAvailable product product.stock 0 product.status on_sale; const availableProducts products.filter(isAvailable);这里有几个细节值得提filter回调的返回值不一定是布尔值JS 会做真假值转换所以return product.stock这种写法也合法数字 0、空字符串会被过滤掉。但为了可读性我建议还是写明确的比较表达式让别人一眼看出你的筛选条件。filter和map经常组合使用典型场景就是前端渲染前的数据预处理先过滤掉无效数据再映射出视图模型。这个组合是处理列表数据的第一板斧几乎能覆盖一半以上的日常需求。一个容易踩的坑filter不会改变数组元素本身。如果你想在过滤的同时修改某些内容不能靠filter实现需要搭配map。比如找出所有库存大于 0 的商品并把它们的价格打 8 折就得先filter再map两步走。2.3 reduce把数组变成任何你想要的东西reduce是数组方法里最灵活、也最劝退新人的一个。它的核心概念是归并从左到右遍历数组每次把上次的计算结果和当前元素一起传给回调最终得到一个累积值。基本语法const sum [1, 2, 3, 4].reduce((total, current) total current, 0); // 10第二个参数是初始值强烈建议永远显式写出来。如果不写初始值数组的第一项会被当作初始值从第二项开始遍历。这在空数组上会直接报错Reduce of empty array with no initial value。reduce能做的事远超你的想象它简直就是数据转换瑞士军刀。举例用reduce统计数组元素出现次数const fruits [apple, banana, apple, orange, banana, apple]; const count fruits.reduce((acc, fruit) { acc[fruit] (acc[fruit] || 0) 1; return acc; }, {}); // { apple: 3, banana: 2, orange: 1 }用reduce把对象数组按某个字段分组const people [ { name: 张三, dept: 前端 }, { name: 李四, dept: 后端 }, { name: 王五, dept: 前端 } ]; const byDept people.reduce((groups, person) { (groups[person.dept] || []).push(person); return groups; }, {}); // { 前端: [{ name: 张三 }, { name: 王五 }], 后端: [{ name: 李四 }] }这里用到了||逻辑赋值运算符它表示如果左边是假值就把右边的值赋给它在reduce里构建分组结构时特别顺手。reduce还有一个另类用法替代mapfilter的组合来减少遍历次数。比如同时完成筛选和转换// 用 reduce 一次遍历完成 const result numbers.reduce((acc, n) { if (n % 2 0) acc.push(n * 10); return acc; }, []);这种做法性能上有优势但可读性差一些。我的习惯是先考虑map和filter组合只有在组合后性能确实成为瓶颈、或者逻辑天然就是累积成对象/嵌套结构时才上reduce。毕竟可读性在绝大多数业务代码里的优先级高于那几毫秒的性能。2.4 查找与判定find、findIndex、some、every这四个方法解决的是判断数组里有没有、有没有满足条件的元素这个问题。find返回第一个满足条件的元素没有则返回undefinedconst user users.find(u u.id 42);findIndex返回第一个满足条件的索引没有则返回 -1。它和你熟悉的indexOf的区别在于indexOf只能按值查找findIndex可以按条件查找复杂场景下更灵活。some用来判断是否存在至少一个满足条件的元素返回布尔值。典型场景是表单校验判断数组里是否有非法值。every判断是否所有元素都满足条件也返回布尔值。它有一个短路机制遍历过程中只要遇到一个不满足条件的立即返回false后面的元素不会再被访问所以性能表现很好。这几个方法虽然简单但也容易混淆。我自己总结了一个记忆方式find和findIndex回答在不在、在哪儿some回答有没有every回答是不是都。需要注意闭包陷阱find回调里如果涉及异步数据或者依赖当前循环索引的变量要小心闭包带来的变量共享问题。好在find的回调参数里第二个就是index优先用参数而不是外部变量能避免大半闭包坑。2.5 flat、flatMap多维数组的降维打击flat(depth)把嵌套数组展平depth表示展平层数默认 1。Infinity表示彻底展平const nested [1, [2, [3, [4]]]]; nested.flat(1); // [1, 2, [3, [4]]] nested.flat(Infinity); // [1, 2, 3, 4]平时最常用的场景是处理接口返回的嵌套分类数据或者手写扁平化菜单。不过要注意flat会移除数组里的空位[1, , 3].flat()的结果是[1, 3]。这个特性有时能帮你清理脏数据有时也可能导致位置错位需要根据场景取舍。flatMap是map和flat(1)的组合先映射再展平一层。它和先map再flat的区别是flatMap只展平一层不能指定更深层数。典型场景是展开一对多关系const orders [ { id: 1, items: [鼠标, 键盘] }, { id: 2, items: [显示器] } ]; const allItems orders.flatMap(order order.items); // [鼠标, 键盘, 显示器]这个场景如果手动写需要两层循环嵌套flatMap一行就完事了。2.6 fill、Array.from批量生成和填充数组fill(value, start, end)用固定值填充数组。start和end是可选参数表示填充的起止索引含头不含尾。用它可以快速创建固定长度的测试数据Array(5).fill(0); // [0, 0, 0, 0, 0]这里有个大坑如果fill填充的是对象所有位置引用同一个对象。修改其中一个位置的对象其他位置的假象会跟着变因为它们是同一个引用。要生成互相独立的对象数组应该用Array.from配合工厂函数Array.from({ length: 3 }, (_, index) ({ id: index 1 })); // [{ id: 1 }, { id: 2 }, { id: 3 }]Array.from接收一个类数组对象或可迭代对象返回真正的新数组。它的第二个参数是映射函数相当于map。这个 API 在处理arguments、NodeList、字符串转数组时非常常用const str hello; const chars Array.from(str); // [h, e, l, l, o]凡是类数组对象想变成真数组第一反应都应该是Array.from而不是循环push。2.7 扩展运算符数组的合体与分身扩展运算符...使用频率极高它是浅拷贝的一等公民。合并数组const arr1 [1, 2]; const arr2 [3, 4]; const merged [...arr1, ...arr2]; // [1, 2, 3, 4]复制数组const copy [...arr1]; copy.push(5); // arr1 仍然是 [1, 2]copy 是 [1, 2, 5]这个复制是浅拷贝。如果数组元素是对象复制后内外层的对象引用仍然相同。换句话说[...arr]只是最外层数组换了个新壳里面的对象还是那些对象。还可以配合Math.max、Math.min寻找数组极值const max Math.max(...[3, 7, 1, 9]); // 9这也是把数组值添加到另一个数组最直接的方式。ES5 时代要合并数组得用concat或者push.apply现在一个展开运算符清爽地在字面量层面就完成了。3. 对象高级方法四个维度全面解锁对象操作在 ES6 里也有不少新工具。我把它们分成四类遍历收集、复制合并、键值对互转、快捷取值。3.1 静态方法三件套keys、values、entriesObject.keys(obj)返回对象自身可枚举属性名的数组。Object.values(obj)返回属性值的数组。Object.entries(obj)返回键值对数组每个元素是[key, value]形式。有了entries对象和数组之间的转换变得非常自然。比如我要把对象里的每一项做统一处理const scores { 语文: 90, 数学: 85, 英语: 92 }; const entries Object.entries(scores); // [[语文, 90], [数学, 85], [英语, 92]] const withLevel entries.map(([subject, score]) ({ subject, score, level: score 90 ? 优秀 : 良好 }));注意map回调的参数这里用了解构赋值([subject, score])直接从每个键值对数组里取出两个值这个写法非常常见。这三个方法返回的都是数组所以它们能和前面所有的数组方法无缝衔接。这是 ES6 设计上很聪明的一点对象不好直接遍历那就先转成数组再用数组方法去花样处理。一个细节Object.keys和for...in的区别在于Object.keys只返回对象自身的可枚举属性不会包含原型链上的可枚举属性。用for...in还需要加hasOwnProperty判断相比之下Object.keys更省心也更安全。强烈建议在遍历对象属性的场景优先用Object.keys或Object.entries。3.2 对象合并与拷贝Object.assign 与扩展运算符的对决Object.assign(target, ...sources)把多个源对象的可枚举属性复制到目标对象返回目标对象。它修改并返回第一个参数const target { a: 1 }; const result Object.assign(target, { b: 2 }, { c: 3 }); // result 和 target 指向同一个对象{ a: 1, b: 2, c: 3 }一个常见的坑是Object.assign的第一个参数如果传{}会得到一个全新对象不影响原对象如果直接传原对象原对象会被修改。所以复制对象的推荐姿势是const copy Object.assign({}, original);ES6 里更简洁的复制方式是对象扩展运算符const copy { ...original };两者在浅拷贝的语义上等价但扩展运算符的写法更直观还能在展开的同时覆盖部分属性const user { name: 张三, age: 30 }; const updated { ...user, age: 31, vip: true }; // { name: 张三, age: 31, vip: true }这是 React/Vue 里不可变数据更新的核心写法几乎每个组件里都会出现。要提醒的是Object.assign和对象扩展运算符都是浅拷贝。只有一层属性时没问题嵌套对象的修改会同时影响原对象。深拷贝是另一个话题需要structuredClone或者序列化方案这里不展开。3.3 Object.fromEntries从数组回到对象Object.fromEntries是Object.entries的逆操作接收键值对数组返回对象。它最大的价值是让对象 → 数组处理 → 变回对象成为一个完整闭环。比如我想把对象里的所有属性值都过滤掉无效项const data { name: 张三, age: 30, email: , phone: undefined }; const cleaned Object.fromEntries( Object.entries(data).filter(([, value]) value ! value ! undefined) ); // { name: 张三, age: 30 }另一个高频场景是从Map转对象const map new Map([[a, 1], [b, 2]]); const obj Object.fromEntries(map); // { a: 1, b: 2 }这个 API 在日常业务里没有entries那么普及但在处理 URL 查询参数、表单配置这类键值对集合时很实用。3.4 解构赋值从对象和数组中优雅取值解构赋值的核心价值是让取数据这件事变得极其直观。对象解构const user { name: 张三, age: 30, address: { city: 北京 } }; const { name, age } user; // name 张三, age 30数组解构const [first, second] [10, 20, 30]; // first 10, second 20解构的常见高级用法包括给新变量重命名const { name: userName } user; // userName 张三设置默认值const { email 无邮箱 } user; // email 无邮箱嵌套解构const { address: { city } } user; // city 北京解构还有两个特别适合实际场景的地方。一个是交换变量let a 1, b 2; [a, b] [b, a];另一个是函数参数的配置对象模式function createUser({ name, age 18, address {} }) { // ... }这样调用时只需传一个对象而且有没有传某个字段都有兜底不用在函数体里做大量判空。3.5 可选链与空值合并告别冗长的判空这是 ES2020 加入的特性对日常开发的影响非常大。可选链?.允许你在访问深层属性时如果中间某一层是null或undefined表达式直接返回undefined而不是抛错。以前要拿一个可能不存在的嵌套值得这样写const street user user.address user.address.street;可选链一出来变成const street user?.address?.street;它还支持可选函数调用const result config?.getData?.();以及可选的索引访问const firstItem arr?.[0];空值合并运算符??和它经常搭伴出现。??只在左边是null或undefined时才返回右边的默认值和||的区别是||在左边是0、空字符串、false时也会取默认值而??不会。举个例子const align config.align ?? left; // config.align 为 0 时结果还是 0 // config.align 为 undefined 时结果才是 left用||的话config.align为0时会错误地变成left。所以当默认值语义是空才替换时优先用??。它俩还有一个规矩是不能混用在一个表达式里比如a?.b ?? c没问题但a ?? b || c会直接语法报错必须加括号。这一对搭配起来处理接口数据、配置项这类可能缺字段的情况写起来极度舒适同时少了很多防御性的if判断。4. 综合实战高频场景的优雅解法方法学了一堆最终要落到解决实际问题上。下面这几个场景是搜索关键词里高频出现的也是日常开发里反复用到的我把它们归到一起讲。4.1 数组去重从暴力解法到一行代码最简单的去重用Set加扩展运算符const arr [1, 2, 2, 3, 3, 4]; const unique [...new Set(arr)]; // [1, 2, 3, 4]背后的原理是Set天然不允许重复值数组转成Set再转回来就完成了去重。这个方法适用于基本类型数组效率也高。如果数组元素是对象Set的去重是引用比较两个内容相同但引用不同的对象会被当作不同项。这时候要根据某个字段去重见下一节。4.2 对象数组去重按字段去重的标准方案最常见的是按id去重。思路是维护一个已见过的id集合用filter筛选出首次出现的记录const items [ { id: 1, name: A }, { id: 2, name: B }, { id: 1, name: A-dup } ]; const seen new Set(); const uniqueById items.filter(item { if (seen.has(item.id)) return false; seen.add(item.id); return true; });也可以写成更函数式的写法用reduce累加去重后的数组const uniqueById items.reduce((acc, item) { if (!acc.some(existing existing.id item.id)) { acc.push(item); } return acc; }, []);两种方案里Set配合filter的性能更好some在数组里嵌套查找是 O(n²)而且逻辑更清晰。推荐优先用Set方案。4.3 提取对象数组的一部分字段这也是提取数组对象一部分这个热搜词的典型诉求。比如后端返回了一堆字段前端只想要id和title可以用map配合解构const list [ { id: 1, title: 文章1, content: 很长的正文..., author: 张三 }, { id: 2, title: 文章2, content: 很长的正文..., author: 李四 } ]; const summaries list.map(({ id, title }) ({ id, title })); // [{ id: 1, title: 文章1 }, { id: 2, title: 文章2 }]这里在map回调的参数上直接解构只取需要的字段返回时再组装新对象。如果还想排除某些字段而不是挑选字段可以用这个技巧const { password, ...safeUser } user;把敏感字段单独解构出来剩余字段放进新的safeUser对象在返回用户信息给前端时非常常用。4.4 两个数组取交集与差集js 字符串数组取交集这类需求用filterincludes解决最直观const arr1 [a, b, c]; const arr2 [b, c, d]; const intersection arr1.filter(item arr2.includes(item)); // [b, c]差集在 arr1 但不在 arr2 中const difference arr1.filter(item !arr2.includes(item)); // [a]对称差集两个数组的独有元素合并const symmetricDiff [ ...arr1.filter(item !arr2.includes(item)), ...arr2.filter(item !arr1.includes(item)) ];如果数组很大includes的 O(n²) 复杂度会变慢可以用Set做查重优化const set2 new Set(arr2); const intersection arr1.filter(item set2.has(item));这个优化思路在业务代码里不值得过早做但数据量上了万级以后效果明显。4.5 判断对象是否为空判断对象为空也是高频问题。ES5 时代得先for...in遍历看看有没有属性。现在用Object.keys就能快速判断const isEmpty obj Object.keys(obj).length 0;注意这里有个语义细节Object.keys只统计可枚举的自有属性。如果对象里有不可枚举的属性或者只有原型链上的属性length也会是 0。在绝大多数业务场景下这符合预期但如果你要严谨地这个对象是不是完全没有任何可检测的键可以再配合Object.getOwnPropertyNamesconst hasAnyKeys obj Reflect.ownKeys(obj).length 0;实际开发中还有一种空值判断就是把null、undefined、空对象、空数组、空字符串统一处理。可以包裹成一个工具函数内部根据类型分支判断。这是很多项目里utils文件夹都会有的基建代码。5. 常见问题与排查心得下面这些问题很多是从真实项目里翻车翻出来的也有不少是社区里反复讨论的重点。我整理成了一份踩坑速查希望能帮你少走弯路。5.1 为什么 forEach 里不能用 break 和 return 跳出循环forEach的回调函数里的return只是结束当前这一次回调不会中断整个循环相当于for循环里的continue。而break在forEach里干脆就是语法错误因为forEach本身是一个函数调用不是循环语法结构。如果需要提前退出在 ES6 时代的正确选择是some或every它们在满足条件时会自动停住const found arr.some(item { if (item.id targetId) { // 找到目标这里返回 truesome 就会停止遍历 return true; } return false; });或者干脆用for...of它是 ES6 新增的遍历语法支持break、continue、return。如果你明确知道遍历中需要中断用for...of比用数组方法更合适。5.2 vue watch 数组的第一项为什么新值和旧值一样这个现象背后不是 Vue 的问题而是 JavaScript 引用类型赋值的天然行为。watch监听一个数组时newValue和oldValue往往指向同一个数组引用数组是对象传递的是引用。当你对数组做原地修改比如push、通过索引赋值新旧值指向同一个引用自然看起来内容一样。要真正能区分新旧值你需要让 Vue 监听的是新数组也就是用不可变更新的方式去替换数组而不是原地修改// 错误示范原地修改 list.push(newItem); // 正确示范产生新数组watch 才能捕获变化 list [...list, newItem];这不是数组方法本身的问题而是引用类型 原地修改这个概念层面的事。理解了这一点很多类似为什么我改了对象界面上没反应的问题都能迎刃而解。5.3 回调函数中 this 指向丢失map、filter这些方法除了接收回调函数还可以接收第二个参数回调中的this指向。如果你在回调里用到this记得传这个参数否则在严格模式下this是undefined非严格模式下是window。const multiplier { factor: 2 }; const result [1, 2, 3].map(function (n) { return n * this.factor; }, multiplier); // [2, 4, 6]不过更推荐的做法是用箭头函数箭头函数没有自己的this会继承外层作用域的this大多数场景下更符合直觉。两种方式各有优势箭头函数适用于想直接用外部 context第二参数适用于同一个回调函数被多个 context 复用。5.4 空数组和 undefined 的防御边界map、filter遇到空数组会直接返回空数组不会报错这一点很安全。但如果你从接口拿到的数据不是数组、而是undefined直接调用.map就会炸const list response.data?.list; // 如果 list 是 undefined list.map(item item); // TypeError: Cannot read properties of undefined标准的防御姿势是加个默认值const list response.data?.list ?? [];这也印证了前面讲可选链和空值合并的价值。其实写代码时养成的习惯应该是所有来自外部输入的数据在调用数组方法前先确认它是数组。某些数据源返回null某些返回对象统一处理成数组再往下走能避免大量运行时错误。5.5 链式调用的性能与可读性平衡一次完整的数据处理经常是filter→map→reduce串起来。每个方法都会遍历一次数组这个多次遍历在数据量不大的情况下可以忽略但如果数组有几十万个元素链式调用会有可感知的性能开销。要优化的话可以把多个操作合并进一次reduce或者用for循环暴力解决。对于普通业务我建议先写出可读性最好的版本等性能问题真实出现、用性能工具定位到瓶颈了再考虑合并。千万不能一开始就为了性能写出难维护的代码。另外还有一个细节链式调用里注意每个方法返回的数据结构。map返回数组filter返回数组但Object.entries返回的数组和Object.fromEntries期望的输入格式要匹配如果用reduce把对象数组归并成了对象那后面的方法就不能直接调了。每一步都留意返回值这是链式调用不翻车的基本素养。5.6 关于 javascript:void(0) 和运行时错误的一点排查经验很多入门者会在页面链接、或是调试控制台里看到javascript:void(0)它本身的意思差不多是执行一段 JavaScript 表达式但不产生返回值用来避免页面跳转。在老式页面里hrefjavascript:void(0)是用来做点击但跳转的占位写法。这在现代前端里已经不推荐用了尽量用button配合事件处理替代。还有类似 A JavaScript error occurred in the main process 这类报错常见于 Electron 应用环境或者浏览器控制台里语法错误导致的脚本中断。排查思路通常是打开开发者工具控制台先看报错堆栈第一行定位到第几行第几列再看是变量未定义还是调用不存在的方法。多数运行时错误最终都能归结到某个变量不是预期类型——比如以为它是数组实际是undefined用我们上面说的判空和默认值就能规避相当大一部分。6. 一点实操建议文章写到最后分享几个我自己用了很多年 ES6 之后沉淀下来的习惯。第一遇到数组和对象的处理需求先停下来想想有没有原生方法能直接干这事而不是立刻写for循环。这个习惯一旦养成你会发现代码里的临时变量、分支判断都少了很多。我自己刚开始的时候总是忍不住回到for循环的舒适区后来强制自己一个项目里所有列表处理都用map/filter/reduce写完回头看代码确实更干净。第二代码是写给人看的顺便让机器执行。所以可读性永远排在写出更短的代码前面。像reduce这种灵活度高的方法如果逻辑复杂注释写清楚这一步在干什么或者干脆把累积逻辑抽成小函数比硬写出炫技的一行代码要好得多。第三把 ES6 方法当作工具箱而不是考点。平时刷题或看源码时看到新写法不要只收藏直接在真实项目里用起来。只有真正踩过坑、修过 bug你对这些方法的记忆才会牢固。这篇文章从数组到对象从方法原理到综合场景把 ES6 核心语法里最实用的一部分过了一遍。如果你能把map、filter、reduce、解构赋值、可选链、Object.entries这六样东西真正用熟日常开发中 80% 的数据处理场景你都能写得又短又清晰。剩余的 20%翻翻文档查查示例基本也难不住你。