B 币安 · 全球最大的加密货币交易所币安 注册 → AD OKX OKX · 全球领先的加密货币交易所OKX 注册 → AD
💻 编程基础 · 第5讲 / 10

函数 — 拆成一个个有名字的小机器

函数是接收输入、处理后把结果交回来的一段有名字的代码。结果要用 `return` 返回,而不是用 `console.log` 打印,这样其他代码才能接着使用,测试起来也更容易。

⏱ 约22分钟 ✍️ 练习题 5 道 🧪 代码练习 4 题 更新 2026-10-09
🎯 本讲学习目标
  • 能编写带参数和返回值的函数并调用它
  • 能说明 return 和 console.log 的区别,并按情况使用
  • 能用箭头函数写短小的函数,并避开使用花括号时的 return 陷阱
  • 能把长计算拆成小的纯函数,并用输入和期望值来检验

1.什么是函数——接收输入、交回结果的机器

第1讲我们把程序看作输入 → 处理 → 输出。函数就是把这个结构切出一小块并起个名字。想想自动售货机就容易明白:投进钱、按下按钮(输入),不必知道里面发生了什么,饮料(结果)就出来了。做好一台,就能一次又一次地使用同一台售货机。

没有函数的话,每次要用同一个计算都得复制代码。一旦计算规则变了,就要找出所有复制过的地方逐一修改,漏掉一处,结果就会彼此不一致。做成函数后,规则只存在于一个地方,修改时也只改那里。而且像 vatOf(price) 这样,光看名字就知道它做什么,代码读起来就像句子。

创建函数叫“定义”,使用函数叫“调用”。只定义的话什么也不会发生,要在名字后面加上括号,像 vatOf(10000) 这样调用,函数体才会执行。注意,不加括号只写 vatOf,只是指向函数本身,并不会执行。

定义一次,调用多次——虚构的 10% 增值税计算
function vatOf(price) {
  return price * 0.1;
}
console.log(vatOf(10000));
console.log(vatOf(25000));
输出
1000
2500

2.参数与返回值

函数名后面括号里的 price 叫作参数,是函数内部使用的“空格的名字”。调用时放进去的实际值 10000 叫作实参。调用时,实参装进参数里,然后执行函数体。参数有多个时用逗号隔开,实参按书写顺序依次放入。顺序放反了不会报错,却会得出离谱的结果,所以顺序很重要。

return 把结果交回调用的地方,并在那一刻结束函数。return 下面的行不会执行。没有 return,或者只写不带值的 return;,函数就返回 undefined。实参放少了,缺的参数也会是 undefined,用在计算里就会出现第2讲见过的 NaN。JavaScript 在这些情况下也不会报错,所以要自己检查。

也可以预先定好没有实参时使用的值。在参数后面加上 = 值,就成了默认值。下面的代码里不传 rate 时就用 0.1。默认值适合“通常就是这个值”的设置,但给必须传入的值加默认值,万一漏传也察觉不到,所以要分情况使用。

缺了实参就是 undefined → NaN,有默认值就用默认值
function rectArea(w, h) {
  return w * h;
}
console.log(rectArea(3, 4));
console.log(rectArea(3));

function tax(price, rate = 0.1) {
  return price * rate;
}
console.log(tax(20000));
console.log(tax(20000, 0.05));
输出
12
NaN
2000
1000

3.`return` 与 `console.log`——最常见的混淆

刚学函数时最容易混淆的,是“返回”和“打印到屏幕”的区别。两者看起来都能看到结果,做的事却完全不同。console.log 是把值写到屏幕上给人看,程序无法接着使用那个值。return 是把值交给调用它的代码,接收方可以把它存进变量或放进别的计算。

下面代码里的 showDouble 只打印结果而不返回。所以把 showDouble(4) 存进变量时,屏幕上会打印 8,但变量里装的是 undefined,再加 1 就变成 NaN。可以把它想成:按计算器让答案显示在屏幕上,和把这个答案交给下一步计算,是两回事。

本页练习的评分也是同样的原理。评分器调用函数,把“返回的值”和期望值比较。如果在函数里只用 console.log 打印正确答案,屏幕上即使显示了正确的数字,评分也会失败。原则很简单:负责计算的函数用 return 返回,打印到屏幕的事放在调用函数的外面做。

只打印的函数与返回值的函数
function showDouble(x) {
  console.log(x * 2);
}
function double(x) {
  return x * 2;
}
const a = showDouble(4);
console.log('a 的值', a, a + 1);
const b = double(4);
console.log('b 的值', b, b + 1);
输出
8
a 的值 undefined NaN
b 的值 8 9
`return` 与 `console.log` 对比
`return``console.log`
交给谁调用函数的代码看屏幕的人
值能否接着用能(用于变量、计算)不能
函数会结束吗在那一刻结束继续执行下一行
练习评分按返回的值评分不用于评分
主要用在哪里负责计算的函数里检查、调试、最终输出

4.箭头函数——另一种简短写法

函数也可以写成 const 名字 = (参数) => 表达式 的样子,这叫箭头函数。箭头右边只有一个表达式时,这个表达式的值会自动返回,所以不写 return。在把函数传给另一个函数时用得特别多,比如第6讲要学的 map、filter,在 AI 写的代码里也经常见到。

有一个陷阱要注意。箭头右边一旦用了花括号,就会被当作普通函数体,必须自己写 return。下面的 bad 在花括号里只写了表达式、漏了 return,结果返回 undefined。它不会报错,所以不打印结果的话很容易蒙混过去。记住:短的不加花括号,多行的花括号和 return 一起写。

用 function 写的函数和箭头函数,在本课程涉及的范围内做的事情一样。细节上有些不同(例如处理 this 的方式),但入门阶段不必了解。没有哪一种更“好”,跟着合作的人或项目所用的写法走就行。

用了花括号就必须自己写 return
const add = (a, b) => a + b;
const square = (x) => x * x;
const bad = (x) => { x * x; };
const good = (x) => {
  const y = x * x;
  return y;
};
console.log(add(2, 3), square(4));
console.log(bad(4), good(4));
输出
5 16
undefined 16

5.函数里的变量只在函数里——作用域

在函数里用 let 或 const 创建的变量,只在这个函数里可见。这叫作用域。函数结束后,里面的变量也随之消失,所以在外面使用这个名字会出现 ReferenceError。错误信息 is not defined 的意思是“在这里看不到这个名字”。信息的具体措辞在不同浏览器中可能略有差异。

这条规则一开始看起来不方便,但正因为有它,函数之间才不会互相干扰。好几个函数各自在内部使用 total 这个名字也不会混在一起。如果想在外面使用函数里算出的值,不要偷偷修改外面的变量,而要用 return 返回。在循环和条件语句的花括号里创建的 let、const 也一样,只在那对花括号里可见(这就是第4讲要把累加变量放在循环上方的原因)。

在函数外面看不到里面的变量
function makeTotal() {
  const inside = 100;
  return inside + 1;
}
console.log(makeTotal());
console.log(inside);
输出
101
ReferenceError: inside is not defined

6.拆成小函数

把很长的计算写成一大块,很难找到哪里出了错。如果拆成多个“只做一件事”的小函数再组装起来,每个零件都能单独检验,名字本身也起到说明的作用。函数名用动词表明它做的事,如 getTotal、isAdult;返回真假的函数习惯以 is、has 开头。

找拆分点的问题是:“这一部分能用一句话说明吗?”像“把商品价格全部加起来”“根据合计决定运费”这样能成为一句话的,就是一个函数。说明里出现好几个“然后”,就说明还能再拆。不过,把一行的计算拆得过细反而难读,所以要按值得起名字的意义单位来拆。

例题虚构的网店规则:把商品价格全部加起来,合计达到 50,000韩元及以上运费为 0韩元,否则加 3,000韩元。这个计算该怎样拆成函数?
function subtotal(prices) {
  let sum = 0;
  for (const p of prices) {
    sum += p;
  }
  return sum;
}
function shippingFee(sum) {
  return sum >= 50000 ? 0 : 3000;
}
function orderTotal(prices) {
  const sum = subtotal(prices);
  return sum + shippingFee(sum);
}
console.log(orderTotal([20000, 10000]));
console.log(orderTotal([30000, 20000]));
输出
33000
50000
  1. 第1步 拆成句子:“把价格全部加起来” / “按合计决定运费” / “把两者相加得出最终金额”。
  2. 第2步 零件函数:subtotal(prices) 是第4讲的累加模式,shippingFee(sum) 是第3讲的条件语句。
  3. 第3步 组装:orderTotal(prices) 只负责调用这两个零件。规则变了(例如标准改成 60,000韩元),只改 shippingFee 一处。
  4. 验算:[20000, 10000] → 合计 30,000 + 运费 3,000 = 33,000。[30000, 20000] → 合计 50,000(边界值,属于“及以上”,所以免运费)= 50,000。
答案把合计、运费、最终金额分别拆成函数再组装:33000、50000
条件 ? 值1 : 值2 是条件为真时取值1、为假时取值2 的简短条件表达式(三元运算符)。可以看作把第3讲的 if/else 写成了一行。写长了的话,用 if 更好读。

7.纯函数与易于测试的代码

输入相同就总是返回相同结果、也不修改外部变量或屏幕之类东西的函数,叫作纯函数。double(4) 什么时候调用都是 8。相反,下面的 addVisit 会修改外部变量 visits,所以同样的调用每次返回的值都不一样。这种函数的结果取决于“到目前为止发生过什么”,很难追查原因。

纯函数最大的优点是容易测试,因为只要有一张“这个输入对应这个结果”的表就够了。写好函数后,挑几个普通情况、边界值(0、空列表、恰好等于标准的值)和奇怪的输入,先写下期望值,再和结果比较。本页练习的评分方式正是如此,第10讲验证 AI 写的代码时也用同样的方法。

当然,不是所有函数都能是纯的。打印到屏幕、保存文件、发送到服务器,这些事的目的就是改变外部。诀窍是把计算单独拆成纯函数,把改变外部的事集中在边缘的少数几个地方。这样,容易出错的计算部分可以用表格检验,改变外部的部分很短,用眼睛检查也容易。

同样的调用、不同的结果(不纯)vs 总是相同的结果(纯)
let visits = 0;
function addVisit() {
  visits++;
  return visits;
}
console.log(addVisit(), addVisit());

const nextCount = (n) => n + 1;
console.log(nextCount(0), nextCount(0));
输出
1 2
1 1
用输入与期望值的表亲自检验——一定要放进边界值 19
const isAdult = (age) => age >= 19;
function check(input, want) {
  const got = isAdult(input);
  const ok = got === want;
  console.log(input, ok ? '通过' : '失败');
}
check(18, false);
check(19, true);
check(0, false);
输出
18 通过
19 通过
0 通过

📌 要点总结

  • 函数是接收输入(参数)、处理后用 return 交回结果的有名字的代码
  • console.log 只是给人看,要让其他代码使用就必须 return
  • 缺了实参是 undefined,没有 return 也是 undefined——不报错却可能变成 NaN
  • 箭头函数用了花括号,就必须自己写 return
  • 拆成能用一句话说明的小纯函数,就容易用输入与期望值的表来检验

✍️ 练习题

请先自己作答,再展开“答案与解析”。

Q1. 有 function f(x) { console.log(x + 1); } 时,执行 const r = f(2); 后 r 里装的是什么?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ② undefined

f 只把 3 打印到屏幕,没有 return,所以返回 undefined。想接着使用这个值,就改成 return x + 1;。

Q2. const g = (x) => { x * 2; }; 中 g(5) 的结果是?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ② undefined

用了花括号就被当作函数体,必须自己写 return。可以像 (x) => x * 2 这样去掉花括号,或改成 { return x * 2; }。

Q3. 用 area(5) 调用 function area(w, h) { return w * h; } 会得到?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ③ NaN

h 没有收到实参,成了 undefined,而 5 * undefined 是 NaN。实参个数不对,JavaScript 也不会报错。

Q4. 下面哪一个是纯函数?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ② 返回收到的两个数中较大者的函数

两个数中的较大者,输入相同结果就总是相同,也不修改外部。修改外部变量、依赖时间这类输入以外的东西、打印到屏幕,都不是纯的。

Q5. 在函数外面使用函数里用 const 创建的变量,会出现哪种错误?请写出错误名称。

答案与解析
参考答案 ReferenceError(… is not defined)

函数里的变量只在这个函数里可见。在外面需要这个值的话,就用 return 返回并存进变量。

🧪 代码练习

负责计算的函数必须返回值才能被评分。从修改只打印的代码开始,到组装小函数,依次完成吧。

代码只在本浏览器内的隔离环境中运行,不会发送到服务器。无法访问网络,超过2秒会自动停止。改过的代码只保存在此浏览器中。按 Ctrl+Enter(⌘+Enter)运行,Tab 键输入两个空格(先按 Esc 再按 Tab 可跳到下一项)。

浏览器脚本未开启,无法运行代码,但题目、初始代码、自动检查项和参考答案都可以直接阅读。

1摄氏转华氏

编写函数 cToF(c):接收摄氏温度 c,返回华氏温度。公式是 华氏 = 摄氏 × 9 / 5 + 32。例:cToF(100) 是 212。

自动检查
  • cToF(0)期望值 32
  • cToF(100)期望值 212
  • cToF(-40)期望值 -40
  • cToF(36.5)期望值 97.7
💡 提示

没有 return,函数就返回 undefined。

查看参考答案
function cToF(c) {
  return c * 9 / 5 + 32;
}

2修改只打印的函数

虚构网店的运费函数 shippingFee(sum) 应该在合计 sum 达到 50,000韩元及以上时返回 0,否则返回 3000。初始代码只把值打印到屏幕,所以评分失败。请改成返回值。

自动检查
  • shippingFee(12000)期望值 3000
  • shippingFee(50000)期望值 0
  • shippingFee(49999)期望值 3000
  • shippingFee(80000)期望值 0
💡 提示

把 console.log(值) 改成 return 值 就行。50,000 是边界值,属于“及以上”,所以免运费。

查看参考答案
function shippingFee(sum) {
  if (sum >= 50000) {
    return 0;
  }
  return 3000;
}

3组装小函数

编写 orderTotal(prices):接收价格数组 prices,返回最终支付金额。在合计上加运费(合计达到 50,000韩元及以上为 0韩元,否则 3,000韩元)。但如果是空数组,说明没有订单,返回 0。请利用初始代码里的 subtotal 和 shippingFee。

自动检查
  • orderTotal([20000,10000])期望值 33000
  • orderTotal([30000,20000])期望值 50000
  • orderTotal([])期望值 0
  • orderTotal([49999])期望值 52999
  • orderTotal([60000,5000])期望值 65000
💡 提示

用 prices.length === 0 判断是否为空数组。把这个检查放在最前面并直接 return,下面的计算就不会执行。

查看参考答案
function subtotal(prices) {
  let sum = 0;
  for (const p of prices) {
    sum += p;
  }
  return sum;
}
const shippingFee = (sum) => (sum >= 50000 ? 0 : 3000);

function orderTotal(prices) {
  if (prices.length === 0) {
    return 0;
  }
  const sum = subtotal(prices);
  return sum + shippingFee(sum);
}

4限制在范围内(箭头函数)

编写箭头函数 clamp:把数 x 限制在 min 到 max(含两端)之间后返回。x 小于 min 时返回 min,大于 max 时返回 max,在两者之间就原样返回 x。例:clamp(15, 0, 10) 是 10。(假设 min 不大于 max。)

自动检查
  • clamp(15, 0, 10)期望值 10
  • clamp(-3, 0, 10)期望值 0
  • clamp(7, 0, 10)期望值 7
  • clamp(0, 0, 10)期望值 0
  • clamp(10, 0, 10)期望值 10
💡 提示

这是用了花括号的箭头函数,所以每个分支都要有 return。也可以把 Math.min 和 Math.max 叠在一起写成一行。

查看参考答案
const clamp = (x, min, max) => {
  if (x < min) return min;
  if (x > max) return max;
  return x;
};

🤖 可以这样问 AI

复制后把 [ ] 部分换成你的情况。不要直接相信回答,请与正文对照核实。

想把长代码拆成函数时

请把下面这段 JavaScript 代码拆成“只做一件事”的小函数。把每个函数的名字、参数、返回值整理成表格,负责计算的函数不要用 `console.log`,而是用 `return` 返回。不要改变功能,并逐行说明改动之处。代码:[粘贴]

需要一张测试表来检验自己写的函数时

请为下面的函数做一张包含 6 组测试输入和期望值的表格:普通情况 2 组、边界值(0、空值、等于标准的值)3 组、奇怪的输入 1 组,并用一句话说明每个期望值为什么是这样。函数说明:[这个函数做什么] 代码:[粘贴]

分不清 `return` 和 `console.log` 时

请写 3 个非常短的例子,展示 JavaScript 中 `return` 和 `console.log` 的区别。每个例子都先不要告诉我运行结果,等我猜出结果并回答后,再告诉我对不对以及原因。

🧰 相关工具

可以当场检查、整理本讲涉及的 JSON、正则表达式和文本的工具。请不要粘贴真实的个人信息或密码。

参考
  • MDN 文档 — JavaScript 指南:函数
  • MDN 文档 — 箭头函数表达式、return 语句、默认参数参考
  • 编程入门教材中关于纯函数与单元测试的一般概念

达成全部学习目标后,请点击完成。

💻 编程基础

  1. 1什么是程序:把事情拆成顺序、条件和循环的思维
  2. 2值、变量与数据类型:给值贴上名字标签
  3. 3条件语句:根据情况走不同的路
  4. 4循环 — 让同一件事准确地重复多次
  5. 5函数 — 拆成一个个有名字的小机器
  6. 6数组与对象 — 装载和处理数据
  7. 7字符串与文本处理——截取、查找、替换文字
  8. 8调试与读懂错误——把红字变成线索
  9. 9简单的自动化示例——用代码做表格计算和文本整理
  10. 10阅读并检验 AI 写的代码——测试、安全、许可证与个人信息
📚 推荐阅读
📊Using AI for Spreadsheets, and Checking the Formulas→ 💻AI Coding Assistants: Security and Licence Risks→ 💬Practising Conversation in a Foreign Language with AI→ ⚙️Finding Work Tasks Worth Automating with AI→
← AI时代基础学力课程