js代码02
题目
好的,我们继续。
在上一个练习中,我们已经不自觉地接触到了接下来要学习的核心概念:我们将一个函数(箭头函数)作为参数传递给了另一个函数(.filter
或 .map
)。这种能力,即把函数当成普通变量一样传来传去,是 JavaScript 强大功能的基础。
练习 02: 函数的力量 - 高阶函数与闭包
这个练习将带你领略 JavaScript 中最深刻、最强大的特性之一。我们将创建一个“函数工厂”,也就是一个能制造其他函数的函数。
🎯 学习目标:
- 理解“函数是一等公民 (Functions are First-Class Citizens)”的含义。
- 学会创建并使用“高阶函数 (Higher-Order Function)”。
- 理解并利用“闭包 (Closure)”的强大特性来记忆状态。
背景知识:
- 函数是一等公民: 在 JavaScript 中,函数与其他数据类型(如数字、字符串)地位平等。你可以把它存入变量、作为参数传递给另一个函数,甚至可以作为另一个函数的返回值。
- 高阶函数 (Higher-Order Function, HOF): 一个函数如果满足以下至少一个条件,它就是高阶函数:
- 接受一个或多个函数作为参数(例如我们用过的
.map
和.filter
)。 - 返回另一个函数。(这是我们本次练习的重点)
- 接受一个或多个函数作为参数(例如我们用过的
- 闭包 (Closure): 这是个“魔法”。当一个函数(内部函数)在另一个函数(外部函数)内部被定义时,它就创建了一个闭包。这个闭包使得内部函数有权访问外部函数作用域中的变量,即使在外部函数已经执行完毕之后。换句话-说,内部函数“记住”了它被创建时的环境。
🛠️ 任务:
我们要创建一个名为 createLogger
的函数工厂。这个工厂不直接打印日志,而是根据你提供的“前缀”,生产出专门的日志记录函数。
- 创建一个名为
createLogger
的函数,它接受一个字符串参数prefix
。 createLogger
函数必须返回一个新的函数。- 这个被返回的新函数(我们称之为“日志记录器”)接受一个字符串参数
message
。 - 当调用“日志记录器”时,它应该在控制台打印一条格式化的日志,格式为:
[前缀] [时间戳] 消息内容
。
📋 初始代码:
在你的 js-learning
文件夹中,创建一个新文件 02-higher-order-functions.js
,并将以下代码复制进去。你的任务是完成 createLogger
函数的内部实现。
// --- 在这里编写你的代码 ---/*** 创建一个日志记录器函数。* @param {string} prefix - 每条日志消息的前缀。* @returns {function(string): void} - 一个新的函数,该函数接受一个消息字符串并将其记录到控制台。*/
function createLogger(prefix) {// 你的实现需要写在这里。// 关键:你需要从这个函数返回另一个函数。
}// --- 代码结束 ---// --- 使用你创建的函数工厂 ---
// 1. 我们使用 createLogger 工厂创建了三个不同的“日志记录器”实例。
const infoLogger = createLogger("INFO");
const warningLogger = createLogger("WARNING");
const errorLogger = createLogger("ERROR");// 2. 调用这些专门的日志记录器。
infoLogger("Application has started successfully.");
warningLogger("The database connection is getting slow.");
errorLogger("Failed to fetch user data!");// 3. 验证闭包:即使 createLogger 函数已经执行完毕,
// infoLogger 仍然“记住”了它的前缀是 "INFO"。
infoLogger("This is another informational message.");
💡 要求与提示:
createLogger
的核心任务就是return
一个函数。- 被返回的那个内部函数需要同时访问两个变量:一个是外部传入的
prefix
,另一个是自己接收的message
。 - 要获取当前时间的 ISO 格式字符串,你可以使用
new Date().toISOString()
。
✅ 预期输出:
你运行代码后,控制台应该打印出类似下面的内容(时间戳会根据你实际运行的时间而变化):
[INFO] [2025-06-30T14:11:03.456Z] Application has started successfully.
[WARNING] [2025-06-30T14:11:03.457Z] The database connection is getting slow.
[ERROR] [2025-06-30T14:11:03.457Z] Failed to fetch user data!
[INFO] [2025-06-30T14:11:03.458Z] This is another informational message.
这个练习触及了 JavaScript 最强大、最独特的概念之一。慢慢来,仔细思考函数和变量作用域之间的关系。完成后我们来深入探讨!
答案
当然可以!这个概念确实是 JavaScript 中的一个难点,初次接触时感到困惑非常正常。这正是通过看答案和解析来学习的最佳时机。
下面是完整的实现代码。
参考实现 (02-higher-order-functions.js
)
/*** 创建一个日志记录器函数。* @param {string} prefix - 每条日志消息的前缀。* @returns {function(string): void} - 一个新的函数,该函数接受一个消息字符串并将其记录到控制台。*/
function createLogger(prefix) {// 这个外部函数的作用域,包含一个我们传入的变量: `prefix`// 关键一步:我们从这个函数返回了“另一个函数”。// 这里我们使用箭头函数语法来定义这个返回的函数。return (message) => {// 闭包的“魔法”在这里发生!// 这个内部函数可以访问它自己的参数 (message)// 同时,它也能访问它“出生环境”中的变量 (prefix)const timestamp = new Date().toISOString();// 它将两者结合起来,打印出完整的日志console.log(`[${prefix}] [${timestamp}] ${message}`);};
}// --- 使用你创建的函数工厂 ---
const infoLogger = createLogger("INFO");
const warningLogger = createLogger("WARNING");
const errorLogger = createLogger("ERROR");infoLogger("Application has started successfully.");
warningLogger("The database connection is getting slow.");
errorLogger("Failed to fetch user data!");infoLogger("This is another informational message.");
深入解析:闭包 (Closure) 如何工作
让我们用一个比喻来彻底理解这段代码。
你可以把 createLogger
函数想象成一个拥有特定模具的“函数制造工厂”。
第一步:下订单
当我们执行 const infoLogger = createLogger("INFO");
这行代码时:
- 我们调用了
createLogger
工厂,并给了它一个订单,说:“我想要一个印有INFO
字样的模具”。 - 工厂函数
createLogger
接收到"INFO"
,将其存放在名为prefix
的变量里。 - 工厂的核心工作是制造并返回一个新的函数,也就是我们代码里的
(message) => { ... }
。 - 在制造这个新函数的时候,闭包就产生了。这个新函数被赋予了一个神奇的“记忆背包”。它把自己需要用到的、来自工厂环境的“模具”——也就是
prefix
变量(其值为"INFO"
)——放进了这个背包里。 - 工厂
createLogger
的工作完成了,它把这个刚刚制造出来的、背着“INFO”背包的新函数作为返回值,交给了我们。 - 我们用
const infoLogger
这个变量名签收了这个新函数。
现在,infoLogger
变量指向的已经不是工厂了,而是那个专门负责打印 INFO 日志的、有记忆的函数工人。
第二步:使用工人
当我们执行 infoLogger("Application has started successfully.");
时:
- 我们调用了
infoLogger
这个函数工人。 - 我们给了它新的任务信息,也就是
message
参数:“Application has started successfully.”。 - 函数工人开始工作。它拿到了新的
message
,然后打开它自己的“记忆背包”,找到了当初工厂给它的模具prefix
(值为"INFO"
)。 - 它把所有信息(
prefix
、当前时间、message
)组装起来,通过console.log
打印出来。
warningLogger
和 errorLogger
的创建过程完全一样,只是它们各自的“记忆背包”里分别装着 "WARNING"
和 "ERROR"
的模具。它们是三个独立的、拥有不同记忆的函数工人。
回顾我们的学习目标
- 高阶函数:
createLogger
是一个高阶函数,因为它返回了另一个函数。 - 函数是一等公民: 我们将返回的函数赋值给了
infoLogger
、warningLogger
等变量,证明了函数可以像普通值一样被存储。 - 闭包:
infoLogger
函数记住了它被创建时prefix
的值是"INFO"
,即使在createLogger
函数早已执行完毕后,这个记忆依然存在。这就是闭包的力量。
你现在已经掌握了 JavaScript 一个非常强大的模式:利用闭包来创建可配置的、可重用的函数,这在各种框架和库中都随处可见。
花点时间消化一下,可以尝试修改代码,比如让 createLogger
再多接收一个参数来控制时间戳的格式等等。
准备好后,我们就可以进入下一个练习,探索另一个核心概念:异步编程。