函数概述
函数是JavaScript中的一等公民,用于封装可重用的代码块。在JavaScript中,函数不仅是执行特定任务的代码块,更是构建复杂程序的基础。
函数的重要性
- 代码复用 - 避免重复代码,提高开发效率
- 模块化 - 将复杂问题分解为小问题,便于维护
- 抽象 - 隐藏实现细节,提供清晰的接口
- 作用域管理 - 创建局部作用域,避免变量污染
- 数据封装 - 通过闭包实现私有变量
- 行为抽象 - 将操作封装为可复用的单元
函数的特点
- 函数是一等公民,可以作为参数传递或作为返回值
- 函数可以拥有属性和方法
- 函数可以动态创建和调用
- 支持函数式编程范式
函数定义
JavaScript提供了多种定义函数的方式,每种方式都有其特点和适用场景:
1. 函数声明
函数声明
// 函数声明 (会被提升)
function greet(name) {
return "Hello, " + name + "!";
}
let message = greet("张三");
console.log(message); // "Hello, 张三!"
// 函数声明可以在定义前调用 (函数提升)
console.log(square(5)); // 25
function square(x) {
return x * x;
}
特点: 函数声明会被提升到当前作用域的顶部,可以在声明前调用。
2. 函数表达式
函数表达式
// 函数表达式 (不会被提升)
const multiply = function(a, b) {
return a * b;
};
console.log(multiply(4, 5)); // 20
// 命名函数表达式
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
console.log(factorial(5)); // 120
特点: 函数表达式不会被提升,必须在定义后才能调用。命名函数表达式在调试时更有帮助。
3. 箭头函数 (ES6)
箭头函数
// 箭头函数语法
const add = (a, b) => {
return a + b;
};
// 简写形式 (单行返回)
const square = x => x * x;
// 无参数箭头函数
const sayHello = () => "Hello!";
// 返回对象字面量
const createUser = (name, age) => ({ name, age });
console.log(add(2, 3)); // 5
console.log(square(4)); // 16
console.log(sayHello()); // "Hello!"
console.log(createUser("李四", 30)); // {name: "李四", age: 30}
箭头函数特点:
- 没有自己的
this,继承自父作用域 - 没有
arguments对象 - 不能用作构造函数
- 没有
prototype属性
4. 构造函数
构造函数
// 使用Function构造函数创建函数
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 3)); // 5
// 动态创建复杂函数
const operation = new Function('x', 'y', `
if (x > y) {
return x - y;
} else {
return y - x;
}
`);
console.log(operation(5, 10)); // 5
注意: 使用Function构造函数创建函数会带来安全风险,因为它会从字符串解析代码,应谨慎使用。
函数参数
JavaScript函数的参数处理非常灵活,支持多种参数传递方式:
参数传递
参数传递
// 基本参数
function introduce(name, age, city) {
console.log(`我叫${name},今年${age}岁,来自${city}`);
}
introduce("王五", 25, "上海");
// 默认参数 (ES6)
function createOrder(product, quantity = 1, price = 10) {
return {
product,
quantity,
price,
total: quantity * price
};
}
console.log(createOrder("书本")); // quantity=1, price=10
console.log(createOrder("钢笔", 2)); // quantity=2, price=10
console.log(createOrder("电脑", 1, 5000)); // quantity=1, price=5000
剩余参数 (ES6)
剩余参数
// 剩余参数 (...)
function sum(...numbers) {
let total = 0;
for (let num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
// 与普通参数结合使用
function join(separator, ...strings) {
return strings.join(separator);
}
console.log(join("-", "a", "b", "c")); // "a-b-c"
参数解构
参数解构
// 对象参数解构
function displayUser({ name, age, email = "未知" }) {
console.log(`姓名: ${name}, 年龄: ${age}, 邮箱: ${email}`);
}
const user = { name: "张三", age: 25 };
displayUser(user); // 姓名: 张三, 年龄: 25, 邮箱: 未知
// 数组参数解构
function getFirstTwo([first, second]) {
return { first, second };
}
const numbers = [1, 2, 3, 4];
console.log(getFirstTwo(numbers)); // {first: 1, second: 2}
arguments 对象
arguments 对象
// arguments 对象 (类数组对象)
function showArgs() {
console.log("参数个数: " + arguments.length);
for (let i = 0; i < arguments.length; i++) {
console.log(`参数 ${i}: ${arguments[i]}`);
}
}
showArgs("a", "b", "c");
// 参数个数: 3
// 参数 0: a
// 参数 1: b
// 参数 2: c
注意: 箭头函数没有自己的
arguments对象,建议使用剩余参数代替。
返回值
函数使用return语句返回值,如果没有return语句或return后没有值,函数返回undefined:
返回值
// 显式返回值
function add(a, b) {
return a + b;
}
// 无返回值 (返回 undefined)
function logMessage(message) {
console.log(message);
// 没有 return 语句
}
// 提前返回
function getGrade(score) {
if (score < 0 || score > 100) {
return "无效分数";
}
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
console.log(add(2, 3)); // 5
console.log(logMessage("Hi")); // undefined
console.log(getGrade(85)); // "B"
返回多个值
返回多个值
// 返回数组
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 analyzeArray(arr) {
return {
sum: arr.reduce((a, b) => a + b, 0),
average: arr.reduce((a, b) => a + b, 0) / arr.length,
min: Math.min(...arr),
max: Math.max(...arr)
};
}
const analysis = analyzeArray([1, 2, 3, 4, 5]);
console.log(analysis); // {sum: 15, average: 3, min: 1, max: 5}
作用域和闭包
理解作用域和闭包是掌握JavaScript函数的关键:
函数作用域
函数作用域
let globalVar = "全局变量";
function outerFunction() {
let outerVar = "外部变量";
function innerFunction() {
let innerVar = "内部变量";
console.log(innerVar); // 可以访问
console.log(outerVar); // 可以访问
console.log(globalVar); // 可以访问
}
innerFunction();
// console.log(innerVar); // 错误: innerVar 未定义
}
outerFunction();
闭包
闭包示例
// 创建计数器
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("张三");
person.setAge(25);
person.birthday();
console.log(person.getName()); // "张三"
console.log(person.getAge()); // 26
闭包的实际应用
闭包的实际应用
// 模块模式
const calculator = (function() {
let memory = 0;
return {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
},
store: function(value) {
memory = value;
},
recall: function() {
return memory;
},
clear: function() {
memory = 0;
}
};
})();
console.log(calculator.add(5, 3)); // 8
calculator.store(10);
console.log(calculator.recall()); // 10
高阶函数
高阶函数是接受函数作为参数或返回函数的函数,是函数式编程的核心概念:
高阶函数
// 接受函数作为参数
function operate(a, b, operation) {
return operation(a, b);
}
const result1 = operate(5, 3, (x, y) => x + y);
const result2 = operate(5, 3, (x, y) => x * y);
console.log(result1); // 8
console.log(result2); // 15
// 返回函数
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 数组高阶函数
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
const evens = numbers.filter(num => num % 2 === 0);
const sum = numbers.reduce((total, num) => total + num, 0);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(evens); // [2, 4]
console.log(sum); // 15
函数组合
函数组合
// 函数组合工具
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
// 示例函数
const add5 = x => x + 5;
const multiply3 = x => x * 3;
const subtract2 = x => x - 2;
// 组合函数 (从右到左执行)
const composed = compose(subtract2, multiply3, add5);
console.log(composed(10)); // (10 + 5) * 3 - 2 = 43
// 管道函数 (从左到右执行)
const piped = pipe(add5, multiply3, subtract2);
console.log(piped(10)); // (10 + 5) * 3 - 2 = 43
递归函数
递归函数是调用自身的函数,适用于解决可以分解为相似子问题的问题:
递归函数
// 阶乘函数
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
// 斐波那契数列 (带缓存优化)
const fibonacci = (function() {
const cache = {};
return function fib(n) {
if (n <= 1) return n;
if (cache[n]) return cache[n];
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
})();
console.log(fibonacci(10)); // 55
// 遍历嵌套对象
function findValue(obj, targetKey) {
for (let key in obj) {
if (key === targetKey) {
return obj[key];
}
if (typeof obj[key] === 'object' && obj[key] !== null) {
const result = findValue(obj[key], targetKey);
if (result !== undefined) {
return result;
}
}
}
return undefined;
}
const nestedObj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
f: 4
}
}
};
console.log(findValue(nestedObj, 'e')); // 3
立即执行函数 (IIFE)
立即执行函数在定义后立即执行,常用于创建独立作用域:
立即执行函数
// 基本 IIFE
(function() {
console.log("这个函数会立即执行");
})();
// 带参数的 IIFE
(function(name) {
console.log("Hello, " + name);
})("张三");
// 箭头函数 IIFE
(() => {
console.log("箭头函数 IIFE");
})();
// 返回值
const result = (function(a, b) {
return a + b;
})(5, 3);
console.log(result); // 8
// 模块模式 IIFE
const myModule = (function() {
let privateVar = "私有变量";
function privateFunction() {
console.log("私有函数");
}
return {
publicMethod: function() {
console.log("公有方法可以访问: " + privateVar);
privateFunction();
},
getPrivateVar: function() {
return privateVar;
}
};
})();
myModule.publicMethod(); // 公有方法可以访问: 私有变量
console.log(myModule.getPrivateVar()); // "私有变量"
生成器函数
生成器函数可以暂停和恢复执行,用于创建迭代器:
生成器函数
// 基本生成器函数
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = numberGenerator();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3
console.log(generator.next().done); // true
// 无限序列生成器
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
const infinite = infiniteSequence();
console.log(infinite.next().value); // 0
console.log(infinite.next().value); // 1
console.log(infinite.next().value); // 2
// 异步生成器
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
(async function() {
for await (let value of asyncGenerator()) {
console.log(value); // 1, 2, 3
}
})();
实践练习
👆 请点击上方按钮进行演示操作
选择不同的演示按钮来探索JavaScript的各种功能和用法
练习代码
// 练习1: 计算阶乘
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
// 练习2: 斐波那契数列
const fibonacci = (function() {
const cache = {};
return function fib(n) {
if (n <= 1) return n;
if (cache[n]) return cache[n];
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
})();
console.log(fibonacci(10)); // 55
// 练习3: 函数组合
const compose = (...functions) =>
input => functions.reduceRight((acc, fn) => fn(acc), input);
const add5 = x => x + 5;
const multiply3 = x => x * 3;
const subtract2 = x => x - 2;
const composed = compose(subtract2, multiply3, add5);
console.log(composed(10)); // 43
// 练习4: 柯里化函数
function 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 curriedAdd = curry((a, b, c) => a + b + c);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
函数性能优化
了解如何优化函数性能对于编写高效JavaScript代码至关重要:
内存管理
- 避免不必要的闭包,及时释放不再使用的引用
- 使用尾调用优化(在支持的环境中)
- 避免在循环中创建函数
执行优化
- 使用记忆化(缓存函数结果)
- 避免深度递归,考虑使用迭代替代
- 使用节流和防抖处理高频函数调用
记忆化示例
// 记忆化函数
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = fn.apply(this, args);
cache[key] = result;
return result;
};
}
// 使用记忆化优化斐波那契数列
const memoizedFibonacci = memoize(function(n) {
if (n <= 1) return n;
return memoizedFibonacci(n - 1) + memoizedFibonacci(n - 2);
});
console.log(memoizedFibonacci(40)); // 快速计算
最佳实践
- 使用有意义的函数名,清晰表达函数意图
- 函数应该只做一件事(单一职责原则)
- 避免过长的函数,考虑拆分成小函数
- 优先使用纯函数(相同输入总是产生相同输出)
- 使用默认参数而不是在函数内检查参数
- 优先使用箭头函数,特别是作为回调时
- 避免在函数内修改外部状态(减少副作用)
- 合理使用函数注释,说明参数和返回值
- 考虑函数的可测试性,设计易于测试的函数
函数设计原则
- 单一职责原则 - 每个函数只负责一个明确的任务
- 开闭原则 - 函数应该对扩展开放,对修改关闭
- 依赖倒置原则 - 高层函数不应该依赖低层函数,两者都应该依赖抽象
- 接口隔离原则 - 使用多个专门的函数而不是一个通用的函数