05.JavaScript 函数详解
一、函数的定义与作用
1. 函数的基本概念
- 函数是一段可重复使用的代码块
- 用于执行特定任务或计算值
- 提高代码的可重用性、可读性和可维护性
2. 函数的主要作用
- 封装:将代码逻辑封装起来
- 复用:多次调用同一段代码
- 抽象:隐藏实现细节,暴露接口
- 模块化:将程序分解为小的功能单元
二、函数的声明方式
1. 函数声明(Function Declaration)
// 基本语法
function functionName(parameters) {
// 函数体
return result;
}
// 示例
function greet(name) {
return "Hello, " + name + "!";
}
function add(a, b) {
return a + b;
}
特点:
- 存在函数提升(可以在声明前调用)
- 有自己独立的
this绑定 - 属于全局作用域或函数作用域
2. 函数表达式(Function Expression)
// 基本语法
const functionName = function(parameters) {
// 函数体
return result;
};
// 示例
const multiply = function(x, y) {
return x * y;
};
// 匿名函数表达式
const greet = function(name) {
return "Hello, " + name;
};
// 有名称的函数表达式(推荐)
const factorial = function calcFactorial(n) {
if (n <= 1) return 1;
return n * calcFactorial(n - 1);
};
特点:
- 不存在函数提升
- 可以作为参数传递
- 可以立即调用
3. 箭头函数(Arrow Function,ES6+)
// 基本语法
const functionName = (parameters) => {
// 函数体
return result;
};
// 示例
const square = (x) => {
return x * x;
};
// 简化形式
const square = x => x * x; // 单参数可省略括号
const add = (a, b) => a + b; // 单行可省略return和大括号
const greet = name => `Hello, ${name}`; // 模板字符串
const noParam = () => console.log("Hello"); // 无参数需要括号
特点:
- 没有自己的
this,继承外层作用域的this - 没有
arguments对象 - 不能用作构造函数(不能用
new) - 没有
prototype属性 - 更简洁的语法
4. 构造函数(Function Constructor,不推荐)
// 语法:new Function(arg1, arg2, ..., functionBody)
const add = new Function('a', 'b', 'return a + b');
const greet = new Function('name', 'return "Hello, " + name');
console.log(add(2, 3)); // 5
console.log(greet("Alice")); // "Hello, Alice"
特点:
- 动态创建函数
- 性能较差(每次都会解析)
- 安全性问题(可能执行恶意代码)
- 一般不建议使用
三、函数的调用方式
1. 直接调用
function sayHello() {
console.log("Hello!");
}
sayHello(); // 直接调用
2. 作为方法调用(对象的方法)
const person = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
}
};
person.greet(); // "Hello, John"(this指向person对象)
3. 作为构造函数调用
function Person(name, age) {
this.name = name;
this.age = age;
}
const john = new Person("John", 25); // 使用new关键字
console.log(john.name); // "John"
4. 间接调用(call, apply, bind)
function introduce(greeting, punctuation) {
console.log(greeting + ", I'm " + this.name + punctuation);
}
const person = { name: "Alice" };
// call - 立即调用,参数逐个传递
introduce.call(person, "Hello", "!"); // "Hello, I'm Alice!"
// apply - 立即调用,参数以数组传递
introduce.apply(person, ["Hi", "."]); // "Hi, I'm Alice."
// bind - 返回新函数,稍后调用
const boundIntro = introduce.bind(person, "Hey");
boundIntro("!!"); // "Hey, I'm Alice!!"
5. 立即调用函数表达式(IIFE)
// 基本形式
(function() {
console.log("立即执行");
})();
// 带参数
(function(name) {
console.log("Hello, " + name);
})("John");
// 箭头函数形式
(() => {
console.log("IIFE with arrow function");
})();
// 异步IIFE
(async function() {
const data = await fetchData();
console.log(data);
})();
6. 回调函数调用
// 作为参数传递
function processData(data, callback) {
// 处理数据
const result = data.toUpperCase();
// 调用回调函数
callback(result);
}
processData("hello", function(result) {
console.log(result); // "HELLO"
});
// 数组方法中的回调
const numbers = [1, 2, 3];
numbers.forEach(function(num) {
console.log(num * 2);
});
四、函数参数详解
1. 形参与实参
// 形参(parameters):函数定义时声明的变量
function greet(name, greeting = "Hello") {
console.log(greeting + ", " + name);
}
// 实参(arguments):函数调用时传递的值
greet("John", "Hi"); // "Hi, John"
greet("Alice"); // "Hello, Alice"(使用默认参数)
2. 参数传递机制
// 基本类型:按值传递
function changePrimitive(value) {
value = 100;
}
let num = 50;
changePrimitive(num);
console.log(num); // 50(不变)
// 引用类型:按引用传递(实际是传递引用的值)
function changeObject(obj) {
obj.name = "Changed";
}
const person = { name: "Original" };
changeObject(person);
console.log(person.name); // "Changed"(改变)
3. 默认参数(ES6)
function createUser(name, age = 18, isActive = true) {
return {
name,
age,
isActive
};
}
console.log(createUser("John")); // {name: "John", age: 18, isActive: true}
console.log(createUser("Alice", 25)); // {name: "Alice", age: 25, isActive: true}
console.log(createUser("Bob", 30, false)); // {name: "Bob", age: 30, isActive: false}
4. 剩余参数(Rest Parameters,ES6)
// 收集剩余参数为数组
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
console.log(sum(5)); // 5
// 与其他参数结合使用
function showInfo(name, age, ...hobbies) {
console.log(`Name: ${name}, Age: ${age}`);
console.log(`Hobbies: ${hobbies.join(", ")}`);
}
showInfo("John", 25, "reading", "gaming", "hiking");
5. arguments 对象
function showArguments() {
console.log("参数数量:", arguments.length);
console.log("所有参数:", arguments);
// 转换为数组
const argsArray = Array.from(arguments);
// 或使用扩展运算符
const argsArray2 = [...arguments];
}
showArguments(1, 2, 3, 4);
// 注意:箭头函数没有arguments对象
const showArgs = () => {
// console.log(arguments); // 错误!
};
五、函数的返回值
1. return 语句
// 返回一个值
function add(a, b) {
return a + b;
}
// 提前返回
function divide(a, b) {
if (b === 0) {
return "Error: Division by zero";
}
return a / b;
}
// 返回对象
function createUser(name, age) {
return {
name: name,
age: age,
isAdult: age >= 18
};
}
// 没有return或return空值,则返回undefined
function noReturn() {
// 没有return语句
}
console.log(noReturn()); // undefined
function emptyReturn() {
return;
}
console.log(emptyReturn()); // undefined
2. 返回函数(高阶函数)
// 函数工厂
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. 返回多个值
// 返回数组
function getMinMax(numbers) {
return [Math.min(...numbers), Math.max(...numbers)];
}
const [min, max] = getMinMax([1, 5, 3, 9, 2]);
console.log(min, max); // 1, 9
// 返回对象
function getUserInfo() {
return {
name: "John",
age: 25,
email: "john@example.com"
};
}
const {name, age} = getUserInfo();
console.log(name, age); // "John", 25
六、作用域与闭包
1. 函数作用域
// 全局变量
let globalVar = "I'm global";
function outerFunction() {
// 局部变量(函数作用域)
let outerVar = "I'm in outer function";
function innerFunction() {
// 可以访问外层变量
console.log(outerVar); // "I'm in outer function"
console.log(globalVar); // "I'm global"
// 自己的局部变量
let innerVar = "I'm in inner function";
}
// console.log(innerVar); // 错误!无法访问内层变量
innerFunction();
}
outerFunction();
2. 闭包(Closure)
// 基本闭包
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
// 闭包的实际应用:数据封装
function createPerson(name) {
let age = 0; // 私有变量
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
setAge: function(newAge) {
if (newAge >= 0) {
age = newAge;
}
},
birthday: function() {
age++;
}
};
}
const person = createPerson("Alice");
person.setAge(25);
person.birthday();
console.log(person.getName(), person.getAge()); // "Alice", 26
七、特殊函数类型
1. 生成器函数(Generator Function,ES6)
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().done); // true
// 无限生成器
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
// 使用return提前结束
function* generatorWithReturn() {
yield 1;
return "结束";
yield 2; // 不会执行
}
2. 异步函数(Async Function,ES7)
// 基本语法
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// 使用方式
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
// 异步函数表达式
const fetchUser = async function(userId) {
// 异步操作
};
// 箭头异步函数
const fetchPost = async (postId) => {
// 异步操作
};
3. 递归函数
// 阶乘
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
// 斐波那契数列(带记忆化)
function fibonacci(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 2) return 1;
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
return memo[n];
}
console.log(fibonacci(10)); // 55
4. 纯函数(Pure Function)
// 纯函数:相同输入总是得到相同输出,无副作用
function pureAdd(a, b) {
return a + b;
}
// 非纯函数:有副作用
let counter = 0;
function impureAdd(a) {
counter++; // 副作用:修改了外部状态
return a + counter;
}
// 非纯函数:依赖于外部状态
function dependsOnExternal(x) {
return x + Date.now(); // 每次结果不同
}
5. 柯里化函数(Currying)
// 普通函数
function addThree(a, b, c) {
return a + b + c;
}
// 柯里化版本
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // 6
// 使用ES6简化
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
};
const curriedMultiply = curry((a, b, c) => a * b * c);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
八、函数的方法与属性
1. 函数的属性
function example(a, b, c) {
return a + b + c;
}
// 函数名
console.log(example.name); // "example"
// 参数数量
console.log(example.length); // 3
// 函数体字符串
console.log(example.toString());
2. call, apply, bind 方法
function introduce(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const person = { name: "Alice" };
// call - 立即执行,参数逐个传递
introduce.call(person, "Hello", "!");
// apply - 立即执行,参数数组传递
introduce.apply(person, ["Hi", "."]);
// bind - 返回新函数,稍后执行
const boundIntro = introduce.bind(person);
setTimeout(() => boundIntro("Hey", "!!"), 1000);
// bind 部分参数
const sayHello = introduce.bind(person, "Hello");
sayHello("!"); // "Hello, I'm Alice!"
3. 自定义函数属性
// 缓存计算结果
function factorial(n) {
if (!factorial.cache) {
factorial.cache = {};
}
if (factorial.cache[n]) {
console.log(`从缓存获取 ${n}!`);
return factorial.cache[n];
}
if (n <= 1) return 1;
const result = n * factorial(n - 1);
factorial.cache[n] = result;
console.log(`计算 ${n}!`);
return result;
}
console.log(factorial(5)); // 计算并缓存
console.log(factorial(5)); // 从缓存获取
九、最佳实践
1. 函数命名规范
// 使用动词或动词短语
function getUserData() {} // 获取用户数据
function calculateTotal() {} // 计算总计
function validateInput() {} // 验证输入
// 使用小驼峰命名法
function processPayment() {}
function formatDateString() {}
2. 保持函数简洁(单一职责)
// 不好的写法:一个函数做太多事情
function processUser(user) {
// 验证用户
// 保存到数据库
// 发送欢迎邮件
// 记录日志
// ...
}
// 好的写法:分解为多个函数
function validateUser(user) { /* ... */ }
function saveUserToDB(user) { /* ... */ }
function sendWelcomeEmail(user) { /* ... */ }
function logUserCreation(user) { /* ... */ }
function processUser(user) {
validateUser(user);
saveUserToDB(user);
sendWelcomeEmail(user);
logUserCreation(user);
}
3. 参数处理
// 使用对象参数提高可读性
function createUser(options) {
const {
name,
age = 18,
email,
isActive = true
} = options;
// 使用参数
}
// 调用更清晰
createUser({
name: "John",
email: "john@example.com",
age: 25
});
4. 错误处理
// 使用try-catch处理可能失败的操作
function safeParseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error("JSON解析失败:", error.message);
return null;
}
}
// 参数验证
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError("参数必须是数字");
}
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
}
5. 文档注释
/**
* 计算两个数的和
* @param {number} a - 第一个数字
* @param {number} b - 第二个数字
* @returns {number} 两数之和
* @throws {TypeError} 如果参数不是数字
* @example
* add(2, 3) // 返回 5
*/
function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('参数必须是数字');
}
return a + b;
}
十、总结对比
1. 不同函数声明方式对比
| 特性 | 函数声明 | 函数表达式 | 箭头函数 | 构造函数 |
|---|---|---|---|---|
| 提升 | ✓ | ✗ | ✗ | ✗ |
| 名称 | 必需 | 可选 | 可选 | 无 |
| this | 动态 | 动态 | 词法 | 动态 |
| arguments | ✓ | ✓ | ✗ | ✓ |
| new调用 | ✓ | ✓ | ✗ | ✓ |
| prototype | ✓ | ✓ | ✗ | ✓ |
2. 使用场景建议
- 函数声明:需要提升或需要多次调用的函数
- 函数表达式:作为回调函数或赋值给变量
- 箭头函数:
- 简短的回调函数
- 需要继承外层
this的场景 - 需要更简洁的语法时
- 构造函数:创建多个相似对象
- 生成器函数:需要生成序列值
- 异步函数:处理异步操作
3. 性能注意事项
- 避免过度嵌套:深度嵌套的函数影响可读性和性能
- 合理使用闭包:闭包会保持外部变量引用,可能造成内存泄漏
- 缓存函数结果:对于计算密集型函数,考虑缓存结果
- 避免在循环中创建函数:每次循环都会创建新函数对象
// 不好的写法:在循环中创建函数
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 好的写法:使用IIFE或闭包
for (let i = 0; i < 10; i++) {
(function(index) {
setTimeout(function() {
console.log(index);
}, 1000);
})(i);
}
// 或使用let的块级作用域
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 1000);
}
4. 现代JavaScript函数特性
- 默认参数:简化参数处理
- 剩余参数:替代
arguments对象 - 展开运算符:简化数组和对象操作
- 解构参数:提取对象属性作为参数
- 尾调用优化(ES6):递归函数性能优化
// 现代函数示例
const createUser = ({
name,
age = 18,
...otherInfo
}) => ({
id: Date.now(),
name,
age,
...otherInfo,
createdAt: new Date()
});
const user = createUser({
name: "John",
email: "john@example.com"
});








很详细