JavaScript 函数完全指南

掌握函数声明、箭头函数、高阶函数、柯里化等核心概念

JavaScript 函数完全指南

函数是 JavaScript 的一等公民。本文详解函数的各种特性和高级用法。

函数创建

函数声明

// 函数声明(会提升)
function greet(name) {
  return `Hello, ${name}!`;
}

// 调用
greet('Alice');  // 'Hello, Alice!'

// 函数声明会提升到作用域顶部
sayHi();  // 可以在声明前调用
function sayHi() {
  console.log('Hi!');
}

函数表达式

// 匿名函数表达式
const greet = function(name) {
  return `Hello, ${name}!`;
};

// 命名函数表达式(便于递归和调试)
const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1);
};

// 函数表达式不会提升
// sayHi();  // ReferenceError
const sayHi = function() {
  console.log('Hi!');
};

箭头函数

// 基本语法
const greet = (name) => `Hello, ${name}!`;

// 单参数可省略括号
const double = n => n * 2;

// 无参数需要括号
const getRandom = () => Math.random();

// 多语句需要大括号和 return
const sum = (a, b) => {
  const result = a + b;
  return result;
};

// 返回对象需要括号
const createUser = (name, age) => ({ name, age });

箭头函数 vs 普通函数

// 1. this 绑定
const obj = {
  name: 'Alice',

  // 普通函数:动态 this
  regular() {
    console.log(this.name);
  },

  // 箭头函数:词法 this(继承外层)
  arrow: () => {
    console.log(this.name);  // undefined(外层 this)
  },

  // 方法中的回调
  delayed() {
    // 普通函数需要保存 this
    const self = this;
    setTimeout(function() {
      console.log(self.name);
    }, 100);

    // 箭头函数自动继承 this
    setTimeout(() => {
      console.log(this.name);
    }, 100);
  }
};

// 2. 无 arguments 对象
function regular() {
  console.log(arguments);  // 可用
}

const arrow = () => {
  // console.log(arguments);  // 报错
};

// 使用剩余参数代替
const arrow2 = (...args) => {
  console.log(args);
};

// 3. 不能作为构造函数
const Arrow = () => {};
// new Arrow();  // TypeError

// 4. 无 prototype 属性
console.log(Arrow.prototype);  // undefined

参数处理

默认参数

// 默认值
function greet(name = 'Guest', greeting = 'Hello') {
  return `${greeting}, ${name}!`;
}

greet();            // 'Hello, Guest!'
greet('Alice');     // 'Hello, Alice!'
greet('Bob', 'Hi'); // 'Hi, Bob!'

// 默认值可以是表达式
function createId(prefix = 'id', num = Date.now()) {
  return `${prefix}_${num}`;
}

// 使用前面的参数
function rectangle(width, height = width) {
  return width * height;
}

rectangle(5);  // 25(正方形)

剩余参数

// 收集剩余参数
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

sum(1, 2, 3);        // 6
sum(1, 2, 3, 4, 5);  // 15

// 与固定参数组合
function log(level, ...messages) {
  console.log(`[${level}]`, ...messages);
}

log('INFO', 'User', 'logged in');
// [INFO] User logged in

// 必须是最后一个参数
// function invalid(...rest, last) {}  // SyntaxError

参数解构

// 对象解构
function createUser({ name, age, role = 'user' }) {
  return { name, age, role };
}

createUser({ name: 'Alice', age: 25 });

// 带默认值
function config({ host = 'localhost', port = 3000 } = {}) {
  return `${host}:${port}`;
}

config();                   // 'localhost:3000'
config({ port: 8080 });     // 'localhost:8080'

// 数组解构
function getFirst([first, second]) {
  return first;
}

getFirst([1, 2, 3]);  // 1

this 绑定

隐式绑定

const obj = {
  name: 'Alice',
  greet() {
    console.log(`Hello, ${this.name}`);
  }
};

obj.greet();  // 'Hello, Alice'

// 方法赋值给变量会丢失 this
const fn = obj.greet;
fn();  // 'Hello, undefined'

显式绑定

function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const user = { name: 'Alice' };

// call - 立即调用,逐个传参
greet.call(user, 'Hello', '!');  // 'Hello, Alice!'

// apply - 立即调用,数组传参
greet.apply(user, ['Hi', '?']);  // 'Hi, Alice?'

// bind - 返回绑定函数
const boundGreet = greet.bind(user, 'Hey');
boundGreet('...');  // 'Hey, Alice...'

// 部分应用
const sayHi = greet.bind(user, 'Hi', '!');
sayHi();  // 'Hi, Alice!'

硬绑定

// 防止 this 丢失
class Button {
  constructor(label) {
    this.label = label;
    // 绑定方法
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(`Clicked: ${this.label}`);
  }
}

// 或使用类字段语法
class Button2 {
  label = 'Button';

  // 箭头函数自动绑定
  handleClick = () => {
    console.log(`Clicked: ${this.label}`);
  };
}

高阶函数

函数作为参数

// 回调函数
function doTwice(fn) {
  fn();
  fn();
}

doTwice(() => console.log('Hello'));
// Hello
// Hello

// 数组方法
const numbers = [1, 2, 3, 4, 5];

// map
const doubled = numbers.map(n => n * 2);

// filter
const evens = numbers.filter(n => n % 2 === 0);

// reduce
const sum = numbers.reduce((acc, n) => acc + n, 0);

// 自定义高阶函数
function times(n, fn) {
  for (let i = 0; i < n; i++) {
    fn(i);
  }
}

times(3, i => console.log(`Iteration ${i}`));

函数作为返回值

// 返回函数
function multiplier(factor) {
  return n => n * factor;
}

const double = multiplier(2);
const triple = multiplier(3);

double(5);  // 10
triple(5);  // 15

// 配置函数
function createLogger(prefix) {
  return function(message) {
    console.log(`[${prefix}] ${message}`);
  };
}

const infoLog = createLogger('INFO');
const errorLog = createLogger('ERROR');

infoLog('Hello');   // [INFO] Hello
errorLog('Oops');   // [ERROR] Oops

柯里化

基本概念

// 非柯里化
function add(a, b, c) {
  return a + b + c;
}
add(1, 2, 3);  // 6

// 柯里化
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

curriedAdd(1)(2)(3);  // 6

// 箭头函数版
const curriedAdd2 = a => b => c => a + b + c;

// 部分应用
const add1 = curriedAdd(1);
const add1and2 = add1(2);
add1and2(3);  // 6

通用柯里化

// 柯里化函数
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...moreArgs) {
      return curried.apply(this, args.concat(moreArgs));
    };
  };
}

// 使用
function sum(a, b, c) {
  return a + b + c;
}

const curriedSum = curry(sum);

curriedSum(1, 2, 3);    // 6
curriedSum(1)(2)(3);    // 6
curriedSum(1, 2)(3);    // 6
curriedSum(1)(2, 3);    // 6

函数组合

基本组合

// 组合两个函数
const compose = (f, g) => x => f(g(x));

const double = x => x * 2;
const addOne = x => x + 1;

const doubleThenAddOne = compose(addOne, double);
doubleThenAddOne(5);  // 11 (5 * 2 + 1)

// 管道(从左到右)
const pipe = (f, g) => x => g(f(x));

const addOneThenDouble = pipe(addOne, double);
addOneThenDouble(5);  // 12 ((5 + 1) * 2)

多函数组合

// 组合多个函数
const compose = (...fns) =>
  fns.reduce((f, g) => (...args) => f(g(...args)));

const pipe = (...fns) =>
  fns.reduce((f, g) => (...args) => g(f(...args)));

// 使用
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const transform = pipe(addOne, double, square);
transform(2);  // 36 ((2 + 1) * 2)² = 36

函数记忆化

基本实现

// 简单记忆化
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.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// 使用
const slowFib = n => n <= 1 ? n : slowFib(n - 1) + slowFib(n - 2);
const fastFib = memoize(function fib(n) {
  return n <= 1 ? n : fastFib(n - 1) + fastFib(n - 2);
});

console.time('slow');
slowFib(35);  // 很慢
console.timeEnd('slow');

console.time('fast');
fastFib(35);  // 几乎瞬间
console.timeEnd('fast');

带过期的缓存

function memoizeWithTTL(fn, ttl = 60000) {
  const cache = new Map();

  return function(...args) {
    const key = JSON.stringify(args);
    const cached = cache.get(key);

    if (cached && Date.now() - cached.time < ttl) {
      return cached.value;
    }

    const result = fn.apply(this, args);
    cache.set(key, { value: result, time: Date.now() });
    return result;
  };
}

实用工具函数

防抖

function debounce(fn, delay) {
  let timer;

  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用
const handleSearch = debounce((query) => {
  console.log('Searching:', query);
}, 300);

节流

function throttle(fn, interval) {
  let lastTime = 0;

  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// 使用
const handleScroll = throttle(() => {
  console.log('Scrolling');
}, 100);

只执行一次

function once(fn) {
  let called = false;
  let result;

  return function(...args) {
    if (!called) {
      called = true;
      result = fn.apply(this, args);
    }
    return result;
  };
}

const initialize = once(() => {
  console.log('Initialized');
  return { ready: true };
});

initialize();  // 'Initialized' { ready: true }
initialize();  // { ready: true }(不再打印)

最佳实践总结

函数最佳实践:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   函数设计                                          │
│   ├── 单一职责原则                                 │
│   ├── 保持函数短小                                 │
│   ├── 避免副作用                                   │
│   └── 使用描述性命名                               │
│                                                     │
│   箭头函数使用                                      │
│   ├── 回调函数优先使用箭头函数                     │
│   ├── 对象方法使用普通函数                         │
│   └── 需要 this 时注意选择                         │
│                                                     │
│   性能优化                                          │
│   ├── 昂贵计算使用记忆化                           │
│   ├── 频繁调用使用防抖/节流                        │
│   └── 避免不必要的函数创建                         │
│                                                     │
└─────────────────────────────────────────────────────┘
概念用途示例
柯里化参数复用curry(fn)(a)(b)
组合函数串联compose(f, g)(x)
记忆化缓存结果memoize(fn)
防抖延迟执行debounce(fn, 300)

掌握函数特性,写出更优雅的 JavaScript 代码。