从零开始学前端 | 第十四章:函数与代码复用
2026/7/20 22:12:56 网站建设 项目流程

本章定位

前面几章里,你已经开始真正写“有逻辑”的 JavaScript 代码了。

你已经学习了:

  1. JavaScript 是什么,代码在哪里运行。
  2. 变量、数据类型与运算。
  3. 条件判断与循环。

到这里,你已经能写出一些会:

  • 存值
  • 判断
  • 计算
  • 重复执行

的代码。

但这时候通常会出现一个新的问题:

同一段逻辑,我好像要写很多次。

例如:

  • 计算多个商品的总价
  • 多次输出欢迎语
  • 多次格式化同一类文本
  • 多个地方都需要做相似判断

如果每次都重新从头写一遍,代码很快就会变得:

  • 重复很多
  • 不容易修改
  • 容易写乱

这时候,函数就登场了。

这一章要解决的核心问题是:

如何把一段逻辑封装起来,给它起个名字,然后在需要的时候反复使用。

这也是“代码复用”最基础、最重要的一步。

本章学习目标

学完这一章后,你应该能做到:

  1. 理解函数是什么,以及为什么程序需要函数。
  2. 掌握函数的定义和调用。
  3. 理解参数的作用,知道函数如何接收不同输入。
  4. 理解返回值的作用,知道函数如何把结果交出来。
  5. 知道为什么要把重复代码提炼成函数。
  6. 理解作用域的基础概念,知道函数内外的变量可见范围不同。
  7. 能写出“计算总价”“格式化姓名”这类简单可复用函数。

一、为什么程序需要函数

函数存在,不是为了把代码写得“更高级”,而是为了帮你解决一个非常实际的问题:

重复逻辑不要每次都重写。

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);

这段代码当然可以运行,但你会发现几个问题:

  1. 逻辑几乎一模一样。
  2. 代码越来越长。
  3. 如果计算规则变了,要改很多地方。

这就是函数最适合出场的时候。

2. 函数像是“带名字的小工具”

你可以先把函数理解成:

一段被单独封装起来、可以重复使用的代码。

它很像你给一套固定操作做了一个小工具。

例如:

  • 计算总价
  • 打印欢迎语
  • 格式化姓名

只要这套操作未来还会再用,就值得考虑封装成函数。

3. 函数在做两件非常重要的事

1. 复用代码

写一次,多次调用。

2. 让逻辑更容易读

当你看到:

calculateTotalPrice()

往往会比直接看到一大段重复计算更容易理解。

二、函数到底是什么

函数可以先用一句最直白的话记住:

函数就是把一段逻辑包起来,然后在需要时执行它。

它通常会包含下面几部分:

  1. 函数名
  2. 参数(可选)
  3. 函数体
  4. 返回值(可选)

你现在不用一下子把这四个概念都吃透,后面我们会一个个展开。

1. 本章先统一使用function写法

JavaScript 里创建函数有不止一种写法。

但对纯小白来说,这一章我们先统一使用最基础、最直观的一种:

function函数名(){// 代码}

这样会更容易把“函数名、参数、返回值、作用域”这些基础概念看清楚。

后面如果需要,再接触其他写法会更稳。

三、最基础的函数定义与调用

先学函数时,最容易混淆的两件事是:

  1. 定义函数
  2. 调用函数

这两个不是一回事。

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();

这样做有两个明显好处:

  1. 欢迎语只写了一次。
  2. 以后如果文案要改,只需要改函数内部。

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);

这里的执行过程是:

  1. 调用addNumbers(10, 20)
  2. 函数内部计算10 + 20
  3. return把结果30带出来
  4. 把这个结果交给result

3.console.logreturn不是一回事

这是初学者特别容易混淆的一点。

例如下面这个函数:

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 元");}}

这个函数能不能运行?

能。

但它同时在做:

  1. 计算总价
  2. 输出总价
  3. 判断预算
  4. 输出判断结果

对于当前阶段来说,这已经有点挤在一起了。

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. 这个例子在练什么

它虽然简单,但已经同时练到了:

  1. 多个参数
  2. 返回值
  3. 模板字符串
  4. 代码复用

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:封装“计算总价”函数

请你自己写一个函数,至少接收:

  • 单价
  • 数量
  • 运费

然后返回最终总价。

你可以先自己写,再检查是否满足:

  1. 有函数名
  2. 有参数
  3. 有返回值

2. 练习 2:封装“格式化姓名”函数

请写一个函数,接收:

然后返回完整姓名。

例如:

formatFullName("王","小白");

返回:

王小白

3. 练习 3:把重复代码提炼成函数

请回头看看你前面写过的 JavaScript 小练习,找一找有没有这种情况:

  • 同一段输出写了很多次
  • 同一套计算逻辑写了很多次
  • 同一种格式化操作写了很多次

然后尝试把其中一段提炼成函数。

这个练习特别重要,因为它最接近“真实代码重构”的第一步。

4. 练习 4:写几个不同输入、不同输出的小函数

你可以尝试写下面这些非常小的函数:

1.getDoubleNumber(number)

接收一个数字,返回它的两倍。

2.getWelcomeMessage(userName)

接收用户名,返回欢迎语。

3.isPass(score)

接收成绩,返回它是否大于等于 60。

这些函数都很短,但非常适合练习:

  • 参数
  • 返回值
  • 命名
  • 单一职责

十五、学习重点提示

这一章最重要的,不是把函数语法硬背下来,而是真正理解:

为什么一段逻辑值得被单独封装成函数。

请重点记住:

  1. 函数是把一段逻辑封装起来,在需要时重复使用。
  2. 定义函数和调用函数不是一回事。
  3. 参数是函数接收输入的入口。
  4. 返回值是函数把处理结果交回外部的方式。
  5. console.log是打印结果,return是返回结果。
  6. 函数尽量只做一件事,这样更容易理解和复用。
  7. 函数名尽量表达动作和意图。
  8. 函数内部变量通常只在函数内部有效,这和作用域有关。

如果你只记一句话,请记住:

函数的价值,不只是“少写几行代码”,而是把逻辑组织得更清楚、让重复工作真正可以复用。

十六、本章小结

这一章,你已经从“会写一段逻辑”走到了“会把一段逻辑封装起来反复使用”。

你已经学习了:

  • 函数是什么
  • 为什么要封装函数
  • 函数的定义与调用
  • 参数和返回值的作用
  • 函数命名与单一职责
  • 作用域的基本概念
  • 如何把重复代码提炼成可复用函数

这一步非常关键。

因为从这一章开始,你的代码不再只是“能跑的一段段片段”,而是开始慢慢具备更清楚的结构。

后面当你继续学习数组、对象、DOM、事件和更复杂的交互逻辑时,函数几乎会一直陪着你。

可以说:

函数是 JavaScript 中最重要的组织工具之一。

十七、课后思考题

请你思考下面几个问题:

  1. 为什么函数特别适合解决“重复逻辑”问题?
  2. 为什么说定义函数和调用函数不是一回事?
  3. 参数和返回值分别在函数中扮演什么角色?
  4. 为什么console.logreturn不能混为一谈?
  5. 为什么“一个函数尽量只做一件事”会让代码更清楚?
  6. 为什么函数内部变量不能随便拿到函数外面使用?

建议你用自己的话把答案写下来。只要你能把这些问题说清楚,说明你已经真正开始掌握“函数思维”了。

十八、下一章预告

下一章我们会继续学习:

从零开始学前端 | 第十五章:数组与对象

到那时,你会开始接触如何组织“多个值”和“带结构的数据”,这会让你的 JavaScript 从处理单个变量,进一步走向处理更真实的数据世界。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询