大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
函数与作用域
这一课你将能做到
- 声明函数并用参数接收输入、用
return给出结果 - 解释什么叫「作用域」(变量在哪可见),并能指出一段代码里谁能看见谁
- 看懂箭头函数的基本写法,知道和普通函数在调用方式上的一点差别
为什么要学
把「评级」「校验年龄」复制粘贴三次,改规则就要改三处。
函数把一段逻辑打包,起个名字,需要时调用——这是编程最重要的习惯之一。
作用域则决定「名字在哪能用」。搞不清就会出现:外面改不到里面的变量、或者误用全局变量互相踩脚。
先建立图像
函数像榨汁机:
输入(参数:苹果) → 处理 → 输出(return:果汁)
不写 return,默认得到 undefined(「没明确送出东西」)
作用域人话:变量的「可见范围」。
在函数里用 const/let 声明的变量,外面一般拿不到——这是好事,避免名字打架。
外层:site
└─ 函数 greet 内:message
greet 可读 site(往外找)
外层不能读 message(进不去)
跟着做
步骤 1:函数声明
// 函数声明:有名字,可被调用
function add(a, b) {
return a + b; // 把结果送出去
}
console.log(add(2, 3)); // 5
console.log(add); // 函数本身(还没调用)
记住:add 是函数值;add() 才是调用并执行。
步骤 2:函数表达式与箭头函数
const add2 = function (a, b) {
return a + b;
};
const add3 = (a, b) => {
return a + b;
};
// 只有一句返回时可以更短:
const add4 = (a, b) => a + b;
console.log(add2(1, 2), add3(1, 2), add4(1, 2));
初学:三种都能算;团队项目里箭头函数很常见。
注意:箭头函数没有自己的 this(进阶话题);DOM 回调里用箭头通常更省心。
步骤 3:作用域演示
const site = "CodeRoadMap"; // 外层
function greet(name) {
const message = "欢迎," + name; // 只在函数里
return message + " @ " + site; // 里面可以读外面的 site
}
console.log(greet("同学"));
// console.log(message); // 打开会报错:外面没有 message
你应看到欢迎语;若取消注释最后一行,会得到 ReferenceError——这正好说明作用域在起作用。
步骤 4:默认参数与提前校验
function labelScore(score, unit = "分") {
if (!Number.isFinite(score)) {
return "无效分数";
}
return `${score}${unit}`;
}
console.log(labelScore(95)); // 95分
console.log(labelScore(95, " pts")); // 95 pts
console.log(labelScore(NaN)); // 无效分数
默认参数减少「调用方漏传」时的尴尬;无效输入仍应用卫语句拦住。
步骤 5:接到按钮(把函数当积木)
保存为 js-functions.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>函数</title>
</head>
<body>
<input id="a" type="text" value="2" />
<input id="b" type="text" value="3" />
<button type="button" id="sum">相加</button>
<p id="out"></p>
<script>
const add = (x, y) => x + y;
function readNumber(id) {
return Number(document.getElementById(id).value);
}
document.getElementById("sum").addEventListener("click", () => {
const x = readNumber("a");
const y = readNumber("b");
const out = document.getElementById("out");
if (!Number.isFinite(x) || !Number.isFinite(y)) {
out.textContent = "请输入有效数字";
return;
}
out.textContent = "结果:" + add(x, y);
});
</script>
</body>
</html>
点击「相加」应看到结果。注意:readNumber 和 add 各管一件事——小函数更好测、更好改。
步骤 6:闭包混个脸熟(计数器)
function makeCounter() {
let n = 0; // 被内部函数「记住」
return function () {
n = n + 1;
return n;
};
}
const c = makeCounter();
console.log(c()); // 1
console.log(c()); // 2
const c2 = makeCounter();
console.log(c2()); // 1 —— 另一份独立的 n
人话:内层函数还能访问外层变量,即使外层已经执行完——这叫闭包。
先会用计数器直觉即可,不必一次啃完所有理论。
讲清楚要点
- 因为要把逻辑复用,所以抽函数:参数进、
return出。 - 因为
return立刻结束函数,所以return后面的代码不会跑。 - 因为函数内
const/let有块/函数作用域,所以外面访问不到,反而减少命名冲突。 - 因为同样输入应尽量同样输出,所以少在函数里偷偷改全局变量(更「纯」更好查)。
- 因为
add与add()不同,所以把函数当值传递时不要多余加括号(事件监听常传函数引用)。 - 因为参数太多难记,所以超过 3~4 个考虑收成一个对象参数(对象课会用到)。
你可能会懵的地方
-
()忘了写会怎样?
add是函数本身;add()才是调用。没括号等于「把函数当值传递」,不一定执行。 -
闭包是什么?
函数「记住」外层变量的能力。步骤 6 的计数器就是最小例子。 -
箭头函数能不能当所有写法的替代?
多数回调可以。若你需要传统的this绑定或arguments,再用普通函数——进阶时再查 MDN。 -
函数提升(hoisting)
function foo(){}声明在文件后面有时仍能调用;const foo = ()=>{}则不行。现代代码更推荐表达式/箭头并注意声明顺序。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 复制粘贴同一段逻辑 | 改一处漏三处 | 抽函数 |
| 依赖隐式全局变量 | 难查 bug | 参数传入或明确模块内变量 |
| 函数又长又多职责 | 难测难读 | 拆小函数 |
该 return 却只 console.log | 调用方拿不到结果 | 需要结果就 return |
事件里写成 fn() 立刻执行 | 绑的是返回值 | 传 fn 或 () => fn() |
自测三问
return的作用是什么?不写会怎样?- 函数内部的
const外面为什么常常访问不到? add和add()的区别?
小结一句话
函数打包输入→处理→输出;用作用域限制变量可见范围;先会声明/箭头/return,再慢慢熟悉闭包。