Web-Dev-For-Beginners JS 基础精讲:从函数声明到高阶函数的 JavaScript 方法与函数完全指南
本文基于 Web-Dev-For-Beginners 课程 2-js-basics 模块的「Methods and Functions」一节,系统讲解 JavaScript 中函数(Function)与方法(Method)的核心概念:声明与调用、参数与实参、默认值、返回值、函数作为参数(高阶函数),以及匿名函数与箭头函数三种语法风格的选择策略。读完本文,你不仅能独立完成课程配套的「Fun with Functions」练习和 mathUtils.js 工具库挑战,还能在仓库内的 Terrarium、打字游戏、太空射击游戏等真实项目源码中印证这些概念的实际落地方式。
为什么需要函数:把重复逻辑打包成可复用组件
重复编写同一段代码是编程中最常见的烦恼之一。函数(function)解决了这个问题:它允许你把代码打包成一个可复用的块。课程的类比很形象——函数就像让福特装配线革命化的标准件:一旦你做出了一个可靠的组件,就可以在任何需要的地方使用,而不必从零重建。
函数让你把代码片段捆绑起来,在程序的任何位置重复调用。与其把同样的逻辑到处复制粘贴,不如一次创建函数、随时调用。这样做有两个直接收益:
- 代码保持整洁、组织有序;
- 后续维护更容易——试想如果一段逻辑散落在代码库的 20 个地方,修改时会有多大的维护成本。
课程还特别强调命名即文档:给函数起一个描述性的名字至关重要。当你看到 cancelTimer() 时,立刻就能明白它是干什么的,就像清楚标注的按钮告诉你点击后会发生什么一样。
函数(Function)与方法(Method)的区别
课程原文明确指出:你在整个课程中其实一直在使用方法。console.log() 就是一个方法——本质上是属于 console 对象的一个函数。关键区别在于:
- 函数是独立存在的代码块;
- 方法是挂载在对象上的函数,调用时通过对象访问(
对象.方法())。
许多开发者在口语中会混用这两个术语,但理解「方法依附于对象」这一点,是后续学习 DOM API(document.getElementById())、数组方法(map()、filter())的基础。
课程挑战(Challenge):能否用一句话概括函数和方法的区别?答案要点:函数独立存在,方法是属于某个对象、通过该对象调用的函数。
创建和调用函数
语法结构
函数定义遵循一致的模式:
function nameOfFunction() { // function definition
// function definition/body
}
逐部分拆解:
function关键字告诉 JavaScript「嘿,我在创建一个函数!」;nameOfFunction是函数描述性名称的位置;- 圆括号
()是放置参数(parameters)的位置; - 花括号
{}包含调用该函数时实际执行的代码。
一个最小示例:displayGreeting
function displayGreeting() {
console.log('Hello, world!');
}
这个函数把 "Hello, world!" 打印到控制台。定义之后,你可以按需使用任意多次。
要**执行(调用)**函数,写出函数名后紧跟括号:
// calling our function
displayGreeting();
执行这一行时,函数体内所有代码都会被执行,浏览器控制台显示 "Hello, world!"。你甚至可以反复调用同一个函数。
一个重要的 JavaScript 特性:JavaScript 允许你先定义函数、后调用它,也可以先写调用、后写定义(函数声明会被提升,hoisting),JavaScript 引擎会处理执行顺序。
命名最佳实践
课程给出的三条实用建议:
- 给函数起清晰、有描述性的名字——未来的你会感谢现在的自己;
- 多词名称使用驼峰命名法(camelCasing),如
calculateTotal而不是calculate_total; - 让每个函数专注于做好一件事。
实战印证:仓库中 Terrarium 项目的解决方案 script.js 恰好体现了「一次定义、多次复用」——
dragElement函数只定义了一次(script.js 处function dragElement(terrariumElement) {...}),随后被连续调用 14 次,让plant1到plant14这 14 个盆栽元素全部获得拖拽能力。如果不用函数,就得复制 14 份几乎相同的拖拽代码。
向函数传递信息:参数(Parameters)与实参(Arguments)
displayGreeting 只输出固定的 "Hello, world!",能力有限。参数让函数变得灵活:它们是占位符,每次调用时都可以填入不同的值,同一个函数因此能在不同调用中处理不同信息。
定义函数时,把参数列在括号里,多个参数用逗号分隔:
function name(param, param2, param3) {
}
更新后的问候函数接受一个名字参数:
function displayGreeting(name) {
const message = `Hello, ${name}!`;
console.log(message);
}
注意这里用反引号(`)和 ${} 把变量直接拼进字符串——这是模板字符串(template literal),是混入变量构建字符串的便捷方式。
调用时传入任意名字即可:
displayGreeting('Christopher');
// 运行时输出 "Hello, Christopher!"
JavaScript 会把字符串 'Christopher' 赋给 name 参数,再构造出 "Hello, Christopher!" 这条个性化消息。参数可以是任何类型:字符串、数字、布尔值、对象,甚至函数。
实战印证:4-typing-game/solution/index.js 中的
renderQuote函数就是一个典型的带参数函数——它接受quote字符串参数,内部用模板字符串逐词渲染成带高亮类的<span>;而 index.js 的messages.success更是把「函数作为配置项」和模板字符串结合了起来:success: (seconds) => \🎉 Congratulations! You finished in ${seconds.toFixed(2)} seconds.``。
参数的信息流
课程用流程图总结了参数的工作方式,用文字表述就是:
函数调用 displayGreeting('Alice')
→ 参数绑定:name = 'Alice'
→ 函数体执行:`Hello, ${name}!` 模板拼接
→ 产出结果:'Hello, Alice!'
概念核对:参数(parameter)是定义函数时声明的占位符;实参(argument)是调用时实际传入的值。多传的实参会被函数忽略;传少了则对应参数为 undefined。
默认值:让参数可选
如果想让某些参数变成可选的,就用默认值(default values)。
以「允许自定义问候词、未指定时回退到 Hello」为例,用等号即可设置默认值,和普通变量赋值写法一致:
function displayGreeting(name, salutation='Hello') {
console.log(`${salutation}, ${name}`);
}
这里 name 仍然是必需的,而 salutation 在调用者没有提供时使用备用值 'Hello'。
于是同一个函数有两种调用方式:
displayGreeting('Christopher');
// 输出 "Hello, Christopher"(使用默认值)
displayGreeting('Christopher', 'Hi');
// 输出 "Hi, Christopher"(使用自定义值)
第一次调用中 JavaScript 使用默认的 "Hello";第二次则使用自定义的 "Hi"。这种灵活性让函数能适应不同场景。
进阶提示:默认参数让函数更易用——调用者可以用合理默认值快速上手,需要定制时再传参,同时保持向后兼容。
实战印证:太空游戏的 app.js 把默认参数用到了极致:
const $ = (sel, ctx = document) => ctx.querySelector(sel); const $$ = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel));
ctx的默认值是document,因此$('#myCanvas')默认在全局查找;需要限定作用域时才传第二个参数。从源码结构看,这是「默认参数 + 箭头函数 + 返回值」三个概念的组合应用。
返回值:让函数把结果交还给你
前面的函数只是往控制台打印消息。如果希望函数计算出某个结果并交还给你呢?这就是**返回值(return values)**的用途:函数可以把一个值交还给调用方,你可以把它存进变量,或在代码的其他地方使用。
用 return 关键字加上要返回的值:
return myVariable;
关键行为:当函数执行到 return 语句时,会立即停止运行,并把该值送回给调用者。
把问候函数改成「返回消息」而不是「打印消息」:
function createGreetingMessage(name) {
const message = `Hello, ${name}`;
return message;
}
现在这个函数构造消息并把它交还给你。用变量接收返回值:
const greetingMessage = createGreetingMessage('Christopher');
此时 greetingMessage 包含 "Hello, Christopher",你可以把它用在任何地方——显示在网页上、写进邮件、传给另一个函数。函数如果没有 return 语句,会隐式返回 undefined。
关键洞察:返回值的函数更通用,因为由调用方决定如何处置结果,这让代码更具模块化和可复用性。
实战印证:typing-game 的 index.js 中
const getRandomQuote = () => quotes[Math.floor(Math.random() * quotes.length)];是一个箭头函数形式的「纯计算、返回结果」函数:它不直接操作 UI,而是把随机选中的一句名言交还给调用者,由 startGame 决定拿它做什么。返回值让「取数据」和「用数据」分离,正是课程所说模块化思想的体现。
函数作为函数的参数:高阶函数
函数可以被当作参数传给另一个函数。这个概念初看复杂,却是非常强大的特性,支撑起灵活的编程模式。
这种模式最典型的应用场景是表达「当某事发生时,执行另一件事」:比如「定时器结束时运行这段代码」「用户点击按钮时调用这个函数」。
课程用内置函数 setTimeout 演示——它等待指定时间后执行一段代码,你需要告诉它「执行什么」,正好适合传入一个函数:
function displayDone() {
console.log('3 seconds has elapsed');
}
// timer value is in milliseconds
setTimeout(displayDone, 3000);
注意我们传给 setTimeout 的是 displayDone(不带括号)——我们并没有自己调用它,而是把它「移交」给 setTimeout,说「3 秒后调用它」。如果写成 setTimeout(displayDone(), 3000),则是立即调用并把返回值(这里是 undefined)传过去,行为完全不同。
实战印证一(事件回调):Terrarium 的 dragElement 内部就是高阶用法的教科书案例:
terrariumElement.onpointerdown = pointerDrag;把内部函数pointerDrag交给元素作为指针按下事件的回调;随后document.onpointermove = elementDrag;、document.onpointerup = stopElementDrag;又分别注册了移动与松手回调。实战印证二(Promise 回调):浏览器扩展项目的 index.js 中,
axios.get(...).then((response) => { ... })把处理响应的箭头函数交给 Promise 的then方法,这正是「当某事(数据到达)发生时,执行另一件事(渲染结果)」的同款模式。此外 index.js 里let num = (element) => element > closestNum; co2Scale.findIndex(num);也是把函数作为参数传给数组方法findIndex——从源码结构看,这是高阶函数在浏览器环境中的日常形态。
匿名函数
有时你只为一次用途需要函数,不想为它命名——只用一次的函数何必额外占用一个名字?
JavaScript 允许创建匿名函数(anonymous functions):没有名字、直接在使用处定义的函数。
用匿名函数重写定时器示例:
setTimeout(function() {
console.log('3 seconds has elapsed');
}, 3000);
效果完全相同,但函数直接定义在 setTimeout 调用内部,省去了独立的函数声明。
箭头函数(Arrow Functions)
现代 JavaScript 提供了更简洁的函数写法——箭头函数,使用 =>(长得像箭头)。它允许你省略 function 关键字,写出更紧凑的代码。
setTimeout(() => {
console.log('3 seconds has elapsed');
}, 3000);
结构拆解:() 是放参数的位置(本例为空),然后是箭头 =>,最后是花括号包裹的函数体。功能与之前完全一致,只是语法更简洁。
实战印证:打字游戏项目整体就是箭头函数风格。index.js 中连续定义了四个箭头函数工具:
getRandomQuote(取随机引语)、renderQuote(渲染引语)、highlightWord(高亮当前词);index.js 的handleTyping处理每次击键。而太空游戏的 app.js 则用(() => { ... })()(立即执行函数表达式,IIFE)把整个游戏封装在独立的箭头函数作用域内——从源码结构看,这是一种避免污染全局命名空间的常用组织手法。
何时选择哪种写法
课程给出的实用准则:
| 风格 | 形态 | 特点 | 适用场景 |
|---|---|---|---|
| 传统命名函数 | function name() {} |
具名、可被提升(hoisted) | 需要在多处复用的函数 |
| 箭头函数 | const name = () => {} |
简洁、现代风格 | 短回调、工具函数,现代代码库中最常见 |
| 匿名函数 | function() {} |
无名字、就地定义 | 一次性使用的事件处理器等 |
如果函数会被多次使用,就给它命名并独立定义;如果只是某个单一用途,考虑匿名或内联箭头函数。传统语法与箭头函数都是有效选择,只是箭头函数在现代代码库中更为普遍。
现代趋势:因为简洁,箭头函数正成为许多开发者的默认选择,但传统函数依然有其位置。
项目内函数用法的横向对照
仓库里几个项目的解决方案,恰好覆盖本节的每个知识点,适合作为自检清单:
| 知识点 | 文档示例 | 仓库印证(文件与位置) |
|---|---|---|
| 一次定义、多次调用 | displayGreeting() |
script.js:dragElement 调用 14 次 |
| 参数 + 模板字符串 | displayGreeting(name) |
index.js:renderQuote(quote) |
| 默认参数 | salutation='Hello' |
app.js:(sel, ctx = document) |
| 返回值 | createGreetingMessage |
index.js:getRandomQuote |
| 函数作为参数 | setTimeout(displayDone, 3000) |
index.js:findIndex(num);script.js:事件回调赋值 |
| 匿名/箭头回调 | setTimeout(() => {...}, 3000) |
index.js:.then((response) => {...});index.js:addEventListener("click", startGame) |
| 闭包(延伸) | — | script.js 注释明确说明:dragElement 内部函数捕获外层 pos1~pos4 状态,正是后续课程「闭包」概念的雏形 |
实战练习:Fun with Functions 作业
课程配套作业 assignment.md(「Fun with Functions」)要求创建一个名为 functions-practice.js 的文件,分五个部分巩固本节全部概念:
Part 1:基础函数
sayHello:不接受任何参数,仅向控制台打印 "Hello!"。introduceYourself:接受name参数,打印类似 "Hi, my name is [name]" 的消息。
Part 2:带默认参数的函数
greetPerson:接受两个参数——name(必需)和greeting(可选,默认 "Hello"),打印 "[greeting], [name]!" 格式的消息。
Part 3:返回值的函数
addNumbers:接受num1和num2两个参数,返回它们的和。createFullName:接受firstName和lastName参数,返回拼合后的完整姓名(单个字符串)。
Part 4:综合运用
calculateTip:接受billAmount(必需)和tipPercentage(可选,默认 15)两个参数,计算并返回小费金额。
Part 5:测试你的函数
为每个函数添加调用,并用 console.log() 展示结果。作业给出的示例测试调用:
// 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 打开浏览器开发者控制台,即可看到
console.log()的输出。
评分标准(Rubric)摘要
作业定义了五个维度的评分标准:函数创建(6 个函数是否语法正确、命名规范)、参数与默认值(必需/可选参数与默认值是否符合规格)、返回值(该返回的函数正确返回,不该返回的只做动作)、代码质量(组织清晰、命名有意义、缩进规范)、测试(所有函数都有恰当测试且结果展示清晰)。
加分挑战(可选)
- 把其中一个函数改写成箭头函数版本;
- 创建一个接受另一个函数作为参数的函数(类似课程中
setTimeout的示例); - 增加输入校验,让函数能妥善处理无效输入。
进阶挑战:用 Agent 模式构建 mathUtils.js
课程还设计了一个 GitHub Copilot Agent 模式的挑战:创建一个演示本节全部概念的数学工具库 mathUtils.js,包含以下函数:
add:接受两个参数,返回它们的和;multiply:带默认参数值(第二个参数默认为 1);square:箭头函数,接受一个数字并返回其平方;calculate:接受一个函数作为参数和两个数字,然后对该两个数字应用这个函数;- 用合适的测试用例演示调用每个函数。
参照课程的概念,一个符合规格的实现大致如下(自行在本地环境编写运行):
// mathUtils.js — 演示本节全部函数概念
// 1. 基础参数 + 返回值
function add(a, b) {
return a + b;
}
// 2. 默认参数
function multiply(a, b = 1) {
return a * b;
}
// 3. 箭头函数
const square = (n) => n * n;
// 4. 函数作为参数(高阶函数)
function calculate(fn, a, b) {
return fn(a, b);
}
// 5. 测试用例
console.log(add(2, 3)); // 5
console.log(multiply(4)); // 4(b 使用默认值 1)
console.log(square(5)); // 25
console.log(calculate(add, 2, 3)); // 5
console.log(calculate(multiply, 4, 5)); // 20
其中 calculate 就是「函数作为参数」概念的最小实用形态:它本身不关心具体运算,只负责「应用你交给我的那个函数」,调用方通过传入不同的函数决定行为——这与 Terrarium 中「事件发生 → 执行回调」的模式在结构上是同一件事。
复习与进阶学习路径
课程建议重点复习箭头函数——它们在代码库中的使用率持续上升。练习方法:先按传统语法写一个函数,再把它改写成箭头函数语法。
按照课程给出的进阶节奏,一个合理的练习路径是:
- 起步(几十分钟内):写一个返回你最喜欢的数字的简单函数;写一个两参数求和函数;把一个传统函数改写成箭头函数;完成「函数 vs 方法」的一句话挑战。
- 本周:构建上面的
mathUtils.js工具库;完成「Fun with Functions」作业的全部 6 个函数;写一个以另一个函数为参数的函数;练习默认参数与模板字符串返回值。 - 进阶:把写过的重复代码重构为可复用函数;只用函数(不用全局变量)搭一个小计算器;用箭头函数配合
map()、filter()练习数组方法;研究高阶函数与函数式编程概念;在闭包与作用域(仓库 Terrarium 的dragElement是天然的闭包示例)上深入,构建大量使用函数组合的项目。
小结:函数工具箱
把本节的知识浓缩成一张对照表:
| 概念 | 关键语法 | 核心要点 |
|---|---|---|
| 函数声明 | function name() {} |
描述性命名(camelCase)、一次定义、多次复用、可提升 |
| 方法 | obj.method() |
挂在对象上的函数,如 console.log() |
| 参数 | function f(a, b) {} |
占位符;调用时传入的实参可以是任意类型 |
| 默认值 | function f(a, b = 1) {} |
参数可选、保持向后兼容 |
| 返回值 | return value; |
遇到 return 立即退出并交还值;调用方决定用途,模块化更强 |
| 高阶函数 | setTimeout(fn, ms)、findIndex(fn) |
函数可作参数传递,表达「当 X 发生时做 Y」 |
| 匿名函数 | function() {} |
一次性用途、无需命名 |
| 箭头函数 | const f = (a) => ... |
简洁现代,短回调首选 |
函数的收益可以归结为四点:代码可复用、组织更清晰、更易测试、支持模块化设计。正如课程结语所言:函数是更大程序的构建块——你未来构建的每一个应用都会用函数来组织、复用和结构化代码。掌握把逻辑打包成可复用组件的能力,就是进入模块化编程世界的入口。
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
