本章定位
前面几章里,你已经开始真正写“有逻辑”的 JavaScript 代码了。
你已经学习了:
- JavaScript 是什么,代码在哪里运行。
- 变量、数据类型与运算。
- 条件判断与循环。
到这里,你已经能写出一些会:
- 存值
- 判断
- 计算
- 重复执行
的代码。
但这时候通常会出现一个新的问题:
同一段逻辑,我好像要写很多次。
例如:
- 计算多个商品的总价
- 多次输出欢迎语
- 多次格式化同一类文本
- 多个地方都需要做相似判断
如果每次都重新从头写一遍,代码很快就会变得:
- 重复很多
- 不容易修改
- 容易写乱
这时候,函数就登场了。
这一章要解决的核心问题是:
如何把一段逻辑封装起来,给它起个名字,然后在需要的时候反复使用。
这也是“代码复用”最基础、最重要的一步。
本章学习目标
学完这一章后,你应该能做到:
- 理解函数是什么,以及为什么程序需要函数。
- 掌握函数的定义和调用。
- 理解参数的作用,知道函数如何接收不同输入。
- 理解返回值的作用,知道函数如何把结果交出来。
- 知道为什么要把重复代码提炼成函数。
- 理解作用域的基础概念,知道函数内外的变量可见范围不同。
- 能写出“计算总价”“格式化姓名”这类简单可复用函数。
一、为什么程序需要函数
函数存在,不是为了把代码写得“更高级”,而是为了帮你解决一个非常实际的问题:
重复逻辑不要每次都重写。
1. 没有函数时,重复代码会怎样
假设你要计算三个商品的总价:
constproductAPrice=99;constproductAQuantity=2;constproductATotal=productAPrice*productAQuantity;console.log(productATotal);constproductBPrice=59;constproductBQuantity=3;constproductBTotal=productBPrice*productBQuantity;console.log(productBTotal);constproductCPrice=199;constproductCQuantity=1;constproductCTotal=productCPrice*productCQuantity;console.log(productCTotal);这段代码当然可以运行,但你会发现几个问题:
- 逻辑几乎一模一样。
- 代码越来越长。
- 如果计算规则变了,要改很多地方。
这就是函数最适合出场的时候。
2. 函数像是“带名字的小工具”
你可以先把函数理解成:
一段被单独封装起来、可以重复使用的代码。
它很像你给一套固定操作做了一个小工具。
例如:
- 计算总价
- 打印欢迎语
- 格式化姓名
只要这套操作未来还会再用,就值得考虑封装成函数。
3. 函数在做两件非常重要的事
1. 复用代码
写一次,多次调用。
2. 让逻辑更容易读
当你看到:
calculateTotalPrice()往往会比直接看到一大段重复计算更容易理解。
二、函数到底是什么
函数可以先用一句最直白的话记住:
函数就是把一段逻辑包起来,然后在需要时执行它。
它通常会包含下面几部分:
- 函数名
- 参数(可选)
- 函数体
- 返回值(可选)
你现在不用一下子把这四个概念都吃透,后面我们会一个个展开。
1. 本章先统一使用function写法
JavaScript 里创建函数有不止一种写法。
但对纯小白来说,这一章我们先统一使用最基础、最直观的一种:
function函数名(){// 代码}这样会更容易把“函数名、参数、返回值、作用域”这些基础概念看清楚。
后面如果需要,再接触其他写法会更稳。
三、最基础的函数定义与调用
先学函数时,最容易混淆的两件事是:
- 定义函数
- 调用函数
这两个不是一回事。
1. 什么叫“定义函数”
定义函数,就是先把这段逻辑准备好,并给它起一个名字。
例如:
functionsayHello(){console.log("你好,欢迎学习函数");}这里你只是把函数准备好了。
2. 什么叫“调用函数”
调用函数,才是真正让这段逻辑执行。
例如:
sayHello();这一行才会让函数里的代码跑起来。
3. 一个完整的最小例子
functionsayHello(){console.log("你好,欢迎学习函数");}sayHello();这段代码会输出:
你好,欢迎学习函数4. 为什么“定义了函数但没调用”是常见问题
很多初学者第一次写函数时,会写出:
functionsayHello(){console.log("你好");}然后发现控制台什么都没有。
原因不是函数坏了,而是:
你只是定义了函数,还没有调用它。
所以一定要先把这层区别分清:
- 定义:把函数准备好
- 调用:让函数执行
四、为什么说函数能复用代码
这是本章的核心价值之一。
1. 一个重复代码的例子
假设你要连续输出三次欢迎语:
console.log("欢迎来到前端学习世界");console.log("欢迎来到前端学习世界");console.log("欢迎来到前端学习世界");这种写法虽然简单,但已经出现重复了。
2. 提炼成函数之后
你可以改成:
functionprintWelcomeMessage(){console.log("欢迎来到前端学习世界");}printWelcomeMessage();printWelcomeMessage();printWelcomeMessage();这样做有两个明显好处:
- 欢迎语只写了一次。
- 以后如果文案要改,只需要改函数内部。
3. 复用不是“偷懒”,而是更好的组织方式
初学阶段有时会觉得:
这几行代码也不长,为什么还要包成函数?
因为函数不只是为了减少敲字,更是为了:
- 明确逻辑边界
- 降低重复
- 让代码更容易维护
这就是“代码复用”的本质价值。
五、参数是什么
到这里你可能会发现一个问题:
刚才的函数虽然可以重复执行,但它每次做的都是完全一样的事。
那如果我想让函数根据不同输入,产生不同结果怎么办?
这就需要参数。
你可以先把参数理解成:
函数接收外部输入的入口。
1. 一个最简单的带参数函数
functionsayHello(userName){console.log(`你好,${userName}`);}这里的userName就是参数。
调用时你可以传入不同值:
sayHello("小白");sayHello("小明");输出结果分别会是:
你好,小白 你好,小明这说明同一个函数,因为输入不同,结果也可以不同。
2. 参数像是函数的“输入口”
如果把函数想成一个小机器,那参数就像输入口。
你给它不同输入,它就按同样规则处理这些输入。
例如:
- 输入不同姓名
- 输出不同欢迎语
3. 多个参数怎么写
函数当然不只可以接收一个参数。
例如:
functionintroduceUser(userName,cityName){console.log(`你好,我是${userName},我来自${cityName}`);}调用时:
introduceUser("小白","北京");introduceUser("小林","上海");4. 参数顺序为什么重要
这一点很值得初学者尽早注意。
例如:
functionintroduceUser(userName,cityName){console.log(`你好,我是${userName},我来自${cityName}`);}如果你调用时写成:
introduceUser("北京","小白");那结果就会变成:
你好,我是北京,我来自小白也就是说:
参数位置通常是有意义的,调用时顺序不能乱。
5. 先记住两个术语就够了
你后面可能会看到:
- 形参
- 实参
现在你可以先做一个非常基础的对应:
1. 写在函数定义里的,是“参数名字”
例如:
functionsayHello(userName){console.log(userName);}这里的userName是定义时写好的参数位置。
2. 调用函数时传进去的,是真正的值
例如:
sayHello("小白");这里的"小白"才是这次真正传进去的内容。
初学阶段你不用被术语绊住,先把“参数位置”和“传入内容”区分开就够了。
六、返回值是什么
到目前为止,你学的很多函数都只是“做一件事,然后在内部打印出来”。
但函数还有一种非常重要的能力:
把处理结果交回函数外面。
这就叫返回值。
1.return的作用是什么
return可以先理解成:
把函数处理出来的结果带出去。
例如:
functionaddNumbers(numberA,numberB){returnnumberA+numberB;}这个函数不是直接把结果打印死在里面,而是把结果返回出去。
2. 调用后怎么拿到返回值
例如:
functionaddNumbers(numberA,numberB){returnnumberA+numberB;}constresult=addNumbers(10,20);console.log(result);这里的执行过程是:
- 调用
addNumbers(10, 20) - 函数内部计算
10 + 20 - 用
return把结果30带出来 - 把这个结果交给
result
3.console.log和return不是一回事
这是初学者特别容易混淆的一点。
例如下面这个函数:
functionaddNumbers(numberA,numberB){console.log(numberA+numberB);}它确实会在控制台里显示结果,但它并没有把结果“交出来”给外部继续使用。
而下面这种写法:
functionaddNumbers(numberA,numberB){returnnumberA+numberB;}才是真正把结果返回出去。
你可以先记一句非常重要的话:
console.log是打印出来给你看,return是把结果交出来继续用。
4. 为什么返回值很重要
因为很多时候,我们不是只想“看一眼结果”,而是还想拿这个结果继续做别的事。
例如:
- 保存到变量里
- 再参与下一步计算
- 再传给另一个函数
这时候返回值就非常关键。
5.return后面的结果通常就作为函数结果
例如:
functiongetWelcomeMessage(userName){return`你好,${userName}`;}constmessage=getWelcomeMessage("小白");console.log(message);这里函数最终交出来的就是:
你好,小白七、函数要尽量只做一件事
这是写函数时一个非常重要的习惯。
尤其对初学者来说,它能帮你少踩很多坑。
你可以先记住:
一个函数尽量只负责一件明确的事。
1. 为什么这样做更清楚
如果一个函数里同时做太多事,就容易变成:
- 一半在计算
- 一半在判断
- 一半在打印
- 一半在改变量
最后自己也很难看懂这个函数到底在负责什么。
2. 一个“不太清楚”的例子
例如:
functionhandleOrder(price,quantity){consttotalPrice=price*quantity;console.log(`总价是:${totalPrice}`);if(totalPrice>200){console.log("超过 200 元");}else{console.log("未超过 200 元");}}这个函数能不能运行?
能。
但它同时在做:
- 计算总价
- 输出总价
- 判断预算
- 输出判断结果
对于当前阶段来说,这已经有点挤在一起了。
3. 一个更清楚的拆法
你完全可以先拆成职责更单纯的函数。
例如:
functioncalculateTotalPrice(price,quantity){returnprice*quantity;}functionisOverBudget(totalPrice){returntotalPrice>200;}这样会更容易理解:
- 一个函数负责算总价
- 一个函数负责判断是否超预算
这就是“函数尽量只做一件事”的价值。
八、函数名应该怎么取
函数名非常重要,因为函数名本身就在告诉别人:
这段逻辑是做什么的。
1. 函数名尽量表达动作和意图
推荐这种风格:
calculateTotalPrice formatUserName printWelcomeMessage isOverBudget这些名字的共同特点是:
- 看名字就大概知道它在干什么
- 通常像一个动作或一个判断
2. 不推荐太随意的函数名
例如:
doIt test aaa fn1这些名字虽然浏览器能识别,但对读代码的人帮助很小。
3. 为什么函数名特别值得认真取
因为当项目变大后,别人往往先看到的不是函数内部,而是函数名。
如果名字取得好,很多代码会在还没展开细节前就已经更容易读懂。
九、什么是作用域
作用域是函数学习里一个必须接触的基础概念。
你可以先把它理解成:
一个变量在什么范围内可以被访问。
也就是说,不是所有变量都能在所有地方使用。
1. 全局位置定义的变量
例如:
constsiteName="从零开始学前端";functionprintSiteName(){console.log(siteName);}printSiteName();这里的siteName是定义在函数外面的。
当前阶段你可以先把它理解成:
它在外层,所以函数内部也能用到它。
2. 函数内部定义的变量
例如:
functionprintChapterInfo(){constchapterName="函数与代码复用";console.log(chapterName);}printChapterInfo();这里的chapterName是定义在函数内部的变量。
它可以在这个函数内部被使用。
3. 为什么函数内部变量不能随便拿到外面用
如果你写:
functionprintChapterInfo(){constchapterName="函数与代码复用";console.log(chapterName);}printChapterInfo();console.log(chapterName);最后一行通常会报错。
因为:
chapterName只在这个函数内部有效。
这就是作用域在起作用。
4. 为什么作用域很重要
因为它能帮助程序把不同位置的数据边界分清楚。
如果所有变量都能在所有地方随便改,代码会很容易混乱。
所以初学阶段你先记住一个非常重要的基础规则:
函数内部定义的变量,通常只在函数内部使用。
十、一个完整示例:封装“计算总价”函数
下面我们做一个非常贴近前面练习内容的完整示例。
目标是把“总价计算”封装成可复用函数。
1. 示例代码
functioncalculateTotalPrice(unitPrice,quantity,shippingFee){// 先计算商品总价,再加上运费,最后把结果返回出去constproductTotalPrice=unitPrice*quantity;returnproductTotalPrice+shippingFee;}constorderATotalPrice=calculateTotalPrice(99,2,10);constorderBTotalPrice=calculateTotalPrice(59,3,12);console.log(`订单 A 总价:${orderATotalPrice}`);console.log(`订单 B 总价:${orderBTotalPrice}`);2. 这个函数解决了什么问题
它把原本可能重复写很多次的逻辑:
- 单价乘数量
- 再加上运费
统一封装到了一个函数里。
以后只要计算规则不变,你就可以一直复用它。
3. 怎么逐步读懂这段代码
你可以按下面顺序读:
1. 先看函数接收什么输入
- 单价
unitPrice - 数量
quantity - 运费
shippingFee
2. 再看函数内部怎么处理
先算:
unitPrice*quantity得到商品总价。
再加:
+shippingFee得到最终总价。
3. 最后看函数把什么交出来
它通过return把最终总价返回了出去。
所以外部可以这样接:
constorderATotalPrice=calculateTotalPrice(99,2,10);十一、一个完整示例:封装“格式化姓名”函数
再来看一个更偏文本处理的例子。
目标是把姓名组合逻辑提炼成函数。
1. 示例代码
functionformatFullName(lastName,firstName){return`${lastName}${firstName}`;}constuserAFullName=formatFullName("王","小白");constuserBFullName=formatFullName("李","小林");console.log(userAFullName);console.log(userBFullName);2. 这个例子在练什么
它虽然简单,但已经同时练到了:
- 多个参数
- 返回值
- 模板字符串
- 代码复用
3. 为什么这种小函数很值得练
因为函数学习初期,最适合从这种“小而明确”的任务开始。
例如:
- 组合姓名
- 计算总价
- 判断是否及格
- 生成欢迎语
这些任务边界清楚,很适合帮助你建立对函数的直觉。
十二、函数和前面学过的知识是怎么连起来的
走到这里,你会发现函数并不是一个完全新的孤立知识点。
它其实是在把前面学过的内容组织起来。
1. 函数里会继续使用变量
例如:
- 参数本身就是变量位置
- 函数内部也可以定义变量
2. 函数里会继续处理数据类型
例如:
- 数字做计算
- 字符串做拼接
- 布尔值做判断
3. 函数里会继续用到条件判断和循环
例如你后面完全可以写:
- 判断成绩等级的函数
- 输出固定规律的函数
所以函数不是要替代前面知识,而是在帮你把前面知识封装起来、重复利用。
十三、初学者最容易踩的坑
这一章的坑也很典型,而且很多人会反复遇到。
1. 定义了函数,但忘了调用
只写:
functionsayHello(){console.log("你好");}是不会自动输出内容的。
你还要真正调用:
sayHello();2. 以为console.log就等于返回值
再强调一次:
console.log是打印return是返回结果
如果你后面还要继续使用某个结果,通常更应该考虑return。
3. 参数名和值混在一起理解
例如:
functionsayHello(userName){console.log(`你好,${userName}`);}这里的userName是参数位置,不是固定只能等于某一个人。
真正传进去的值,是调用时写的内容:
sayHello("小白");4. 一个函数里塞太多事情
这是初学者很容易出现的问题。
刚学函数时,很容易觉得:
既然都写成函数了,那干脆把一堆事都塞进去。
但这样会让函数越来越难懂。
当前阶段更推荐你先做到:
一个函数尽量只负责一件明确的事。
5. 在函数外面使用函数内部变量
比如:
functionprintChapterInfo(){constchapterName="函数";}console.log(chapterName);这通常会报错。
因为这个变量只在函数内部有效。
6. 函数名没有表达动作
例如:
functiontest(){}functiondoIt(){}这种名字很难帮助人理解逻辑。
更推荐:
functioncalculateTotalPrice(){}functionformatFullName(){}十四、本章实践练习
这一章最适合通过“小函数练习”建立感觉。
重点不是一次写很复杂,而是先把函数的边界想清楚。
1. 练习 1:封装“计算总价”函数
请你自己写一个函数,至少接收:
- 单价
- 数量
- 运费
然后返回最终总价。
你可以先自己写,再检查是否满足:
- 有函数名
- 有参数
- 有返回值
2. 练习 2:封装“格式化姓名”函数
请写一个函数,接收:
- 姓
- 名
然后返回完整姓名。
例如:
formatFullName("王","小白");返回:
王小白3. 练习 3:把重复代码提炼成函数
请回头看看你前面写过的 JavaScript 小练习,找一找有没有这种情况:
- 同一段输出写了很多次
- 同一套计算逻辑写了很多次
- 同一种格式化操作写了很多次
然后尝试把其中一段提炼成函数。
这个练习特别重要,因为它最接近“真实代码重构”的第一步。
4. 练习 4:写几个不同输入、不同输出的小函数
你可以尝试写下面这些非常小的函数:
1.getDoubleNumber(number)
接收一个数字,返回它的两倍。
2.getWelcomeMessage(userName)
接收用户名,返回欢迎语。
3.isPass(score)
接收成绩,返回它是否大于等于 60。
这些函数都很短,但非常适合练习:
- 参数
- 返回值
- 命名
- 单一职责
十五、学习重点提示
这一章最重要的,不是把函数语法硬背下来,而是真正理解:
为什么一段逻辑值得被单独封装成函数。
请重点记住:
- 函数是把一段逻辑封装起来,在需要时重复使用。
- 定义函数和调用函数不是一回事。
- 参数是函数接收输入的入口。
- 返回值是函数把处理结果交回外部的方式。
console.log是打印结果,return是返回结果。- 函数尽量只做一件事,这样更容易理解和复用。
- 函数名尽量表达动作和意图。
- 函数内部变量通常只在函数内部有效,这和作用域有关。
如果你只记一句话,请记住:
函数的价值,不只是“少写几行代码”,而是把逻辑组织得更清楚、让重复工作真正可以复用。
十六、本章小结
这一章,你已经从“会写一段逻辑”走到了“会把一段逻辑封装起来反复使用”。
你已经学习了:
- 函数是什么
- 为什么要封装函数
- 函数的定义与调用
- 参数和返回值的作用
- 函数命名与单一职责
- 作用域的基本概念
- 如何把重复代码提炼成可复用函数
这一步非常关键。
因为从这一章开始,你的代码不再只是“能跑的一段段片段”,而是开始慢慢具备更清楚的结构。
后面当你继续学习数组、对象、DOM、事件和更复杂的交互逻辑时,函数几乎会一直陪着你。
可以说:
函数是 JavaScript 中最重要的组织工具之一。
十七、课后思考题
请你思考下面几个问题:
- 为什么函数特别适合解决“重复逻辑”问题?
- 为什么说定义函数和调用函数不是一回事?
- 参数和返回值分别在函数中扮演什么角色?
- 为什么
console.log和return不能混为一谈? - 为什么“一个函数尽量只做一件事”会让代码更清楚?
- 为什么函数内部变量不能随便拿到函数外面使用?
建议你用自己的话把答案写下来。只要你能把这些问题说清楚,说明你已经真正开始掌握“函数思维”了。
十八、下一章预告
下一章我们会继续学习:
从零开始学前端 | 第十五章:数组与对象
到那时,你会开始接触如何组织“多个值”和“带结构的数据”,这会让你的 JavaScript 从处理单个变量,进一步走向处理更真实的数据世界。