函数

86 阅读5分钟

一、函数的基本特性

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;
  • 没有自己的thisargumentssupernew.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 纯函数的定义

纯函数是指满足以下条件的函数:

  1. 相同输入总是产生相同输出
  2. 没有副作用(不改变外部状态)
  3. 不依赖外部可变状态
// 纯函数示例
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 纯函数的优势

  1. 可预测性:结果只依赖输入参数
  2. 可测试性:不需要复杂的环境设置
  3. 可缓存性:可以实施记忆化(memoization)
  4. 并行安全:无共享状态问题
// 记忆化实现
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 基本概念

高阶函数是指满足以下任一条件的函数:

  1. 接受一个或多个函数作为参数
  2. 返回一个函数作为结果
// 接受函数作为参数
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指向示例
直接调用全局对象/undefinedfunc()
方法调用调用对象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');
}

九、函数设计最佳实践

  1. 单一职责原则:每个函数只做一件事
  2. 合理命名:使用动词短语描述函数行为
  3. 控制参数数量:建议不超过3个,复杂参数使用对象
  4. 避免副作用:优先编写纯函数
  5. 适当注释:解释复杂逻辑和参数
  6. 防御性编程:验证输入参数
// 良好设计的函数示例
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);
}