1.什么是函数——接收输入、交回结果的机器
第1讲我们把程序看作输入 → 处理 → 输出。函数就是把这个结构切出一小块并起个名字。想想自动售货机就容易明白:投进钱、按下按钮(输入),不必知道里面发生了什么,饮料(结果)就出来了。做好一台,就能一次又一次地使用同一台售货机。
没有函数的话,每次要用同一个计算都得复制代码。一旦计算规则变了,就要找出所有复制过的地方逐一修改,漏掉一处,结果就会彼此不一致。做成函数后,规则只存在于一个地方,修改时也只改那里。而且像 vatOf(price) 这样,光看名字就知道它做什么,代码读起来就像句子。
创建函数叫“定义”,使用函数叫“调用”。只定义的话什么也不会发生,要在名字后面加上括号,像 vatOf(10000) 这样调用,函数体才会执行。注意,不加括号只写 vatOf,只是指向函数本身,并不会执行。
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` | |
|---|---|---|
| 交给谁 | 调用函数的代码 | 看屏幕的人 |
| 值能否接着用 | 能(用于变量、计算) | 不能 |
| 函数会结束吗 | 在那一刻结束 | 继续执行下一行 |
| 练习评分 | 按返回的值评分 | 不用于评分 |
| 主要用在哪里 | 负责计算的函数里 | 检查、调试、最终输出 |
4.箭头函数——另一种简短写法
函数也可以写成 const 名字 = (参数) => 表达式 的样子,这叫箭头函数。箭头右边只有一个表达式时,这个表达式的值会自动返回,所以不写 return。在把函数传给另一个函数时用得特别多,比如第6讲要学的 map、filter,在 AI 写的代码里也经常见到。
有一个陷阱要注意。箭头右边一旦用了花括号,就会被当作普通函数体,必须自己写 return。下面的 bad 在花括号里只写了表达式、漏了 return,结果返回 undefined。它不会报错,所以不打印结果的话很容易蒙混过去。记住:短的不加花括号,多行的花括号和 return 一起写。
用 function 写的函数和箭头函数,在本课程涉及的范围内做的事情一样。细节上有些不同(例如处理 this 的方式),但入门阶段不必了解。没有哪一种更“好”,跟着合作的人或项目所用的写法走就行。
returnconst 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 defined6.拆成小函数
把很长的计算写成一大块,很难找到哪里出了错。如果拆成多个“只做一件事”的小函数再组装起来,每个零件都能单独检验,名字本身也起到说明的作用。函数名用动词表明它做的事,如 getTotal、isAdult;返回真假的函数习惯以 is、has 开头。
找拆分点的问题是:“这一部分能用一句话说明吗?”像“把商品价格全部加起来”“根据合计决定运费”这样能成为一句话的,就是一个函数。说明里出现好几个“然后”,就说明还能再拆。不过,把一行的计算拆得过细反而难读,所以要按值得起名字的意义单位来拆。
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步 拆成句子:“把价格全部加起来” / “按合计决定运费” / “把两者相加得出最终金额”。
- 第2步 零件函数:
subtotal(prices)是第4讲的累加模式,shippingFee(sum)是第3讲的条件语句。 - 第3步 组装:
orderTotal(prices)只负责调用这两个零件。规则变了(例如标准改成 60,000韩元),只改shippingFee一处。 - 验算:[20000, 10000] → 合计 30,000 + 运费 3,000 = 33,000。[30000, 20000] → 合计 50,000(边界值,属于“及以上”,所以免运费)= 50,000。
条件 ? 值1 : 值2 是条件为真时取值1、为假时取值2 的简短条件表达式(三元运算符)。可以看作把第3讲的 if/else 写成了一行。写长了的话,用 if 更好读。7.纯函数与易于测试的代码
输入相同就总是返回相同结果、也不修改外部变量或屏幕之类东西的函数,叫作纯函数。double(4) 什么时候调用都是 8。相反,下面的 addVisit 会修改外部变量 visits,所以同样的调用每次返回的值都不一样。这种函数的结果取决于“到目前为止发生过什么”,很难追查原因。
纯函数最大的优点是容易测试,因为只要有一张“这个输入对应这个结果”的表就够了。写好函数后,挑几个普通情况、边界值(0、空列表、恰好等于标准的值)和奇怪的输入,先写下期望值,再和结果比较。本页练习的评分方式正是如此,第10讲验证 AI 写的代码时也用同样的方法。
当然,不是所有函数都能是纯的。打印到屏幕、保存文件、发送到服务器,这些事的目的就是改变外部。诀窍是把计算单独拆成纯函数,把改变外部的事集中在边缘的少数几个地方。这样,容易出错的计算部分可以用表格检验,改变外部的部分很短,用眼睛检查也容易。
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
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 - 拆成能用一句话说明的小纯函数,就容易用输入与期望值的表来检验
🧪 代码练习
负责计算的函数必须返回值才能被评分。从修改只打印的代码开始,到组装小函数,依次完成吧。
代码只在本浏览器内的隔离环境中运行,不会发送到服务器。无法访问网络,超过2秒会自动停止。改过的代码只保存在此浏览器中。按 Ctrl+Enter(⌘+Enter)运行,Tab 键输入两个空格(先按 Esc 再按 Tab 可跳到下一项)。
浏览器脚本未开启,无法运行代码,但题目、初始代码、自动检查项和参考答案都可以直接阅读。
1摄氏转华氏
编写函数 cToF(c):接收摄氏温度 c,返回华氏温度。公式是 华氏 = 摄氏 × 9 / 5 + 32。例:cToF(100) 是 212。
cToF(0)期望值32cToF(100)期望值212cToF(-40)期望值-40cToF(36.5)期望值97.7
💡 提示
没有 return,函数就返回 undefined。
查看参考答案
function cToF(c) {
return c * 9 / 5 + 32;
}2修改只打印的函数
虚构网店的运费函数 shippingFee(sum) 应该在合计 sum 达到 50,000韩元及以上时返回 0,否则返回 3000。初始代码只把值打印到屏幕,所以评分失败。请改成返回值。
shippingFee(12000)期望值3000shippingFee(50000)期望值0shippingFee(49999)期望值3000shippingFee(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])期望值33000orderTotal([30000,20000])期望值50000orderTotal([])期望值0orderTotal([49999])期望值52999orderTotal([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)期望值10clamp(-3, 0, 10)期望值0clamp(7, 0, 10)期望值7clamp(0, 0, 10)期望值0clamp(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 语句、默认参数参考
- 编程入门教材中关于纯函数与单元测试的一般概念
达成全部学习目标后,请点击完成。
此浏览器无法保存记录,仅在当前页面显示。