跳到主要内容

JavaScript 函数

函数是组织代码的基本单元,可以提高代码的复用性和可读性。

函数声明

函数声明(Function Declaration)

// 基本语法
function greet() {
console.log("你好!");
}

// 调用函数
greet(); // 输出:你好!

// 带参数的函数
function greet(name) {
console.log("你好," + name + "!");
}

greet("张三"); // 输出:你好,张三!

函数表达式(Function Expression)

// 将函数赋值给变量
const greet = function(name) {
console.log("你好," + name + "!");
};

greet("张三"); // 输出:你好,张三!

// 函数表达式可以添加函数名(用于递归)
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};

console.log(factorial(5)); // 120

箭头函数(Arrow Function,ES6)

// 基本语法
const greet = (name) => {
console.log("你好," + name + "!");
};

// 单参数可以省略括号
const greet = name => {
console.log("你好," + name + "!");
};

// 单行函数体可以省略大括号和 return
const add = (a, b) => a + b;

// 返回对象字面量需要用括号包裹
const createPerson = (name, age) => ({
name: name,
age: age
});

箭头函数与普通函数的区别

  1. 没有自己的 this
  2. 没有 arguments 对象
  3. 不能用作构造函数
  4. 没有 prototype 属性
// 箭头函数没有自己的 this
const obj = {
name: "张三",
// 普通函数
sayHello: function() {
console.log("你好," + this.name);
},
// 箭头函数
sayHi: () => {
// 这里的 this 指向外部作用域
console.log("你好," + this.name);
}
};

obj.sayHello(); // 你好,张三
obj.sayHi(); // 你好,undefined(箭头函数的 this 不会绑定)

函数参数

默认参数

function greet(name = "游客", greeting = "你好") {
console.log(greeting + "," + name + "!");
}

greet(); // 你好,游客!
greet("张三"); // 你好,张三!
greet("张三", "欢迎"); // 欢迎,张三!

剩余参数(Rest Parameters)

使用 ... 收集剩余参数:

function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

// 与其他参数配合
function multiply(factor, ...numbers) {
return numbers.map(num => num * factor);
}

console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]

arguments 对象

在普通函数中访问所有参数:

function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}

console.log(sum(1, 2, 3)); // 6

注意:箭头函数没有 arguments 对象。

参数解构

// 解构对象参数
function createUser({ name, age, city = "北京" }) {
console.log(`姓名:${name}`);
console.log(`年龄:${age}`);
console.log(`城市:${city}`);
}

createUser({ name: "张三", age: 20 });
// 姓名:张三
// 年龄:20
// 城市:北京

// 解构数组参数
function getScores([first, second, ...rest]) {
console.log(`第一名:${first}`);
console.log(`第二名:${second}`);
console.log(`其他:${rest}`);
}

getScores([95, 88, 76, 92, 85]);
// 第一名:95
// 第二名:88
// 其他:76,92,85

函数返回值

return 语句

function add(a, b) {
return a + b;
}

const result = add(3, 5);
console.log(result); // 8

返回多个值

// 使用数组
function getMinMax(numbers) {
return [Math.min(...numbers), Math.max(...numbers)];
}

const [min, max] = getMinMax([3, 1, 4, 1, 5, 9, 2, 6]);
console.log(`最小值:${min},最大值:${max}`);

// 使用对象
function getStats(numbers) {
return {
min: Math.min(...numbers),
max: Math.max(...numbers),
avg: numbers.reduce((a, b) => a + b) / numbers.length
};
}

const stats = getStats([3, 1, 4, 1, 5, 9, 2, 6]);
console.log(stats); // {min: 1, max: 9, avg: 3.875}

提前返回

function processUser(user) {
// 验证参数
if (!user) {
return null;
}

if (!user.name) {
return { error: "用户名不能为空" };
}

// 正常处理逻辑
return { success: true, user: user };
}

无返回值函数

没有 return 语句或 return 空值的函数返回 undefined:

function printHello() {
console.log("Hello");
}

console.log(printHello()); // undefined

函数调用

调用模式

// 作为函数调用
function greet() {
console.log(this);
}
greet(); // this 指向全局对象(浏览器中为 window)

// 作为方法调用
const obj = {
name: "张三",
greet() {
console.log(this.name);
}
};
obj.greet(); // this 指向 obj

// 作为构造函数调用
function Person(name) {
this.name = name;
}
const person = new Person("李四"); // this 指向新创建的对象

// 使用 call/apply/bind 调用
function greet() {
console.log(this.name);
}
const obj1 = { name: "张三" };
const obj2 = { name: "李四" };
greet.call(obj1); // 张三
greet.apply(obj2); // 李四
const boundGreet = greet.bind(obj1);
boundGreet(); // 张三

call、apply、bind

const person = {
name: "张三",
greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
};

const anotherPerson = { name: "李四" };

// call - 参数逐个传递
person.greet.call(anotherPerson, "Hello", "!"); // Hello, 李四!

// apply - 参数以数组传递
person.greet.apply(anotherPerson, ["Hi", "."]); // Hi, 李四.

// bind - 返回绑定 this 的新函数
const boundGreet = person.greet.bind(anotherPerson);
boundGreet("Hey", "?"); // Hey, 李四?

// 部分应用参数
const greetHello = person.greet.bind(anotherPerson, "Hello");
greetHello("~"); // Hello, 李四~

作用域

全局作用域

在函数外部声明的变量:

const globalVar = "全局变量";

function test() {
console.log(globalVar); // 可以访问
}

函数作用域

在函数内部声明的变量:

function test() {
const localVar = "局部变量";
console.log(localVar); // 可以访问
}
// console.log(localVar); // 错误:localVar 未定义

块级作用域(let/const)

使用 let 和 const 声明的变量只在当前块内有效:

{
let blockVar = "块级变量";
const CONSTANT = "常量";
}
// console.log(blockVar); // 错误
// console.log(CONSTANT); // 错误

if (true) {
let x = 10;
}
// console.log(x); // 错误

作用域链

const globalVar = "全局";

function outer() {
const outerVar = "外层";

function inner() {
const innerVar = "内层";
console.log(globalVar); // 全局
console.log(outerVar); // 外层
console.log(innerVar); // 内层
}

inner();
}

闭包

闭包是指函数可以访问其词法作用域之外的变量的能力。

基本闭包

function createCounter() {
let count = 0;

return function() {
count++;
return count;
};
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// 多个计数器互不影响
const counter2 = createCounter();
console.log(counter2()); // 1

闭包的应用

1. 数据私有化

function createBankAccount(initialBalance) {
let balance = initialBalance;

return {
deposit(amount) {
if (amount > 0) {
balance += amount;
return `存款成功,当前余额:${balance}`;
}
return "存款金额必须为正数";
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return `取款成功,当前余额:${balance}`;
}
return "余额不足或金额无效";
},
getBalance() {
return balance;
}
};
}

const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.deposit(500)); // 存款成功,当前余额:1500
console.log(account.withdraw(200)); // 取款成功,当前余额:1300
console.log(account.balance); // undefined(无法直接访问)

2. 函数工厂

function multiply(factor) {
return function(number) {
return number * factor;
};
}

const double = multiply(2);
const triple = multiply(3);
const quadruple = multiply(4);

console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(quadruple(5)); // 20

3. 延迟执行

function delayedLog(message, delay) {
setTimeout(() => {
console.log(message);
}, delay);
}

delayedLog("这条消息 2 秒后显示", 2000);

闭包的注意事项

// 常见错误:循环中的闭包
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出:3, 3, 3(var 没有块级作用域)
}, 100);
}

// 解决方法 1:使用 let(推荐)
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出:0, 1, 2
}, 100);
}

// 解决方法 2:使用闭包
for (var i = 0; i < 3; i++) {
((index) => {
setTimeout(() => {
console.log(index); // 输出:0, 1, 2
}, 100);
})(i);
}

递归函数

函数调用自身:

// 阶乘
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}

console.log(factorial(5)); // 120

// 斐波那契数列
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}

// 计算数组和
function sumArray(arr) {
if (arr.length === 0) return 0;
return arr[0] + sumArray(arr.slice(1));
}

// 深拷贝对象
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}

if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}

const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}

高阶函数

接受函数作为参数或返回函数的函数:

// 接受函数作为参数
function applyOperation(numbers, operation) {
return numbers.map(operation);
}

const numbers = [1, 2, 3, 4, 5];
console.log(applyOperation(numbers, x => x * 2)); // [2, 4, 6, 8, 10]
console.log(applyOperation(numbers, x => x ** 2)); // [1, 4, 9, 16, 25]

// 返回函数
function createGreeting(prefix) {
return function(name) {
return `${prefix}, ${name}!`;
};
}

const sayHello = createGreeting("Hello");
const sayHi = createGreeting("Hi");

console.log(sayHello("张三")); // Hello, 张三!
console.log(sayHi("李四")); // Hi, 李四!

函数式编程方法

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// map - 转换每个元素
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

// filter - 过滤元素
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// reduce - 汇总元素
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 55

const product = numbers.reduce((acc, n) => acc * n, 1);
console.log(product); // 3628800

// find - 查找第一个匹配元素
const firstEven = numbers.find(n => n % 2 === 0);
console.log(firstEven); // 2

// findIndex - 查找第一个匹配元素的索引
const firstEvenIndex = numbers.findIndex(n => n % 2 === 0);
console.log(firstEvenIndex); // 1

// some - 检查是否有任何元素满足条件
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven); // true

// every - 检查是否所有元素都满足条件
const allPositive = numbers.every(n => n > 0);
console.log(allPositive); // true

// 链式调用
const result = numbers
.filter(n => n > 5) // [6, 7, 8, 9, 10]
.map(n => n * 2) // [12, 14, 16, 18, 20]
.reduce((acc, n) => acc + n, 0); // 80
console.log(result); // 80

小结

本章我们学习了:

  1. 函数声明、函数表达式和箭头函数
  2. 函数参数(默认参数、剩余参数、参数解构)
  3. 函数返回值
  4. 函数调用模式(call、apply、bind)
  5. 作用域(全局、函数、块级)
  6. 闭包的概念和应用
  7. 递归函数
  8. 高阶函数
  9. 函数式编程方法(map、filter、reduce 等)

练习

  1. 编写一个函数,计算两个数的最大公约数(欧几里得算法)
  2. 编写一个函数,接受任意数量的数字,返回最大值和最小值
  3. 使用闭包创建一个缓存函数,缓存函数的计算结果
  4. 编写一个递归函数,实现数组扁平化
  5. 使用函数式方法计算一组学生成绩的平均分(过滤不及格的)