函数
这是什么:函数 = 打包好的一段代码,给它名字和参数,用的时候调用一次就跑一次。
为什么用:变量解决了"记住"的问题。要是同一段代码要反复用呢?复制粘贴三遍?不——打包成函数,用一次调一次。
如何用:在 F12 控制台里写一个 function greet(name),再调用它两次看结果。
声明一个函数
function greet(name) {
return `你好,${name}!`
}
console.log(greet('青蛙')) // 你好,青蛙!
console.log(greet('世界')) // 你好,世界!
「你会看到」两行问候语——同一个函数,喂不同的名字,得到不同的结果。
拆开看:
| 部件 | 作用 |
|---|---|
function greet(name) |
声明函数:名字叫 greet,括号里是参数(占位符) |
return |
把结果交还给调用者,函数到此结束 |
greet('青蛙') |
调用:'青蛙' 被装进参数 name |
记住:写 function 只是打包,加上括号才算调用。只写 greet 不加括号,什么都不会发生。
没写 return 会怎样
function addOne(n) {
n + 1 // 算了,但结果没交出去
}
console.log(addOne(1)) // undefined
「你会看到」undefined——没写 return,函数就"白干"。想让结果出去,必须 return。
箭头函数:函数的简写
// 下面两种写法完全等价
function add(a, b) {
return a + b
}
const add2 = (a, b) => a + b
console.log(add(1, 2)) // 3
console.log(add2(1, 2)) // 3
「你会看到」两个 3。箭头函数把"声明 + return"压缩成一行:参数 => 结果。现代 JS 里你会在别人代码里大量见到它,现在能认出、会写就行。
回调:把函数当参数传
函数在 JS 里是个"值",可以像数字一样传给别人:
setTimeout(function () {
console.log('3 秒到了!')
}, 3000)
「你会看到」控制台先没动静,3 秒后打印"3 秒到了!"。我们把一个函数交给了 setTimeout,时机到了它替我们调用——这种"当参数传的函数"叫回调函数。现在有个直觉就够:以后点按钮、收数据,全靠它。
记忆口诀:function 打包,加括号才调用;return 交结果,箭头是简写;函数当参数传,就叫回调。
❓ 常见问题
Q1:console.log(greet) 打出来一串代码,而不是"你好"?
原因:忘加调用括号。greet 是函数名,代表"函数这个物件";greet() 才是"让它干活"。
[Function: greet]
修复三步:① 找到这行输出;② 在名字后面补上括号和参数:console.log(greet('青蛙'));③ 默念口诀"加括号才是调用"。
举一反三:以后凡见输出 [Function: xxx],第一反应就是"哪里忘了括号"。
Q2:函数里明明算了 n + 1,打印结果却是 undefined?
原因:算了但没 return,结果没交出去,函数默默返回 undefined。
undefined
修复三步:① 打开函数体;② 在算式前面加 return:return n + 1;③ 调用处用 console.log(addOne(1)) 接住结果。
举一反三:undefined 是"函数白干"的信号,调用结果不对时,先查函数里有没有 return。
Q3:箭头函数写 const add = a, b => a + b 报 SyntaxError: Missing initializer in const declaration?
原因:两个及以上参数必须包括号。少括号时 JS 把 const add = a 当成一条完整声明,后面的 , b 就成了看不懂的东西,报错信息也因此很绕。
SyntaxError: Missing initializer in const declaration
修复三步:① 跳到报错那一行;② 给参数套上括号:(a, b) => a + b;③ 记住规则:零个或多个参数要括号,唯一参数可以省。
举一反三:报错看不懂没关系——定位到行、检查参数括号,比读懂报错原文更重要。
Q4:函数体内用了参数 nmae,报 ReferenceError: nmae is not defined?
原因:参数名叫 name,函数体内拼成了 nmae。参数也是变量,拼错照样"查无此人"。
ReferenceError: nmae is not defined
修复三步:① 看报错行号,定位到函数体内那一行;② 对照括号里的参数名逐字母核对;③ 改到与参数名完全一致。 举一反三:函数体内能用的名字只有两个来源——参数和外部变量,报 ReferenceError 就从这两处查。
Q5:setTimeout(remind(), 3000) 为什么不等 3 秒,立刻就执行了?
原因:你写的是 remind()——带括号就是立刻调用,setTimeout 收到的是它的返回值;应该把函数本身(不带括号)交出去,让它到点自己调。
修复三步:① 找到 setTimeout 的第一个参数;② 删掉括号,只传函数名:setTimeout(remind, 3000);③ 或者包一层匿名函数:setTimeout(function () { remind() }, 3000)。
举一反三:"带括号 = 立刻执行;不带 = 把函数当货交给别人"——回调的世界里这条铁律处处适用。
⚠️ 新手翻车现场
翻车一:只打包,不开机
function greet(name) {
return `你好,${name}!`
}
console.log(greet)
完整报错:
[Function: greet]
逐行分析:第 4 行错在 greet 后面没括号——这只是把"函数这个物件"递给了 console.log,它只好把物件本身打出来:一个函数,名字叫 greet。函数从未被调用,return 里那句问候一句都没说出口。
正确写法:
console.log(greet('青蛙'))
翻车二:算完不发货
function addOne(n) {
n + 1
}
console.log(addOne(1))
完整报错:
undefined
逐行分析:第 2 行 n + 1 确实算了,但算完就扔,没 return 交出去;第 4 行接到的只能是 undefined。JS 不报错也不警告——没写 return 是合法的,它只是默默返回 undefined。这种"安静的错"要靠对比预期输出才能抓到。
正确写法:
function addOne(n) {
return n + 1
}
console.log(addOne(1))
翻车三:回调带括号,秒变"立即执行"
function remind() {
console.log('该喝水了!')
}
setTimeout(remind(), 3000)
完整报错:
该喝水了!
(立刻打印,而不是等 3 秒;3 秒后反而什么都没发生)
逐行分析:错在第 4 行 remind() 带了括号——它当场就执行了,setTimeout 收到的只是返回值 undefined;等 3 秒后它试图"调用 undefined",自然什么也不会发生。正确姿势是把函数本身(不带括号)交给 setTimeout。
正确写法:
setTimeout(remind, 3000)
📦 完整小示例
开一家"云端奶茶店":函数算账、箭头函数简写、回调提醒取餐,一样不落:
// 算总价:普通函数写法
function calcPrice(price, cups) {
return price * cups
}
// 同样功能的箭头函数简写
const calcPrice2 = (price, cups) => price * cups
// 打招呼也可以是箭头函数
const greet = (who) => `欢迎光临,${who}!`
console.log(greet('青蛙'))
console.log('原价:' + calcPrice(8, 3) + ' 元')
console.log('箭头版核对:' + calcPrice2(8, 3) + ' 元')
// 回调:2 秒后提醒取餐
setTimeout(function () {
console.log('您的 3 杯奶茶好了,请取餐!')
}, 2000)
你会看到:
欢迎光临,青蛙!
原价:24 元
箭头版核对:24 元
您的 3 杯奶茶好了,请取餐!
💡 代码可直接复制;想改、想跑,点右上「🧪 在线运行场」。
逐段中文精讲
- 第 2~4 行(普通函数):
calcPrice接收price和cups两个参数,return把乘积交出去。函数自己不算具体数——它只管"怎么算",不管"算什么",所以一杯 8 元买 3 杯,或一杯 12 元买 2 杯,都是它。 - 第 7 行(箭头函数):
(price, cups) => price * cups与第 2~4 行完全等价,只是把"声明 + return"压缩成一行。两个参数,所以括号不能省。 - 第 10 行(单参数箭头):
greet只有一个参数who,配合模板字符串拼出问候语——这是日常 JS 里最常见的函数长相。 - 第 13~15 行(调用区):三次调用三次输出。注意
calcPrice(8, 3)的结果被+拼进句子里——函数返回值可以当场当数据用。 - 第 18~20 行(回调区):注意传给 setTimeout 的是匿名函数本身(不带括号),2 秒后由它自己调用——这就是回调的精髓:"货交出去,到点你叫我"。
跑通后做个小实验:把 setTimeout(..., 2000) 的 2000 改成 5000,感受等待变长;再故意把它改成 setTimeout(calcPrice(8, 3), 2000),看看是不是立刻打出了 24——亲手踩一次"带括号"的坑,比看十遍都记得牢。
🎯 什么时候用它
- 表单校验复用:注册页、登录页、改密码页都要查"手机号是不是 11 位"——打包一个
isValidPhone(phone),三处页面调它三次,改规则只改一处。 - 购物车算钱:合计、折扣、满减各封装一个小函数,界面上每动一下就重算一遍——算钱的逻辑进函数,按钮的点击只是"叫它干活"。
- 定时与延时:轮播图 3 秒换一张、提示"操作成功"2 秒后消失、倒计时结束弹提醒——全靠 setTimeout + 回调,前端无处不在。
📝 本章小结
- function 打包,加括号才调用;只写名字不打括号,得到的只是函数这个"物件"。
return把结果交出去,函数到此结束;没写 return,拿到手的永远是 undefined。- 箭头函数是简写:
参数 => 结果;多个参数要括号,(a, b) => a + b。 - 函数是值,可以当参数传给别人——这就是回调;传函数本身别带括号。
- 记忆锚点:函数是台豆浆机——参数是豆子,return 是出浆口;不按开关(不加括号),永远喝不上豆浆。
动手改
- 写一个
function square(n)返回 n 的平方,用console.log(square(9))验证 - 把 greet 改写成箭头函数,结果必须一模一样
- 把 setTimeout 的 3000 改成 1000,再改成 5000,感受"回调"的时机
下一章:操作网页:DOM,让 JS 真正动手改网页 🐸