一、函数的基本特性
1.1 函数的本质
JavaScript中的函数是一等公民,具有以下特性:
- 可以赋值给变量
- 可以作为参数传递
- 可以作为其他函数的返回值
- 可以存储在数据结构中
- 可以拥有属性和方法
// 函数作为一等公民的示例
const sayHello = function(name) {
return `Hello, ${name}!`;
};
function greet(greeter, name) {
return greeter(name);
}
console.log(greet(sayHello, 'Alice')); // "Hello, Alice!"
1.2 函数的声明方式
1.2.1 函数声明
function add(a, b) {
return a + b;
}
- 存在函数提升(hoisting)
- 在定义前即可调用
1.2.2 函数表达式
const multiply = function(a, b) {
return a * b;
};
- 不存在提升
- 可以创建匿名函数
1.2.3 箭头函数(ES6+)
const square = x => x * x;
- 没有自己的
this、arguments、super或new.target - 适合用于非方法函数
1.2.4 构造函数
const divide = new Function('a', 'b', 'return a / b');
- 动态创建函数
- 存在安全性和性能问题,一般不推荐使用
二、函数的内部属性
2.1 标准函数属性
| 属性 | 描述 |
|---|---|
name | 函数名称(匿名函数也有推断名称) |
length | 函数期望的参数个数(不包括剩余参数) |
prototype | 构造函数特有的原型对象 |
caller | 调用当前函数的函数(严格模式下禁用) |
arguments | 函数参数对象,类数组(箭头函数没有,建议使用剩余参数替代) |
function example(a, b, ...rest) {
console.log(example.name); // "example"
console.log(example.length); // 2
console.log(arguments);
// arguments 转数组
const args1 = Array.form(arguments);
const args2 = [...arguments];
const args3 = [].silce.apply(arguments);
const args4 = Array.prototype.silce.apply(arguments);
}
2.2 自定义属性
函数作为对象可以拥有自定义属性:
function counter() {
counter.count = (counter.count || 0) + 1;
return counter.count;
}
console.log(counter()); // 1
console.log(counter()); // 2
三、纯函数与副作用
3.1 纯函数的定义
纯函数是指满足以下条件的函数:
- 相同输入总是产生相同输出
- 没有副作用(不改变外部状态)
- 不依赖外部可变状态
// 纯函数示例
function pureAdd(a, b) {
return a + b;
}
// 非纯函数示例
let base = 10;
function impureAdd(a) {
return a + base; // 依赖外部状态
}
3.2 副作用类型
| 副作用类型 | 示例 |
|---|---|
| 修改外部变量 | let count = 0; function increment() { count++ } |
| 修改参数 | function changeArg(obj) { obj.prop = 'new' } |
| 执行I/O操作 | function log() { console.log('side effect') } |
| 调用非纯函数 | function wrapper() { return impureFunction() } |
3.3 纯函数的优势
- 可预测性:结果只依赖输入参数
- 可测试性:不需要复杂的环境设置
- 可缓存性:可以实施记忆化(memoization)
- 并行安全:无共享状态问题
// 记忆化实现
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const memoizedAdd = memoize((a, b) => a + b);
四、高阶函数
4.1 基本概念
高阶函数是指满足以下任一条件的函数:
- 接受一个或多个函数作为参数
- 返回一个函数作为结果
// 接受函数作为参数
function applyOperation(a, b, operation) {
return operation(a, b);
}
// 返回函数
function createMultiplier(factor) {
return function(x) {
return x * factor;
};
}
4.2 常见高阶函数模式
4.2.1 函数组合
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
const add5 = x => x + 5;
const double = x => x * 2;
const process = compose(double, add5);
console.log(process(10)); // (10 + 5) * 2 = 30
4.2.2 柯里化
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 sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
五、函数执行控制
5.1 调用方式与this绑定
| 调用方式 | this指向 | 示例 |
|---|---|---|
| 直接调用 | 全局对象/undefined | func() |
| 方法调用 | 调用对象 | obj.method() |
| 构造函数调用 | 新创建的对象 | new Constructor() |
| call/apply/bind | 指定对象 | func.call(ctx, arg1, arg2) |
const obj = {
value: 10,
getValue: function() {
return this.value;
}
};
console.log(obj.getValue()); // 10 - 方法调用
const extracted = obj.getValue;
console.log(extracted()); // undefined - 直接调用
5.2 执行上下文管理
// 使用bind固定this
const bound = extracted.bind(obj);
console.log(bound()); // 10
// 箭头函数保持定义时的this
const obj2 = {
value: 20,
getValue: () => this.value // 箭头函数没有自己的this
};
console.log(obj2.getValue()); // undefined(严格模式)
六、函数式编程实践
6.1 不可变数据处理
// 避免直接修改原数组
function pushPure(arr, item) {
return [...arr, item];
}
const original = [1, 2, 3];
const updated = pushPure(original, 4);
console.log(original); // [1, 2, 3]
console.log(updated); // [1, 2, 3, 4]
6.2 常用工具函数
// 谓词函数
const isEven = x => x % 2 === 0;
// 数组操作
const numbers = [1, 2, 3, 4];
const evens = numbers.filter(isEven); // [2, 4]
const doubled = numbers.map(x => x * 2); // [2, 4, 6, 8]
const sum = numbers.reduce((acc, x) => acc + x, 0); // 10
七、函数性能优化
7.1 避免重复计算
// 低效实现
function isPrime(num) {
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return num > 1;
}
// 优化:缓存计算结果
const primeCache = new Map();
function memoizedIsPrime(num) {
if (primeCache.has(num)) return primeCache.get(num);
const result = isPrime(num);
primeCache.set(num, result);
return result;
}
7.2 尾调用优化
// 非尾调用
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 需要保存调用栈
}
// 尾调用优化版本
function tailFactorial(n, acc = 1) {
if (n <= 1) return acc;
return tailFactorial(n - 1, n * acc); // 可被优化
}
八、函数调试与测试
8.1 函数调试技巧
function complexOperation(a, b) {
debugger; // 设置断点
const step1 = a * 2;
const step2 = step1 + b;
console.log({ step1, step2 }); // 调试输出
return step2 / 3;
}
8.2 单元测试要点
// 纯函数易于测试
function testPureAdd() {
console.assert(pureAdd(2, 3) === 5, '2+3 should be 5');
console.assert(pureAdd(0, 0) === 0, '0+0 should be 0');
}
// 非纯函数需要模拟环境
let testBase = 10;
function testImpureAdd() {
console.assert(impureAdd(5) === 15, '5+10 should be 15');
testBase = 20;
console.assert(impureAdd(5) === 25, '5+20 should be 25');
}
九、函数设计最佳实践
- 单一职责原则:每个函数只做一件事
- 合理命名:使用动词短语描述函数行为
- 控制参数数量:建议不超过3个,复杂参数使用对象
- 避免副作用:优先编写纯函数
- 适当注释:解释复杂逻辑和参数
- 防御性编程:验证输入参数
// 良好设计的函数示例
function calculateTotalPrice({ items, discount = 0, taxRate = 0.1 }) {
if (!Array.isArray(items)) throw new Error('Items must be an array');
if (discount < 0 || discount > 1) throw new Error('Invalid discount');
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const discounted = subtotal * (1 - discount);
return discounted * (1 + taxRate);
}