[程序设计] JavaScript | 打造自己的工具盒——一文读懂JavaScript的函数特性
打造自己的工具盒——一文读懂JavaScript的函数特性
JavaScript 中的函数就像一个"工具盒",你可以把一段常用的代码逻辑装进去,需要的时候直接"调用"它来工作。函数让代码更整洁、可复用,是 包括JS在内的任意编程语言中非常核心的概念。下面我们逐个解析JS函数的关键知识点:

一、函数的定义:怎么创建一个函数?
创建函数主要有两种常用方式:函数声明和函数表达式。
1. 函数声明(最常用)
用 function 关键字开头,明确写出函数名,语法:
// 定义函数(形参:a, b 是"形式参数",只是占位符)
function 函数名(形参1, 形参2) {
// 函数体:要执行的代码
return 结果; // 可选,返回计算结果
}
// 例子:定义一个加法函数
function add(a, b) {
return a + b;
}
// 调用函数(实参:1, 2 是"实际参数",真正参与计算的值)
let result = add(1, 2);
console.log(result); // 输出 3
特点:函数声明会被"提升"(提前加载),可以在定义前调用:
// 可以在定义前调用
console.log(add(1,2)); // 3
function add(a, b) {
return a + b;
}
2. 函数表达式
把函数赋值给一个变量,函数名可选(通常省略),语法:
// 把函数赋值给变量(函数名可省略)
let 变量名 = function(形参) {
// 函数体
};
// 例子:用表达式定义加法函数
let add = function(a, b) {
return a + b;
};
// 调用方式和声明式一样
console.log(add(1,2)); // 3
特点:不会被提升,必须在定义后才能调用:
console.log(add(1,2)); // 报错:add is not defined(因为还没定义)
let add = function(a, b) {
return a + b;
};
二、函数的参数:函数的"输入"
参数是函数的"原材料",分为形参(定义时写的)和实参(调用时传的)。JS 对参数的限制很灵活,比如参数数量可以不匹配。
1. 参数数量不匹配时
- 实参多了:多余的实参会被忽略
- 实参少了:缺少的形参会被赋值为
undefined
function add(a, b) {
return a + b;
}
console.log(add(1)); // NaN(因为 b 是 undefined,1 + undefined = NaN)
console.log(add(1, 2, 3)); // 3(多余的 3 被忽略)
2. 默认参数(ES6 特性)
可以给形参设置默认值,当实参没传时自动使用默认值:
// 给 b 设置默认值 0
function add(a, b = 0) {
return a + b;
}
console.log(add(1)); // 1(b 没传,用默认值 0,1+0=1)
console.log(add(1, 2)); // 3(传了实参,用实参 2)
3. 剩余参数(… 语法,ES6 特性)
当不确定要传多少个参数时,用 ...变量名 接收所有实参(返回一个数组):
// 计算任意多个数字的和
function sum(...nums) {
// nums 是一个数组,包含所有传入的实参
let total = 0;
for (let num of nums) {
total += num;
}
return total;
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4)); // 10
4.arguments
连剩余参数都省略了。
// 普通函数:定义时没指定参数,但可以通过 arguments 获取所有实参
function sum() {
let total = 0;
// arguments 包含所有传入的实参,遍历求和
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2)); // 3(arguments 是 [1,2])
console.log(sum(1, 2, 3, 4)); // 10(arguments 是 [1,2,3,4])
三、匿名函数:没有名字的函数
匿名函数就是没有名字的函数,通常作为"临时工具"使用,比如赋值给变量、作为参数传递。
// 1. 赋值给变量(就是上面讲的"函数表达式")
let greet = function() {
console.log("Hello!");
};
greet(); // 输出 Hello!
// 2. 作为参数传递(比如定时器)
setTimeout(function() {
console.log("1秒后执行");
}, 1000); // 1秒后打印 "1秒后执行"
匿名函数的好处:用完即弃,不需要为临时逻辑单独起名字。
四、箭头函数(=>,ES6 特性)
箭头函数是匿名函数的"简化写法",用 => 代替 function,语法更简洁。
基本语法
// 普通匿名函数
let add = function(a, b) {
return a + b;
};
// 箭头函数简化(去掉 function,加 =>)
let add = (a, b) => {
return a + b;
};
更简洁的写法
- 如果只有一个形参,可省略括号
() - 如果函数体只有一句
return,可省略{}和return
// 1. 单个参数
let double = function(num) {
return num * 2;
};
// 简化为
let double = num => num * 2;
// 2. 多个参数(必须保留括号)
let add = (a, b) => a + b;
// 3. 无参数(必须保留空括号)
let greet = () => console.log("Hello");
箭头函数 vs 普通函数(关键区别)
-
this 指向不同:
普通函数的this指向调用它的对象;
箭头函数的this继承自外层作用域(不会变)。// 普通函数 let person = { name: "小明", sayHi: function() { console.log(this.name); // this 指向 person,输出 "小明" } }; // 箭头函数(这里会有问题) let person2 = { name: "小红", sayHi: () => { console.log(this.name); // this 继承外层(全局),全局没有 name,输出 undefined } };
关于this指向问题,可参考这篇博文。
- 箭头函数不能作为构造函数(不能用
new调用)。 - 箭头函数没有
arguments对象(可用剩余参数代替)。
五、函数作为参数:函数也能当"数据"传递
在 JS 中,函数和数字、字符串一样,可以作为参数传给另一个函数(这种函数叫"高阶函数")。
例子1:自定义高阶函数
// 高阶函数:接收一个函数作为参数
function calculate(a, b, operation) {
// 调用传入的函数,传入 a 和 b
return operation(a, b);
}
// 定义两个普通函数
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
// 把函数作为参数传给 calculate
console.log(calculate(2, 3, add)); // 5(执行 2+3)
console.log(calculate(2, 3, multiply)); // 6(执行 2*3)
例子2:数组方法中的回调函数
数组的 map、filter 等方法,本质就是接收一个函数作为参数:
let numbers = [1, 2, 3, 4];
// map 接收一个函数,对每个元素处理
let doubled = numbers.map(function(num) {
return num * 2;
});
// 用箭头函数更简洁
let doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]
六、扩展阅读:编程语言中的“一等公民”
在这里,我们说JavaScript是一种支持函数作为一等公民的编程语言。
“一等公民”是计算机科学中的术语,核心指某种实体(比如函数、数字、字符串)在编程语言中拥有与其他基本数据类型完全同等的权利和操作能力,没有使用上的限制。
简单说,只要一个东西能像数字(比如 5)或字符串(比如 "hello")一样用,它就是“一等公民”。
函数作为“一等公民”的核心特征
判断函数是否是“一等公民”,主要看它是否满足以下4个关键能力,这些能力完全对标基本数据类型(如数字):
-
可赋值给变量
函数能像数字一样直接赋值给变量,变量可以直接代表这个函数。
示例(Python):# 定义函数 def add(a, b): return a + b # 把函数赋值给变量func,此时func和add完全等价 func = add # 通过变量调用函数,结果为3 print(func(1, 2)) -
可作为参数传递给其他函数
函数能像数字、字符串一样,作为参数传入另一个函数(这就是之前讨论的“高阶函数”的基础)。
示例(JavaScript):// 定义一个“处理函数”,接收另一个函数作为参数 function process(num, func) { return func(num); // 调用传入的函数 } // 把匿名函数作为参数传入,结果为10 console.log(process(5, x => x * 2)); -
可作为函数的返回值
函数能像数字一样,被另一个函数“生产”并返回,形成“函数嵌套”的灵活结构。
示例(Python):# 定义一个函数,返回另一个函数 def make_adder(n): # 内部函数,使用外部函数的参数n def adder(x): return x + n return adder # 返回内部函数 # 生成一个“加3”的函数 add3 = make_adder(3) # 调用生成的函数,结果为5 print(add3(2)) -
可存储在数据结构中
函数能像数字、字符串一样,存入列表、字典等数据结构中,方便统一管理和调用。
示例(JavaScript):// 把多个函数存入数组 const operations = [ x => x + 1, // 加1 x => x * 2, // 乘2 x => x - 3 // 减3 ]; // 遍历数组调用函数,依次输出2、2、-2 operations.forEach(func => console.log(func(1)));
为什么“一等公民”很重要?
它是函数式编程的核心基础,能让代码更灵活、更简洁:
- 支持“回调函数”:比如异步操作(如网络请求)完成后,自动调用传入的函数。
- 支持“高阶函数”:像
map()、filter()这类通用工具函数,本质就是接收函数作为参数,实现代码复用。 - 减少冗余代码:通过“动态生成函数”(如上述
make_adder),避免重复写相似逻辑。
对比:非一等公民的局限
有些语言早期不支持函数作为“一等公民”,比如 Java 8 之前:
- 函数不能直接赋值给变量,必须包裹在类(如匿名内部类)中。
- 不能直接作为参数传递,需要先定义接口,再通过实现类传递逻辑。
- 代码会变得冗长,直到 Java 8 引入 Lambda 表达式,才让函数具备了“一等公民”的核心能力。
以下是不同编程语言中“函数作为一等公民”的支持情况对比表,涵盖核心特性、支持版本及语法示例:
| 编程语言 | 支持版本/起始版本 | 核心语法/特性 | 关键能力示例(赋值/传参/返回/存储) |
|---|---|---|---|
| Python | 全版本(原生支持) | 函数直接赋值给变量、lambda 匿名函数 |
python<br># 1. 赋值给变量<br>def add(a, b): return a + b<br>func = add<br><br># 2. 作为参数传递<br>list(map(lambda x: x*2, [1,2,3])) # [2,4,6]<br><br># 3. 作为返回值<br>def make_func(n):<br> return lambda x: x + n<br>add3 = make_func(3)<br><br># 4. 存储在列表<br>funcs = [add, lambda a,b: a*b]<br> |
| JavaScript | 全版本(原生支持) | 函数表达式、() => {} 箭头函数 |
javascript<br>// 1. 赋值给变量<br>const add = (a, b) => a + b;<br><br>// 2. 作为参数传递<br>[1,2,3].map(x => x*2); // [2,4,6]<br><br>// 3. 作为返回值<br>const makeFunc = n => x => x + n;<br>const add3 = makeFunc(3);<br><br>// 4. 存储在数组<br>const funcs = [add, (a,b) => a*b];<br> |
| Java | Java 8+(引入Lambda) | 函数式接口(Function/Predicate)、Lambda表达式 |
java<br>import java.util.function.Function;<br><br>// 1. 赋值给变量(通过函数式接口)<br>Function<Integer, Integer> add = x -> x + 1;<br><br>// 2. 作为参数传递<br>int result = process(5, x -> x * 2);<br>static int process(int num, Function<Integer, Integer> func) {<br> return func.apply(num);<br>}<br> |
| C# | C# 2.0+(委托);C# 3.0+(Lambda增强) | 委托(delegate)、Lambda表达式 |
csharp<br>// 1. 委托类型定义<br>delegate int Op(int x);<br><br>// 2. 赋值与传参<br>Op square = x => x * x;<br>int result = Calculate(3, square);<br>static int Calculate(int num, Op op) => op(num);<br><br>// 3. 存储在列表<br>var funcs = new List<Op> { x => x+1, x => x*2 };<br> |
| Haskell | 全版本(纯函数式语言) | 函数类型(如 a -> b)、柯里化默认支持 |
haskell<br>// 1. 函数赋值(变量即函数)<br>add :: Int -> Int -> Int<br>add a b = a + b<br>func = add<br><br>// 2. 作为参数传递<br>applyTwice :: (a -> a) -> a -> a<br>applyTwice f x = f (f x)<br>result = applyTwice (+1) 5 -- 7<br> |
| C++ | C++11+(Lambda与函数对象) | 函数指针、Lambda表达式、std::function |
cpp<br>#include <functional><br><br>// 1. 存储函数(std::function)<br>std::function<int(int)> func = [](int x) { return x*2; };<br><br>// 2. 作为参数传递<br>void process(int num, std::function<int(int)> f) {<br> cout << f(num) << endl;<br>}<br>process(3, [](int x) { return x+5; }); // 8<br> |
| Ruby | 全版本(一切皆对象) | lambda、proc、方法对象(method) |
```ruby // 1. 赋值给变量 add = lambda { |
| Kotlin | 全版本(原生支持) | 函数类型(如 (Int) -> Int)、Lambda表达式 |
kotlin<br>// 1. 函数类型变量<br>val add: (Int, Int) -> Int = { a, b -> a + b }<br><br>// 2. 作为参数传递<br>fun process(num: Int, func: (Int) -> Int) = func(num)<br>process(6) { it / 2 } // 3(it为默认参数)<br><br>// 3. 作为返回值<br>fun makeFunc(n: Int): (Int) -> Int = { it + n }<br> |
| Go | Go 1.0+(原生支持) | 函数类型直接声明、匿名函数 | go<br>// 1. 函数变量<br>var add func(int, int) int<br>add = func(a, b int) int { return a + b }<br><br>// 2. 作为参数传递<br>func compute(x int, f func(int) int) int {<br> return f(x)<br>}<br>compute(10, func(n int) int { return n - 3 }) // 7<br> |
关键说明:
-
原生支持 vs 增强支持:
- Python、JavaScript、Ruby、Haskell、Go 从设计之初就将函数视为一等公民,语法简洁直接。
- Java、C#、C++ 是通过后期版本(如 Java 8、C++11)引入 Lambda 或函数对象机制,实现了对“函数作为一等公民”的支持。
-
核心差异:
- 纯函数式语言(如 Haskell)中,函数是默认的一等公民,且天然支持柯里化(函数自动返回新函数)。
- 面向对象语言(如 Java、C#)中,函数通常需要通过“接口/委托”包装,但语法逐渐向简洁化发展(如 Lambda 表达式)。
-
实用性:
无论实现方式如何,支持“函数作为一等公民”的语言都能更方便地实现回调、高阶函数(如map/filter)、策略模式等,提升代码灵活性。
总结
- 函数是"可复用的代码块",用
function定义,通过函数名()调用。 - 参数灵活:支持默认值、剩余参数,数量可不匹配。
- 匿名函数没有名字,适合临时使用。
- 箭头函数是简化写法,
this指向和普通函数不同。 - 函数可以作为参数传递,让代码更灵活(高阶函数)。
更多推荐

所有评论(0)