函数就是一组有名字的步骤
每当你想给一段逻辑起个名字并重复使用时,就要写一个函数。JavaScript 中最古老、最直接的写法是函数声明:function 关键字后面跟一个名字。
从左往右读:
function是开始声明的关键字。greet是函数名。(name)是参数列表,也就是函数接收的输入。- 花括号里的代码块是函数体。
greet("Ada")是一次调用。JavaScript 会在name绑定为"Ada"的情况下执行函数体。
定义函数不会运行它,调用才会。
形参与实参
形参(parameter)是函数定义中的名字,实参(argument)是调用时传入的值。日常交流中两者常被混用,但读错误信息时,这个区别就很重要了。
这里 base 和 exponent 是形参,2 和 10 是实参。JavaScript 按顺序绑定它们:第一个实参对应第一个形参,依此类推。
和某些语言不同,传入的实参数量不对时,JavaScript 不会报错。缺少的会变成 undefined,多出来的会被悄悄忽略。这很灵活,偶尔也是个陷阱;后面的文档会讲默认参数和剩余参数。
return 把值送回去
console.log 是把内容写到输出。return 则把一个值交还给调用者,让调用者可以使用它:
没有 return 的函数会返回 undefined:
return 还会立即退出函数。在主逻辑之前用提前返回处理边界情况,是标准做法:
提前返回可以让函数主体不会变成层层嵌套的 if/else 金字塔。
提升:先调用,后定义
这一点真的会让从其他语言过来的人吃惊。函数声明会被提升:JavaScript 在运行任何代码之前,就把它们移到所在作用域的顶部。这意味着你可以在函数声明之前的行调用它:
这段代码运行正常。在执行之前,引擎会扫描作用域,把 square 注册为一个函数,然后才从上到下执行语句。
这是函数声明与其他创建函数的方式(函数表达式、箭头函数)之间真实存在的行为差异。后者像变量一样被提升:名字是已知的,但要等到那一行运行时才会赋值。下一节会详细讲这个区别。
不过大多数代码风格指南仍然建议先定义函数再调用。提升是一张安全网,而不是值得依赖的写法。
声明与表达式
函数声明作为一条独立的语句存在。函数表达式则出现在需要一个值的地方,最常见的是赋值语句的右侧:
两者都会产生可调用的函数。区别在于:
- 声明会被完整提升。表达式遵循它所赋值的变量的提升规则(
const和let在该行运行之前处于暂时性死区)。 - 声明必须有名字。表达式可以是匿名的,不过给它命名有助于阅读堆栈跟踪。
- 声明不能随处出现。在严格模式下,
if代码块里的function foo() {}在不同引擎中的表现不一致,那种地方应该改用表达式。
对于文件中大多数顶层的辅助函数,用声明读起来很自然。对于作为参数传递或赋值给属性的函数,通常会选择表达式(或箭头函数)。
命名与风格
函数名是对函数功能的承诺。多花几个字符起一个有描述性的名字,在别人第一次读代码时就能收回成本。
几个值得采用的约定:
- 函数名应该是动词:
fetchProfile、computeTotal、sendEmail。 - 使用
camelCase,这是 JavaScript 的惯例。 - 返回布尔值的函数通常以
is、has或can开头:isValid、hasAccess、canEdit。
目标是让调用处读起来像一句话。
一个可运行的小例子
把这些部分组合起来:参数、提前返回、有描述性的名字:
一个声明,一个守卫子句,一个清晰的 return。你写的大多数函数都是这个结构。
下一步:箭头函数
函数声明是主力,但现代 JavaScript 大量使用一种更简短的语法:箭头函数,尤其是在回调和单行函数中。它们看起来不一样,在 this 上的行为也不一样,这就是下一页的内容。
常见问题
JavaScript 中如何声明函数?
使用 function 关键字,后面跟函数名、放参数的括号和一个代码块。例如:function greet(name) { return 'Hi, ' + name; }。调用时写出函数名再加括号:greet('Ada')。
JavaScript 中的函数提升是什么?
在代码运行之前,函数声明会被移到所在作用域的顶部,所以你可以在函数定义之前的行调用它。这只适用于 function 声明;赋值给 let 或 const 的函数表达式和箭头函数不会以同样的方式被提升。
函数声明和函数表达式有什么区别?
声明作为一条独立的语句存在(function greet() {}),并且会被提升。表达式把函数赋值给一个变量(const greet = function() {}),遵循普通变量的提升规则。声明按定义必须有名字;表达式则常常是匿名的。