🐸 青蛙编程网
首页 / 前端 / JavaScript / 3. 函数

函数

这是什么:函数 = 打包好的一段代码,给它名字和参数,用的时候调用一次就跑一次。

为什么用:变量解决了"记住"的问题。要是同一段代码要反复用呢?复制粘贴三遍?不——打包成函数,用一次调一次。

如何用:在 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 是出浆口;不按开关(不加括号),永远喝不上豆浆。

动手改

  1. 写一个 function square(n) 返回 n 的平方,用 console.log(square(9)) 验证
  2. 把 greet 改写成箭头函数,结果必须一模一样
  3. 把 setTimeout 的 3000 改成 1000,再改成 5000,感受"回调"的时机

下一章:操作网页:DOM,让 JS 真正动手改网页 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。