题目
好的,完全理解。既然您有编程基础,那我们跳过最基础的语法,直接进入更能体现 JavaScript 特色和现代开发实践的练习。
我们将专注于 JavaScript 的核心优势,例如函数式编程、异步处理、以及 ES6+ 的现代语法。这些练习会更贴近真实项目中的场景。
新的学习路线图 (加速版)
- 阶段一:函数式与数据处理
练习 01
: 数据转换 - 掌握强大的数组方法 (map
,filter
,reduce
)练习 02
: 函数作为一等公民 - 高阶函数与闭包
- 阶段二:异步编程
练习 03
: 回调地狱与 Promise - 理解异步流程的演进练习 04
: 现代异步方案async/await
- 以同步的方式编写异步代码
- 阶段三:ES6+ 核心特性
练习 05
: 解构赋值与展开/剩余语法 - 更优雅地处理数据练习 06
: 模块化 -import
/export
- 阶段四:面向对象与原型
练习 07
:this
的指向 - 理解 JS 中最微妙的概念之一练习 08
: 类 (Class) 与原型链 (Prototype)
我们立刻开始第一个有挑战性的练习。
练习 01: 数据转换 - 掌握强大的数组方法
在任何编程语言中,处理数据集合都是一项核心技能。在 JavaScript 中,我们极少使用传统的 for
循环来做这件事,而是倾向于使用更具声明性、可读性更强的数组方法。
🎯 学习目标:
- 熟练使用
filter
方法根据条件筛选数据。 - 熟练使用
map
方法将数据转换成新的结构。 - 理解并实践方法链 (Method Chaining)。
- 使用箭头函数 (
=>
) 简化代码。 - (进阶) 了解并使用对象解构。
背景:
假设你正在开发一个用户管理系统,你需要从一个完整的用户列表中,提取出符合特定条件的活跃用户,并且只返回他们的部分信息。
🛠️ 任务:
你有一个包含多个用户对象的数组 users
。请完成以下任务:
- 筛选出所有
isActive
为true
的用户。 - 在活跃用户中,进一步筛选出年龄
age
大于30
岁的用户。 - 创建一个新的数组,该数组中的每个对象只包含用户的
name
和email
信息。 - 将最终结果打印到控制台。
📋 初始代码:
在你的 js-learning
文件夹中,创建一个新文件 01-array-methods.js
,并将以下代码复制进去。
const users = [
{ id: 1, name: 'Alice', age: 28, isActive: true, email: 'alice@example.com' },
{ id: 2, name: 'Bob', age: 35, isActive: false, email: 'bob@example.com' },
{ id: 3, name: 'Charlie', age: 42, isActive: true, email: 'charlie@example.com' },
{ id: 4, name: 'Diana', age: 25, isActive: true, email: 'diana@example.com' },
{ id: 5, name: 'Ethan', age: 38, isActive: true, email: 'ethan@example.com' },
{ id: 6, name: 'Fiona', age: 31, isActive: false, email: 'fiona@example.com' }
];
// --- 在这里编写你的代码 ---
// 假设你的结果存储在 a_ctiveAndSeniorUsersInfo 变量中
const activeAndSeniorUsersInfo = "请在这里实现你的逻辑";
// --- 代码结束 ---
console.log(activeAndSeniorUsersInfo);
💡 要求与提示:
- 不要使用 传统的
for
或while
循环。 - 必须使用
.filter()
和.map()
方法来完成。 - 强烈建议 将多个
.filter()
和.map()
调用链接在一起(方法链),例如users.filter(...).map(...)
。 - 进阶挑战:在
map
的回调函数中,尝试使用对象解构来直接获取name
和email
属性。
✅ 预期输出:
你的代码最终应该在控制台打印出以下结果:
[
{ name: 'Charlie', email: 'charlie@example.com' },
{ name: 'Ethan', email: 'ethan@example.com' }
]
这个练习直接反映了现代 JavaScript 开发中每日都在进行的数据处理工作。完成之后,我们可以一起探讨你的解决方案,或者直接看一种参考实现,并讨论其中的技巧。
准备好了就开始吧!
答案
好的,我们来解决这个练习。
这个任务的核心是构建一个数据处理的“流水线”:原始数据进来,经过一步步的筛选和转换,最终得到我们想要的结果。
这是解决该问题的一种优雅且符合现代 JavaScript 风格的实现:
参考实现 (01-array-methods.js
)
const users = [
{ id: 1, name: 'Alice', age: 28, isActive: true, email: 'alice@example.com' },
{ id: 2, name: 'Bob', age: 35, isActive: false, email: 'bob@example.com' },
{ id: 3, name: 'Charlie', age: 42, isActive: true, email: 'charlie@example.com' },
{ id: 4, name: 'Diana', age: 25, isActive: true, email: 'diana@example.com' },
{ id: 5, name: 'Ethan', age: 38, isActive: true, email: 'ethan@example.com' },
{ id: 6, name: 'Fiona', age: 31, isActive: false, email: 'fiona@example.com' }
];
// --- 在这里编写你的代码 ---
const activeAndSeniorUsersInfo = users
.filter(user => user.isActive && user.age > 30)
.map(({ name, email }) => ({ name, email }));
// --- 代码结束 ---
console.log(activeAndSeniorUsersInfo);
代码解析
让我们把这行核心代码拆解开来,看看每一步都发生了什么:
users.filter(...).map(...);
1. .filter(user => user.isActive && user.age > 30)
filter
方法: 它会遍历users
数组中的每一个元素(我们称之为user
)。- 箭头函数
user => ...
: 这是传递给filter
的“测试函数”。数组中的每个user
对象都会经过这个测试。 - 测试条件
user.isActive && user.age > 30
: 只有当一个用户的isActive
属性为true
并且 (&&
)age
属性大于30
时,这个测试函数才会返回true
。 - 结果:
filter
方法会返回一个新的数组,其中只包含通过了测试的用户对象。在我们的例子中,这一步执行完后,会得到一个包含 Charlie 和 Ethan 完整对象的临时数组:[ { id: 3, name: 'Charlie', age: 42, isActive: true, email: 'charlie@example.com' }, { id: 5, name: 'Ethan', age: 38, isActive: true, email: 'ethan@example.com' } ]
2. .map(({ name, email }) => ({ name, email }))
- 方法链 (Method Chaining):
.filter()
的返回值是一个数组,所以我们可以立刻在其结果上调用另一个数组方法,这就是方法链。现在,.map()
方法开始处理上一步筛选出来的那个只包含 Charlie 和 Ethan 的数组。 map
方法: 它同样会遍历数组中的每一个元素,但它的工作不是测试,而是“转换”。它会根据你提供的函数,将每个元素映射(转换)成一个新的形式。- 箭头函数
({ name, email }) => ...
: 这是传递给map
的“转换函数”。- 参数解构
({ name, email })
: 这是 ES6 的一个非常强大的特性。我们没有接收整个user
对象 (user => ...
),而是直接在参数位置上用{}
把我们需要的属性(name
和email
)“解压”出来。这让代码更简洁,意图更明确。 - 返回值
({ name, email })
: 我们的目标是返回一个新对象。{ name, email }
是{ name: name, email: email }
的简写。当对象的键和值的变量名相同时,可以这样简写。- 最重要的细节: 返回一个对象字面量时,必须用括号
()
包裹起来,即() => ({...})
。否则,JavaScript 引擎会把{}
误认为是一个函数体(代码块)而不是一个要返回的对象,从而导致语法错误。
- 参数解构
- 结果:
map
方法最终返回一个新的数组,其中包含了所有转换后的新对象,也就是我们最终期望的结果。
为什么这种方式更好?
与使用传统 for
循环相比,这种函数式、链式调用的方法有几个显著优势:
- 可读性 (Readability): 代码读起来就像一个句子:“对用户进行筛选,条件是活跃且年龄大于30,然后将结果映射成只包含姓名和邮箱的新对象。” 它描述的是做什么 (What),而不是怎么做 (How)。
- 简洁性 (Conciseness): 无需创建临时数组(如
let result = []
),也无需手动push
元素,代码量大大减少。 - 不可变性 (Immutability):
.filter()
和.map()
都不会修改原始的users
数组,而是返回新的数组。这是一个非常重要的特性,它能有效避免在复杂应用中意外修改数据源而导致的各种难以追踪的 bug。
你已经完成了一个非常实用且能体现 JavaScript 现代编程思想的练习。函数(比如我们传递给 filter
和 map
的箭头函数)在 JavaScript 中可以像变量一样被传来传去,这被称为“函数是一等公民”。
准备好后,我们就可以进入下一个练习:练习 02: 函数作为一等公民 - 高阶函数与闭包
,去深入探索这个强大的概念。