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
});
箭头函数与普通函数的区别
- 没有自己的
this - 没有
arguments对象 - 不能用作构造函数
- 没有
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
小结
本章我们学习了:
- 函数声明、函数表达式和箭头函数
- 函数参数(默认参数、剩余参数、参数解构)
- 函数返回值
- 函数调用模式(call、apply、bind)
- 作用域(全局、函数、块级)
- 闭包的概念和应用
- 递归函数
- 高阶函数
- 函数式编程方法(map、filter、reduce 等)
练习
- 编写一个函数,计算两个数的最大公约数(欧几里得算法)
- 编写一个函数,接受任意数量的数字,返回最大值和最小值
- 使用闭包创建一个缓存函数,缓存函数的计算结果
- 编写一个递归函数,实现数组扁平化
- 使用函数式方法计算一组学生成绩的平均分(过滤不及格的)