JavaScript 函数
函数是 JS 最重要的概念之一——把一段代码封装起来,起个名字,需要时调用。JS 里函数是"一等公民":可以赋值给变量、当参数传递、当返回值,这种灵活性是 JS 强大的根源。这一章我们彻底搞清楚函数的所有用法。
1. 三种定义方式
// 1. 函数声明(有提升,可以在定义前调用)
function add(a, b) {
return a + b;
}
console.log(add(1, 2)); // 3
// 2. 函数表达式(赋值给变量)
const subtract = function (a, b) {
return a - b;
};
console.log(subtract(5, 2)); // 3
// 3. 箭头函数(最简洁,推荐)
const multiply = (a, b) => a * b;
console.log(multiply(3, 4)); // 12
// 函数没有 return 就返回 undefined
function greet() {
console.log("hi");
}
console.log(greet()); // undefined函数声明会被提升到作用域顶部,可以在定义之前调用;函数表达式不会提升——变量提升后值是 undefined,在赋值前调用会报错。所以函数表达式必须先定义后使用。
2. 箭头函数
箭头函数是 ES6 引入的,语法极其简洁,是现代 JS 的主流写法:
// 箭头函数语法
const f1 = () => "hello"; // 无参数
const f2 = (x) => x * 2; // 一个参数(括号可省)
const f3 = x => x * 2; // 等价于上一行
const f4 = (a, b) => a + b; // 多参数
const f5 = (a, b) => { // 函数体多行,需要 {} 和 return
const sum = a + b;
return sum;
};
// 箭头函数 vs 普通函数:箭头函数没有自己的 this
const obj = {
name: "小明",
// ❌ 普通函数:this 取决于调用方式
greet: function () {
setTimeout(function () {
console.log(this.name); // undefined(this 是 window)
}, 100);
},
// ✅ 箭头函数:继承外层的 this
greetOk: function () {
setTimeout(() => {
console.log(this.name); // "小明"
}, 100);
}
};箭头函数 vs 普通函数最大的区别:箭头函数没有自己的 this——它的 this 继承自定义时的外层作用域(词法 this)。普通函数的 this 取决于调用方式。这意味着在回调里(尤其 setTimeout、事件处理),箭头函数能避免 this 丢失的经典坑。
另外箭头函数不能用作构造函数(不能 new),没有 arguments 对象。
3. 参数:默认值、剩余、解构
// 默认参数:调用时不传就用默认值
const greet = (name = "匿名", greeting = "你好") => {
console.log(`${greeting},${name}!`);
};
greet(); // 你好,匿名!
greet("小明"); // 你好,小明!
greet("小明", "嗨"); // 嗨,小明!
// 剩余参数(rest):把多余的实参收成数组
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4)); // 10
// 普通参数 + 剩余参数(剩余必须在最后)
const log = (prefix, ...items) => {
console.log(prefix, items);
};
log("fruits", "apple", "banana"); // fruits ["apple", "banana"]
// arguments 对象(老语法,普通函数才有)
function showArgs() {
console.log(arguments); // 类数组,不是真数组
}
showArgs(1, 2, 3); // [1, 2, 3]剩余参数(...args)是替代老式 arguments 的现代写法,得到的是真正的数组,可以调用数组方法。
4. 回调与高阶函数
这是 JS 函数式编程的基础。把函数当数据用:
- 回调:函数作为参数传给另一个函数,在合适时机被"回调"。事件监听、定时器、数组方法全是回调。
- 高阶函数:接收函数作为参数,或返回函数的函数。
// 函数是一等公民:可以当参数传,可以当返回值
// 回调函数:把函数作为参数传给另一个函数
function doSomething(callback) {
console.log("开始干活...");
callback();
}
doSomething(() => console.log("干完了"));
// 数组方法的回调
const nums = [1, 2, 3];
nums.forEach(n => console.log(n));
const doubled = nums.map(n => n * 2);
const evens = nums.filter(n => n % 2 === 0);
// 高阶函数:接收函数作为参数,或返回函数
const multiplier = (factor) => (x) => x * factor;
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15柯里化(返回函数的函数)是高阶函数的典型应用,可以"预填参数"生成专用函数,在函数式编程里大量使用。
5. 递归
递归是函数调用自己。必须要有终止条件,否则会无限调用导致栈溢出:
// 递归:函数调用自己
// 经典:阶乘 n!
function factorial(n) {
if (n <= 1) return 1; // 终止条件
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120(5*4*3*2*1)
// 斐波那契数列
function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
console.log(fib(10)); // 55
// 实战:深拷贝对象
function deepCopy(obj) {
if (obj === null || typeof obj !== "object") return obj;
const result = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key]);
}
}
return result;
}递归通常代码更简洁(树、图的遍历特别自然),但性能上不如循环(每次调用都压栈)。性能敏感场景可以改写成循环,或用尾递归优化(JS 严格模式支持,但浏览器支持不完善)。
6. 立即调用函数(IIFE)
IIFE(Immediately Invoked Function Expression)是定义后立刻执行的函数,主要用于创建独立作用域:
// IIFE:立即调用函数表达式(定义后立刻执行)
(function () {
console.log("立刻执行");
})();
// 用途 1:避免污染全局(ES6 之前用 var 时常用)
(function () {
var private = "secret";
console.log(private);
})();
// console.log(private); // 报错,外面访问不到
// 用途 2:创建独立作用域,避免循环变量泄漏
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// 输出: 0 1 2
// ES6 之后,let/const + 模块基本替代了 IIFEES6 之后,有了 let/const 块级作用域和模块系统,IIFE 的使用大幅减少。但在老代码里仍很常见,需要看得懂。
7. this 简述
JS 的 this 是最让人困惑的概念之一,这里先做简单了解:
- 普通函数:
this取决于调用方式。obj.fn()时this是obj;裸调用fn()时严格模式是undefined,普通模式是window。 - 箭头函数:
this继承自定义时的外层作用域,不受调用方式影响。 - 构造函数(用
new调用):this是新创建的实例。 - call/apply/bind:可以手动指定
this。
const obj = {
name: "小明",
sayHi() { console.log(this.name); },
sayHiArrow: () => console.log(this.name)
};
obj.sayHi(); // "小明"(普通函数,this 是 obj)
obj.sayHiArrow(); // undefined(箭头函数,this 是外层,即 window)
const fn = obj.sayHi;
fn(); // undefined(裸调用,this 是 window/undefined)this 的坑会在"对象"一章进一步讲。
8. 闭包预览
// 函数可以"记住"它定义时所在的作用域
function makeCounter() {
let count = 0; // 私有变量
return function () {
count++; // 内层函数访问外层的 count
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 从外部访问不到,只能通过 counter() 改 - 这就是"私有变量"
// console.log(count); // ReferenceError函数"记住"外层变量、即使外层函数已经返回——这就是闭包。下一章我们专门讲它。
小结
- 三种定义:函数声明(有提升)、函数表达式、箭头函数(推荐)。
- 箭头函数没有自己的
this,在回调里更安全。 - 函数是值,可以作为参数(回调)、返回值(高阶函数)。
- 递归必须有终止条件。
下一篇我们深入 JS 最有特色的概念——作用域与闭包。
← 上一篇 JavaScript 条件与循环
下一篇 JavaScript 作用域与闭包 →