Web-Dev-For-Beginners「Fun with Functions」练习全解:JavaScript 函数定义、参数、默认值与返回值实战
本篇指南基于 Web-Dev-For-Beginners(Web Dev 101,"24 Lessons, 12 Weeks" 在线课程仓库)中 JavaScript 基础模块的第二课作业《Fun with Functions》(assignment.md,仓库内同时提供 孟加拉语译本)展开。作业要求你在 functions-practice.js 中实现 6 个层层递进的函数,覆盖无参函数、带参函数、默认参数、返回语句以及综合应用,并配套完整的测试调用与评分标准。读完并动手完成本篇后,你将能够独立写出带默认参数和返回值规范的 JavaScript 函数,理解"只打印"与"返回结果"的本质区别,并通过加分挑战接触箭头函数与高阶函数等现代语法。
作业定位:它在课程中的位置
这份作业隶属于课程第 2 个模块 JavaScript Basics(2-js-basics) 下的第 2 课 Functions and Methods。该模块共 4 课:变量与数据类型、函数与方法、条件判断、数组与循环。本课是其中承上启下的一课——前一课 Variables and Data Types 教你"存数据",本课教你"封装逻辑",而后面的 Arrays and Loops 将大量依赖你本课写好的函数来处理批量数据。
作业文档的官方说明是:通过创建不同类型的函数,强化你对 JavaScript 函数、参数(parameters)、默认值(default values)和返回语句(return statements)的理解。下面按作业原文的五个部分逐一拆解,每部分都给出可直接运行的参考实现与原理说明。
准备:文件与运行环境
作业要求创建一个名为 functions-practice.js 的 JavaScript 文件并实现下列全部函数。该文件由你自己创建(仓库中没有该文件,这是刻意留给你动手的练习位),放在任意位置都可以,典型做法是:
- 在浏览器中新建一个空白 HTML 页面,通过
<script src="functions-practice.js"></script>引入该文件; - 按
F12打开浏览器的开发者工具(Developer Console)——这是作业文档末尾特别提示的技巧:只有打开控制台,才能看到console.log()的输出。
下面所有代码块均可直接复制到 functions-practice.js 中运行验证。
第 1 部分:基础函数(Basic Functions)
1. sayHello:无参函数
要求:创建一个叫 sayHello 的函数,不接受任何参数,向控制台输出 "Hello!"。
参考实现:
// 无参函数:圆括号内为空
function sayHello() {
console.log('Hello!');
}
从源码结构看,这正是配套课程讲义(2-js-basics/2-functions-methods/README.md)中 displayGreeting 例子的同构形态:function 关键字 + 函数名 + 圆括号 + 花括号函数体。注意两点:
- 调用时必须写圆括号
sayHello()——只写sayHello而不加括号,引用的是函数本身而非执行它; console.log()严格来说是一个方法(method)——挂在console对象上的函数。课程讲义专门提示了 function 与 method 的区别:方法依附于对象,函数独立存在;日常口语中开发者常混用这两个词。
2. introduceYourself:带参函数
要求:创建 introduceYourself 函数,接受一个 name 参数,向控制台输出形如 "Hi, my name is [name]" 的消息。
参考实现:
// 带参函数:name 是占位符(参数),调用时传入的值称为实参(argument)
function introduceYourself(name) {
console.log(`Hi, my name is ${name}`);
}
这里有两个知识点:
- 参数 vs 实参:定义时的
name是参数(占位符),调用introduceYourself('Sarah')时传入的'Sarah'是实参。JavaScript 会把实参按位置绑定到对应参数上; - 模板字符串(template literal):用反引号
`包裹字符串,用${name}把变量直接插进文本里。这是课程讲义推荐拼接动态消息的惯用写法,比'Hi, my name is ' + name更清晰。
第 2 部分:带默认参数的函数(Default Parameters)
3. greetPerson:必填参数 + 可选参数
要求:创建 greetPerson 函数,接受两个参数:name(必填)和 greeting(可选,默认值为 "Hello")。函数向控制台输出形如 "[greeting], [name]!" 的消息。
参考实现:
// greeting 的默认值是 'Hello':调用时不传第二个参数时自动使用
function greetPerson(name, greeting = 'Hello') {
console.log(`${greeting}, ${name}!`);
}
调用效果:
greetPerson('Alex'); // 输出: Hello, Alex!
greetPerson('Maria', 'Hi'); // 输出: Hi, Maria!
默认参数的工作机制(可以推断,符合 ECMAScript 标准行为):当调用方没有为某个参数提供值(即该位置实参为 undefined)时,引擎使用函数定义中 = 后面的默认表达式。这带来两个工程上的好处:
- 向后兼容:旧调用
greetPerson('Alex')在新参数加入后无需修改; - 降低使用门槛:调用者先获得一个合理的默认行为,需要时再自定义——课程讲义称之为让函数"更友好"的实用技巧。
注意默认参数必须写在参数列表的靠后位置:如果把 greeting = 'Hello' 放在 name 前面,调用 greetPerson('Alex') 时 'Alex' 会先绑定给 greeting,语义就错了。本作业刻意把必填参数放前面、可选参数放后面,就是这个原因。
第 3 部分:返回值的函数(Return Values)
4. addNumbers:返回两数之和
要求:创建 addNumbers 函数,接受 num1 和 num2 两个参数,返回它们的和。
参考实现:
function addNumbers(num1, num2) {
return num1 + num2; // return 把结果交还给调用方,并立即结束函数
}
5. createFullName:返回拼接后的字符串
要求:创建 createFullName 函数,接受 firstName 和 lastName 两个参数,把完整姓名作为单个字符串返回。
参考实现:
function createFullName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
return 语句是这部分的核心考点,课程讲义用流程图强调了两条语义:
- 执行到
return时,函数立即停止运行并把值交还给调用方,return之后的代码不会再执行; - 没有
return的函数只会执行动作(比如打印日志),最终隐式返回undefined。
这也正是作业评分标准中单独列出"返回值"一行的原因:该返回的函数必须正确返回值,不该返回的函数(如 sayHello、greetPerson)只做动作即可。返回值让"调用方决定如何使用结果"成为可能——存入变量、参与表达式、再传给别的函数——这就是讲义所说"返回值的函数更通用、代码更模块化"的含义。
第 4 部分:概念综合(Mix It All Together)
6. calculateTip:默认参数 + 返回值 + 数值计算
要求:创建 calculateTip 函数,接受两个参数:billAmount(必填)和 tipPercentage(可选,默认值 15)。函数计算并返回小费金额。
参考实现:
// 默认小费比例 15%:calculateTip(50) 等价于 calculateTip(50, 15)
function calculateTip(billAmount, tipPercentage = 15) {
return (billAmount * tipPercentage) / 100;
}
这个函数把前三部分的能力全部揉在一起:必填/可选参数、默认值、返回数值。注意 tipPercentage 是"百分比"而非"小数",所以要除以 100;calculateTip(50) 应返回 7.5。
第 5 部分:测试你的函数(Test Your Functions)
作业原文给出的测试调用示例(完整继承,可直接使用):
// Test your functions here
sayHello();
introduceYourself("Sarah");
greetPerson("Alex");
greetPerson("Maria", "Hi");
const sum = addNumbers(5, 3);
console.log(`The sum is: ${sum}`);
const fullName = createFullName("John", "Doe");
console.log(`Full name: ${fullName}`);
const tip = calculateTip(50);
console.log(`Tip for $50 bill: $${tip}`);
在浏览器控制台(F12)中运行上述全部代码,预期输出依次为:
Hello!
Hi, my name is Sarah
Hello, Alex!
Hi, Maria!
The sum is: 8
Full name: John Doe
Tip for $50 bill: $7.5
如果实际输出与预期不一致,最常见的两类错误是:把"应该 return 的函数"写成了 console.log(控制台有输出但 sum 变成 undefined);或忘记写默认值导致 greetPerson('Alex') 打印出 undefined, Alex!。对照评分标准第 3 行逐项自查即可。
完整参考答案:functions-practice.js
把六部分合并,一份完整可运行的 functions-practice.js 如下(变量名、命名均遵循作业要求的 camelCasing 规范):
// ============ Part 1: Basic Functions ============
function sayHello() {
console.log('Hello!');
}
function introduceYourself(name) {
console.log(`Hi, my name is ${name}`);
}
// ============ Part 2: Functions with Default Parameters ============
function greetPerson(name, greeting = 'Hello') {
console.log(`${greeting}, ${name}!`);
}
// ============ Part 3: Functions that Return Values ============
function addNumbers(num1, num2) {
return num1 + num2;
}
function createFullName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
// ============ Part 4: Mix It All Together ============
function calculateTip(billAmount, tipPercentage = 15) {
return (billAmount * tipPercentage) / 100;
}
// ============ Part 5: Test Your Functions ============
sayHello();
introduceYourself('Sarah');
greetPerson('Alex');
greetPerson('Maria', 'Hi');
const sum = addNumbers(5, 3);
console.log(`The sum is: ${sum}`);
const fullName = createFullName('John', 'Doe');
console.log(`Full name: ${fullName}`);
const tip = calculateTip(50);
console.log(`Tip for $50 bill: $${tip}`);
评分标准(Rubric)
作业附带的评分表(对照原文 assignment.md 翻译如下),提交前可逐行自查:
| 标准 | 优秀(Exemplary) | 合格(Adequate) | 待改进(Needs Improvement) |
|---|---|---|---|
| 函数创建 | 6 个函数全部正确实现,语法与命名规范均正确 | 4–5 个函数正确实现,存在轻微语法问题 | 仅实现 3 个及以下,或存在重大语法错误 |
| 参数与默认值 | 必填参数、可选参数与默认值均按规范正确使用 | 参数使用正确,但默认值处理可能有问题 | 参数实现错误或缺失 |
| 返回值 | 应返回值的函数正确返回;不应返回的函数仅执行动作 | 大部分返回值正确,存在轻微问题 | return 语句存在明显问题 |
| 代码质量 | 代码清晰、组织有序,变量命名有意义,缩进正确 | 代码可运行,但可读性或组织可更好 | 代码难以阅读或结构混乱 |
| 测试 | 所有函数均被恰当测试,结果展示清晰 | 大部分函数得到恰当测试 | 函数测试不足或测试错误 |
其中"代码质量"一行的命名规范,对应课程讲义中的最佳实践清单:给函数取清晰描述性的名字、多词名用 camelCasing(如 calculateTotal 而非 calculate_total)、每个函数聚焦做好一件事。
核心原理深化:结合课程讲义理解作业考点
作业本身只有需求与评分标准,配套讲义(2-js-basics/2-functions-methods/README.md)给出了每个考点背后的原理,以下要点与作业直接对应。
为什么同一个函数可以反复调用
函数定义一次、调用任意次。讲义的类比是亨利·福特的流水线标准件:逻辑一旦封装成可靠"部件",在任何需要的地方直接使用而无需重建。反过来想维护成本——同一段逻辑散落在 20 处时,修改一处就要改 20 处,而封装成函数后只改一处。这是作业把 6 个函数都设计成"一次定义、多次测试调用"的动机。
调用与定义的顺序无关
JavaScript 引擎会处理函数声明的提升(hoisting):你可以在定义之前调用 sayHello(),运行依然正常。这一点与 const 声明的箭头函数不同,属于函数声明特有的行为。
console.log 是方法,不是函数
整个作业中你反复使用的 console.log() 属于挂在 console 对象上的方法。讲义给出的判别标准:方法依附于对象(写作 对象.方法()),函数独立声明存在。作业里的 sayHello()、greetPerson() 都是后者。
默认参数为何"更灵活"
讲义用状态图对比了三种形态:无参函数输出恒定、带参函数随输入变化、带默认参数函数"可选定制且向后兼容"。对应到本作业,greetPerson 与 calculateTip 同时演示了同一函数被两种调用方式(greetPerson('Alex') 与 greetPerson('Maria', 'Hi'))复用的灵活性——这正是默认参数的价值所在。
返回值为何优于直接打印
讲义的关键结论:返回值的函数更通用,因为调用方决定如何处理结果——显示到网页、拼进邮件、传给下一个函数都可以;而直接 console.log 把输出渠道写死在函数内部。这也是评分表把 addNumbers、createFullName、calculateTip 必须返回、而 sayHello 等只做动作作为独立评分项的原因。
加分挑战(Bonus Challenges)
作业原文列出了三个可选进阶挑战,下面逐一给出实现思路与示例代码。
挑战 1:把其中一个函数改写成箭头函数
讲义指出箭头函数(arrow function)用 => 省去 function 关键字,是现代表达式的常见选择。最短改写:
// 无参 + 简写体:圆括号可省略参数名,单表达式自动返回
const sayHello = () => console.log('Hello!');
// 带参数 + 简写返回
const addNumbers = (num1, num2) => num1 + num2;
// 需要多语句时保留花括号,并显式 return
const calculateTip = (billAmount, tipPercentage = 15) => {
const tip = (billAmount * tipPercentage) / 100;
return tip;
};
讲义给出的选型建议:函数要复用就单独命名定义;一次性使用的回调考虑匿名/箭头形式。箭头函数通常绑定在 const 上,不具有函数声明那样的提升行为。
挑战 2:创建一个把另一个函数作为参数接受的函数
讲义用 setTimeout 示范了"函数作为参数"的模式——注意传的是函数本身(不带圆括号),把"何时执行"的决定权交给对方:
function displayDone() {
console.log('3 seconds has elapsed');
}
// timer 单位是毫秒
setTimeout(displayDone, 3000);
// 等价写法一:匿名函数(只为这一次调用服务,不必命名)
setTimeout(function() {
console.log('3 seconds has elapsed');
}, 3000);
// 等价写法二:箭头函数(更简洁)
setTimeout(() => {
console.log('3 seconds has elapsed');
}, 3000);
在自己的作业文件里可以写一个通用的高阶函数,把 addNumbers 当作参数传入:
// operation 是一个函数,由调用方决定"怎么算"
function calculateWith(a, b, operation) {
return operation(a, b);
}
console.log(calculateWith(5, 3, addNumbers)); // 8
console.log(calculateWith(5, 3, (x, y) => x - y)); // 2(箭头函数实参)
挑战 3:加入输入校验,让函数优雅处理非法输入
参考实现(对第 3、4 部分的关键函数做类型与范围检查):
function addNumbers(num1, num2) {
if (typeof num1 !== 'number' || typeof num2 !== 'number') {
throw new TypeError('addNumbers 的两个参数都必须是数字');
}
return num1 + num2;
}
function calculateTip(billAmount, tipPercentage = 15) {
if (typeof billAmount !== 'number' || billAmount < 0) {
throw new RangeError('billAmount 必须是非负数字');
}
if (typeof tipPercentage !== 'number' || tipPercentage < 0 || tipPercentage > 100) {
throw new RangeError('tipPercentage 必须在 0 到 100 之间');
}
return (billAmount * tipPercentage) / 100;
}
// 测试:正常与非法输入各跑一遍
console.log(calculateTip(50)); // 7.5
try {
calculateTip(50, 120); // 触发 RangeError
} catch (e) {
console.log(`已拦截非法输入: ${e.message}`);
}
校验的意义在于:默认参数只能兜住"忘传"的情况,兜不住"传错"(如 calculateTip('fifty') 会得到 NaN)。加一层 typeof 与范围判断后,函数对非法输入有明确的错误信息,调用方可以用 try/catch 捕获,这也是"代码质量"评分行中"结构良好"的具体体现。
自检清单与后续学习
提交前对照检查:
- [ ] 6 个函数全部实现,命名使用 camelCasing,与作业要求完全一致;
- [ ]
greetPerson与calculateTip的默认值分别是'Hello'与15,且放在必填参数之后; - [ ]
addNumbers、createFullName、calculateTip用return交还结果,而非console.log; - [ ] 测试代码覆盖"传默认值"与"显式传可选参数"两种调用方式;
- [ ] 在浏览器开发者控制台(F12)中确认输出与预期一致;
- [ ] (可选)完成至少一个加分挑战。
完成本作业后,建议按模块顺序继续:Making Decisions with JavaScript(用 if/else 给 calculateTip 这类函数加上条件逻辑),以及 Arrays and Loops(讲义在"一周计划"中特别建议:用 map()、filter() 等数组方法配合箭头函数做练习——那正是本课学到的"函数作为参数"模式的用武之地)。
相关文件索引
| 文件 | 说明 |
|---|---|
| 2-js-basics/2-functions-methods/assignment.md | 本作业英文原文(Fun with Functions) |
| translations/bn/2-js-basics/2-functions-methods/assignment.md | 本作业的孟加拉语译文(与原文内容一致) |
| 2-js-basics/2-functions-methods/README.md | 配套课程讲义:函数、参数、默认值、返回值、箭头函数原理 |
| 2-js-basics/README.md | JavaScript 基础模块总览(4 课) |
| 2-js-basics/1-data-types/README.md | 前置课程:变量与数据类型 |
| 2-js-basics/4-arrays-loops/README.md | 后续课程:数组与循环 |
| sketchnotes/webdev101-js-functions.png | 本课函数知识的手绘速记图(Sketchnote) |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
