Hi!这里是 JustHappy 这是专为编程初学者准备的专栏,哈哈,AI 时代更需要我们返璞归真!我们继续秘籍修炼之旅吧,这期我们来聊聊函数这个概念,函数不是语法糖,是程序员对“重复劳动”的反击:把一串动作起个名字,复杂藏起来,一句调用就能反复用。参数让同一函数适配不同人,返回值把结果带出来,回调/高阶函数把规则也能传递。记住:重复三次,就该抽函数。
上一篇我们讲了模块化。
模块化解决的是:代码放哪里、怎么分组、怎么别乱放。
但还有一个更致命的问题:同一件事你要写多少遍?
重复劳动这件事,写着写着就会失控
假设你在做一个网站,需要给用户发欢迎消息。
最开始只有一个用户,你可能就这么写:
console.log('欢迎张三')
后来来了第二个:
console.log('欢迎张三')
console.log('欢迎李四')
第三个:
console.log('欢迎张三')
console.log('欢迎李四')
console.log('欢迎王五')
那如果有一万个用户呢?
难道你真打算写一万行 console.log?
这时候你会很自然地产生一个想法:
把重复的动作取个名字,以后只叫名字就行。
function welcome() {
console.log('欢迎用户')
}
以后要欢迎谁,你就“重复调用”,而不是“重复粘贴”:
welcome()
welcome()
welcome()
函数最早、最朴素的意义就是这个:反复做的事,别反复写。
函数不是语法,它是在帮你“止损”
教材会说:函数是可重复调用的代码块。
没错,但太表面了。
函数真正解决的问题是:别让人类重复劳动。
因为机器最擅长重复。
而人最不擅长重复(尤其是“改十个地方,漏一个地方”的那种重复)。
你想象一下:欢迎语现在要改成更正式一点,比如从“欢迎张三”改成“欢迎你,张三同学”。
- 你如果写了一万行,那你就要改一万行
- 你如果抽了函数,那你改一行就够了
所以函数看起来像在“偷懒”,其实是在给未来的自己减刑。
函数本质上是在封装动作:给一串步骤起名字
你平时看到的登录可能就一句:
login()
但它背后通常是一串步骤(而且还会越长越长):
- 校验账号密码
- 请求服务器
- 生成或拿到 token
- 保存登录状态
- 跳转首页 / 拉取用户信息
这些细节,调用者根本不想知道。
调用者只关心两件事:
- 我能不能用(接口怎么调)
- 能不能成(结果是什么 / 出错怎么提示)
所以函数的本质可以粗暴总结成一句话:
给一组动作起名字,把复杂藏起来。
软件工程一直在做同一件事:给复杂的东西起名字
随着项目变大,程序员每天都在干一件事:
把一坨复杂逻辑,起个更像人话的名字。
于是你会看到一堆“看起来很简单”的调用:
login()
logout()
createOrder()
payOrder()
sendMessage()
这些名字背后,可能是几百行、几千行。
但对调用者来说,最好是这样的体验:
我只关心“做什么”,不关心“怎么做”。
这其实就是软件工程一个很核心的思路:
关注接口,隐藏实现。
函数为什么会越来越复杂?因为需求越来越不客气
最开始函数只能做“固定动作”,比如 welcome()。
但很快你就会发现:张三要欢迎,李四也要欢迎。
你当然不想写 welcomeZhangSan()、welcomeLiSi() 这种东西。
于是出现了参数:把“变化的部分”交给调用者传进来。
function welcome(name) {
console.log(`欢迎 ${name}`)
}
welcome('张三')
welcome('李四')
再后来,很多函数不只是执行动作,还要把结果交出来:
function sum(a, b) {
return a + b
}
const total = sum(10, 20)
然后你又会碰到更真实的场景:比如请求接口。
request('/user')
请求完成以后怎么办?
- 有的页面要渲染用户信息
- 有的页面要跳转
- 有的页面要缓存起来下次用
不同场景不同处理方式,于是你开始把“未来要做的事”也当成参数传进去——这就是回调。
request('/user', (data) => {
renderUser(data)
})
再再后来,你会发现:数据能传,逻辑也能传。
于是你开始把“规则”传给函数——这就是高阶函数(函数接收函数)。
users.filter((u) => u.age > 18)
你还会发现:有些配置长期不变,有些参数经常变化。
比如域名不变、路径变;或者“请求方式/默认头”不变、“具体接口”变。
于是你会喜欢先“配好”,再“执行”——这就是柯里化那类思路(先固定一部分参数)。
const requestWithBase = (baseUrl) => (path) => fetch(baseUrl + path)
const api = requestWithBase('https://api.example.com')
api('/user')
再后来你又发现:函数执行完就结束了,但我想让它记住一些状态,比如次数、缓存、用户信息……
于是闭包出现了(让行为携带状态):
function createCounter() {
let count = 0
return () => ++count
}
const next = createCounter()
next() // 1
next() // 2
再往后,项目大了、bug 多了、同事也多了,你开始追求:
同样输入,永远同样输出(更好测、更好推导)——这就是纯函数那套味道。
最后你又会回到一个朴素结论:一个函数只做一件事最舒服。
于是你把 format()、validate()、save() 这些小函数组合起来:
const submit = (data) => save(validate(format(data)))
你看,函数越学越多,其实不是函数变花哨了,是需求在变难。
函数只是一路把你带到“软件工程”的门口。
你以为你在学函数,其实你在学抽象
很多人以为函数就是:
function add(a, b) {
return a + b
}
但真实情况是:函数是软件工程里最基础的抽象单位。
后面你接触到的参数、返回值、回调、高阶函数、闭包、纯函数、组合……
几乎都建立在函数之上。
甚至你以为很“高级”的那些东西:设计模式、框架、架构思想……
很多都能追溯到这里:怎么把复杂变简单,怎么把变化关进笼子里。
记住一句话就够了
函数从来不是为了让代码显得高级。
它存在的理由只有一个:让程序员少写重复劳动。
你可以把它当成一个写代码的“警报器”:
- 重复一次:还能忍
- 重复两次:开始不爽
- 重复三次:差不多该抽函数了
因为机器负责重复,人负责思考。
下一篇我们聊参数:为什么同一个函数能处理无数情况。