函数
函数
函数是 JavaScript 最核心的概念,也是与 Java 差异最大的部分。最根本的一点:JS 的函数是一等公民——函数是”值”,可以赋给变量、当参数传、当返回值返回;并且 this 是运行时动态绑定的,与 Java 的 this 含义完全不同。
这一篇覆盖函数从基础到进阶的全部常用知识点:函数声明/表达式、默认参数、rest 参数、箭头函数、IIFE、闭包、this、回调与高阶函数、call/apply/bind。
一、差异总览
| 维度 | Java | JavaScript |
|---|---|---|
| 函数地位 | 方法,属于类 | 一等公民,是值 |
| 定义方式 | 返回值 方法名(参数) {} |
声明式 / 表达式 / 箭头函数 |
| 返回类型 / 参数类型 | 必须声明 | 无类型声明 |
| 重载 | 支持 | 不支持(后定义覆盖前面的) |
| 参数个数 | 固定(可变参数除外) | 随意,多传少传都行 |
this |
指向当前实例 | 运行时动态绑定 |
| 默认参数 | 无(靠重载) | 支持 (a = 1) |
二、函数基础
1. 定义与调用(与 Java 对照)
1 | function add(a, b) { |
- 没有返回类型、参数类型,用
function关键字。 - 没有返回值时,默认返回
undefined(Java 的void)。
2. 函数声明 vs 函数表达式
1 | // 函数声明:有"提升",可先调用后声明 |
⚠️ 函数声明会被提升到作用域顶部,所以”先调用后定义”也能运行;函数表达式(用
const)则必须先定义再调用。
3. 函数是一等公民
函数是值,可以到处传递:
1 | const fn = add; // 赋给变量 |
三、参数
1. 默认参数
1 | function greet(name = "World") { |
- 只在未传参或传
undefined时生效(传null不会用默认值)。 - Java 没有默认参数,靠重载模拟。
2. rest 参数 ...args
收集剩余参数成数组,对应 Java 的可变参数 int... nums:
1 | function sum(...nums) { |
...必须放在参数列表最后。
3. 参数解构
1 | function print({ name, age }) { |
4. arguments 对象(旧语法)
1 | function f() { |
⚠️
arguments是类数组(有 length、下标,但没有数组方法),且箭头函数没有arguments,现代代码一般用 rest 参数替代。
四、箭头函数
箭头函数是更简洁的函数写法,但行为与普通函数有本质区别。
1. 语法
1 | const add = (a, b) => a + b; // 单表达式,直接返回(省略 return 和 {}) |
2. 与普通函数的区别(重点)
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
自己的 this |
有(动态绑定) | 没有,继承外层作用域的 this |
arguments |
有 | 没有 |
能否 new |
能 | 不能(会报错) |
prototype |
有 | 没有 |
⚠️ 最核心:箭头函数**没有自己的
this**,它用的this是定义时外层作用域的this。这既是它最大的优点(解决this丢失),也是新手最常踩的坑(见下面this章节)。
五、立即执行函数 IIFE
IIFE(Immediately Invoked Function Expression)定义后立即执行一次:
1 | (function() { |
作用:隔离作用域,避免变量污染全局。在早期没有 let/const 块级作用域时是必备技巧;现在 let/const 已能隔离块级作用域,但 IIFE 仍常用于一次性初始化、模块化封装等场景。
六、闭包 closure
闭包 = 函数 + 它定义时所在的外层作用域。内层函数”记住”并能访问外层函数的变量,即使外层函数已执行完毕。
1 | function makeCounter() { |
对比 Java:类似匿名内部类捕获局部变量,但 Java 要求变量是 final/effectively final(不可改);JS 闭包可以直接修改外层变量。
经典坑(循环里的 var)
1 | for (var i = 0; i < 3; i++) { |
1 | for (let i = 0; i < 3; i++) { |
⚠️ 记忆点:闭包记住的是变量本身(引用),不是当时的快照值。循环里用
var会共享变量,用let才能每轮独立——这正是第 1 篇强调”循环用 let 不用 var”的原因。
闭包的实际应用
- 封装私有变量(计数器、缓存)。
- 事件监听、定时器回调中保留状态。
- 函数柯里化、防抖/节流等(进阶)。
七、this 绑定规则
JS 的 this **不指向定义它的对象,而取决于”谁调用”**,是运行时决定的。
1. 默认绑定:独立调用
1 | function f() { |
2. 隐式绑定:谁调用指向谁
1 | const obj = { |
3. 显式绑定:call / apply / bind
见下一节。
4. new 绑定:构造函数
用 new 调用时,this 指向新创建的对象。
5. 箭头函数:不绑定 this
1 | const obj = { |
用普通函数会打印
undefined(回调里this指向 window),用箭头函数则正确——这是箭头函数最经典的用法。
6. this 丢失的坑
1 | const obj = { name: "Tom", say() { console.log(this.name); } }; |
解决:用 bind 固定 this,或用箭头函数。
八、call / apply / bind
三者都用于改变函数的 this:
1 | function greet(greeting) { |
| 方法 | 是否立即调用 | 参数形式 | 返回 |
|---|---|---|---|
call |
是 | 参数列表 | 函数返回值 |
apply |
是 | 参数数组 | 函数返回值 |
bind |
否 | 参数列表 | 绑定了 this 的新函数 |
记忆:call/apply 立即执行,bind 返回新函数;call 传列表、apply 传数组(apply 的 a = array)。
九、回调与高阶函数
- 回调函数:作为参数传给另一个函数、在适当时机被调用的函数。
- 高阶函数:接收函数作为参数、或返回函数的函数。
1 | // map 是高阶函数,x => x * 2 是回调 |
回调分同步(如 map)和异步(如 setTimeout、事件监听),异步回调是 JS 处理”耗时操作”的基础,后续深入。
十、速查表
| 需求 | 写法 |
|---|---|
| 声明函数 | function f(a, b) {} |
| 函数表达式 | const f = function(a, b) {}; |
| 箭头函数 | (a, b) => a + b |
| 默认参数 | function f(a = 1) {} |
| 剩余参数 | function f(...args) {} |
| 立即执行 | (function(){})(); / (() => {})(); |
| 闭包计数 | 外层 let n = 0 + 返回内层函数 |
| 绑定 this | fn.call(obj, a) / fn.apply(obj, [a]) / fn.bind(obj) |
| 高阶函数 | arr.map(x => ...) |
十一、复习要点
- 函数是一等公民:是值,可赋给变量、传参、返回。
- 函数声明会提升,函数表达式(
const)不会。 - 参数无类型、个数随意、不支持重载;默认参数、rest 参数、解构是常用特性。
- 箭头函数没有自己的 this / arguments / prototype,不能 new,this 继承外层。
- IIFE 立即执行,用于隔离作用域。
- 闭包 = 函数 + 外层作用域,记住的是变量引用;循环用
var会共享变量(打印全一样),用let才独立。 - this 由”谁调用”决定:默认 window、隐式绑定调用者、显式 call/apply/bind、new 绑定、箭头函数继承外层。
- 方法被单独取出会丢失 this,用
bind或箭头函数解决。 call/apply立即执行(call 列表、apply 数组),bind返回新函数。- 回调是传给别的函数的函数,高阶函数是接收/返回函数的函数。
下一篇预告:对象与常用内置 API——对象字面量、解构、原型与 class、String/Math/Date/JSON 等。