JavaScript Function 函数

简介

函数是 JavaScript 中的一等公民(First-class Citizen),意味着函数可以像变量一样被传递、赋值和返回。函数用于封装可复用的代码块。


函数定义方式

JavaScript 中有三种主要的函数定义方式。

1. 函数声明(Function Declaration)

使用 function 关键字直接定义函数。

function greet(name) {
  return 'Hello, ' + name + '!';
}

console.log(greet('Alice')); // Hello, Alice!

特点:函数提升(Hoisting)

函数声明会被提升到作用域顶部,可以在定义之前调用。

console.log(add(2, 3)); // 5(可以在定义前调用)

function add(a, b) {
  return a + b;
}

2. 函数表达式(Function Expression)

将函数赋值给变量。

const greet = function(name) {
  return 'Hello, ' + name + '!';
};

console.log(greet('Bob')); // Hello, Bob!

// 也可以给函数命名(用于递归等场景)
const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1);
};

特点:没有提升

函数表达式不会被提升,必须在定义之后才能调用。

console.log(multiply(2, 3)); // TypeError: multiply is not a function

const multiply = function(a, b) {
  return a * b;
};

3. 箭头函数(Arrow Function,ES6)

ES6 引入的简洁语法。

// 基本语法
const greet = (name) => {
  return 'Hello, ' + name + '!';
};

// 单个参数时,括号可以省略
const double = x => x * 2;

// 函数体只有一条 return 语句时,可以省略花括号和 return
const add = (a, b) => a + b;

console.log(add(2, 3));   // 5
console.log(double(5));    // 10

特点:没有自己的 thisargumentssupernew.target

// 箭头函数继承外层作用域的 this
const obj = {
  value: 10,
  getValue: function() {
    // 普通函数:this 指向调用者(obj)
    return this.value;
  },
  getValueArrow: () => {
    // 箭头函数:this 指向外层作用域(这里是全局或 undefined)
    return this.value;  // 可能不是你期望的
  }
};

console.log(obj.getValue());       // 10
console.log(obj.getValueArrow());  // undefined(或报错)

三种方式对比

特性 函数声明 函数表达式 箭头函数
语法 function foo() {} const foo = function() {} const foo = () => {}
提升 有(可先调用)
自己的 this 无(继承外层)
arguments 对象 无(用剩余参数替代)
适合作为方法 否(不适合)
适合作为回调 可以 可以 非常适合(简洁)

函数调用

基本调用

function sayHello() {
  console.log('Hello!');
}

sayHello();  // 调用函数

作为方法调用(this 指向)

当函数作为对象的方法调用时,this 指向该对象。

const person = {
  name: 'Alice',
  greet: function() {
    console.log('Hello, I am ' + this.name);
  }
};

person.greet();  // Hello, I am Alice(this 指向 person)

使用 call、apply、bind 改变 this

function introduce(age, city) {
  console.log(`我是 ${this.name},${age} 岁,来自 ${city}`);
}

const person = { name: 'Alice' };

// call:逐个传入参数
introduce.call(person, 25, 'Beijing');
// 我是 Alice,25 岁,来自 Beijing

// apply:以数组形式传入参数
introduce.apply(person, [30, 'Shanghai']);
// 我是 Alice,30 岁,来自 Shanghai

// bind:返回一个新函数,永久绑定 this
const introAlice = introduce.bind(person);
introAlice(28, 'Guangzhou');
// 我是 Alice,28 岁,来自 Guangzhou

参数

形参和实参

// 形参(定义时的参数)
function add(a, b) {
  return a + b;
}

// 实参(调用时传入的参数)
console.log(add(2, 3));  // 2 和 3 是实参

参数数量不匹配

function multiply(a, b) {
  console.log('a:', a, 'b:', b);
  return a * b;
}

console.log(multiply(2, 3));    // a: 2 b: 3 → 6
console.log(multiply(2));        // a: 2 b: undefined → NaN(b 未传入)
console.log(multiply(2, 3, 4)); // a: 2 b: 3 → 6(多出的参数被忽略)

默认参数(ES6)

// 传统方式
function greet(name) {
  name = name || 'Guest';
  return 'Hello, ' + name;
}

// ES6 默认参数
function greet(name = 'Guest') {
  return 'Hello, ' + name;
}

console.log(greet('Alice')); // Hello, Alice
console.log(greet());        // Hello, Guest
console.log(greet(undefined)); // Hello, Guest(undefined 触发默认值)
console.log(greet(null));    // Hello, null(null 不会触发默认值)

剩余参数(Rest Parameters,ES6)

// 收集所有剩余参数为一个数组
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

// 与普通参数结合
function multiply(multiplier, ...numbers) {
  return numbers.map(n => n * multiplier);
}

console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]

arguments 对象(传统方式)

arguments 是一个类数组对象,包含传入的所有参数。箭头函数中没有 arguments

// 传统函数
function oldSum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(oldSum(1, 2, 3, 4)); // 10

// 箭头函数中不可用
const arrowSum = () => {
  // console.log(arguments);  // ReferenceError
};

// 箭头函数应使用剩余参数
const newSum = (...args) => {
  return args.reduce((a, b) => a + b, 0);
};

返回值(return)

基本返回值

function add(a, b) {
  return a + b;  // 返回计算结果
}

let result = add(2, 3);
console.log(result); // 5

没有 return 时返回 undefined

function sayHello() {
  console.log('Hello!');
  // 没有 return
}

let result = sayHello();
console.log(result); // undefined

返回多个值

// 方式1:返回一个对象
function getPerson() {
  return {
    name: 'Alice',
    age: 25,
    city: 'Beijing'
  };
}

let person = getPerson();
console.log(person.name); // Alice
console.log(person.age);  // 25

// 使用解构赋值
let { name, age } = getPerson();
console.log(name, age); // Alice 25

// 方式2:返回一个数组
function getMinMax(arr) {
  return [Math.min(...arr), Math.max(...arr)];
}

let [min, max] = getMinMax([3, 1, 4, 1, 5]);
console.log(min, max); // 1 5

// 方式3:通过参数(不推荐,难以理解)
function getValues(out) {
  out.name = 'Bob';
  out.age = 30;
}
let obj = {};
getValues(obj);
console.log(obj); // { name: 'Bob', age: 30 }

return 的注意事项

// return 后面换行会导致返回 undefined(ASI 自动分号插入)
function bad() {
  return
    { name: 'Alice' };  // 永远不会执行到这里
}

console.log(bad()); // undefined

// 正确写法:保持在同一行或括号紧跟
function good() {
  return { name: 'Alice' };
}

console.log(good()); // { name: 'Alice' }

作用域和闭包

函数作用域

函数内部可以访问外部变量,但外部不能访问函数内部变量。

let globalVar = 'global';

function outer() {
  let outerVar = 'outer';

  function inner() {
    let innerVar = 'inner';
    console.log(globalVar);  // 可以访问:global
    console.log(outerVar);  // 可以访问:outer
    console.log(innerVar);  // 可以访问:inner
  }

  inner();
  // console.log(innerVar);  // ReferenceError: innerVar is not defined
}

outer();
// console.log(outerVar);   // ReferenceError: outerVar is not defined

闭包(Closure)

闭包是指函数可以记住并访问它的词法作用域,即使函数在其词法作用域外执行。

function outer() {
  let count = 0;  // 这个变量被 inner 函数"记住"了

  function inner() {
    count++;
    console.log(count);
  }

  return inner;  // 返回 inner 函数
}

let counter = outer();
counter();  // 1
counter();  // 2
counter();  // 3

// 每次调用 counter,count 的值都被保留了下来
// 这就是闭包:inner 函数"关闭"了 outer 的变量

闭包的实际应用

1. 数据封装(私有变量)

function createCounter() {
  let count = 0;  // 私有变量,外部无法直接访问

  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

let counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2
// console.log(counter.count);    // undefined(无法直接访问)

2. 函数工厂

function multiplyBy(factor) {
  return function(number) {
    return number * factor;
  };
}

let double = multiplyBy(2);
let triple = multiplyBy(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

3. 防抖(debounce)

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

// 使用
let debouncedSearch = debounce((query) => {
  console.log('搜索:', query);
}, 500);

debouncedSearch('hello');
debouncedSearch('hello world');  // 只会在 500ms 后执行最后一次

4. 节流(throttle)

function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

高阶函数

高阶函数是指接受函数作为参数返回函数的函数。

函数作为参数

function greet(name) {
  return 'Hello, ' + name;
}

function processUser(name, callback) {
  return callback(name);
}

console.log(processUser('Alice', greet)); // Hello, Alice

// 使用箭头函数
console.log(processUser('Bob', (name) => 'Hi, ' + name)); // Hi, Bob

函数作为返回值

function createGreeter(greeting) {
  return function(name) {
    return greeting + ', ' + name + '!';
  };
}

let sayHello = createGreeter('Hello');
let sayHi = createGreeter('Hi');

console.log(sayHello('Alice'));  // Hello, Alice!
console.log(sayHi('Bob'));      // Hi, Bob!

内置的高阶函数

数组的 mapfilterreduce 等都是高阶函数。

let numbers = [1, 2, 3, 4, 5];

// map:函数作为参数
let doubled = numbers.map(function(n) { return n * 2; });
console.log(doubled); // [2, 4, 6, 8, 10]

// filter:函数作为参数
let evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]

// reduce:函数作为参数,返回累积结果
let sum = numbers.reduce((total, n) => total + n, 0);
console.log(sum); // 15

// sort:函数作为参数,定义排序规则
let unsorted = [3, 1, 4, 1, 5];
unsorted.sort((a, b) => a - b);  // 升序
console.log(unsorted); // [1, 1, 3, 4, 5]

递归

递归是指函数调用自身。

基本示例:阶乘

// 递归方式
function factorial(n) {
  if (n <= 1) return 1;  // 基线条件(停止递归)
  return n * factorial(n - 1);  // 递归调用
}

console.log(factorial(5));  // 120
// 执行过程:5 * 4 * 3 * 2 * 1 = 120

// 迭代方式(对比)
function factorialIterative(n) {
  let result = 1;
  for (let i = 2; i <= n; i++) {
    result *= i;
  }
  return result;
}

斐波那契数列

// 递归方式(直观但低效,有重复计算)
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(6));  // 8

// 优化:使用记忆化
function fibonacciMemo(n, memo = {}) {
  if (n <= 1) return n;
  if (memo[n]) return memo[n];
  memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
  return memo[n];
}

console.log(fibonacciMemo(10));  // 55

递归的注意事项

// 危险:没有基线条件会导致栈溢出
// function infinite() {
//   return infinite();  // RangeError: Maximum call stack size exceeded
// }

// 递归深度过深也会栈溢出
// function deep(n) {
//   if (n <= 0) return;
//   deep(n - 1);
// }
// deep(100000);  // 可能栈溢出

回调函数(Callback)

回调函数是指作为参数传递给另一个函数,并在某个时刻被调用的函数。

同步回调

function processData(data, callback) {
  let result = data.toUpperCase();
  callback(result);  // 同步调用回调
}

processData('hello', (result) => {
  console.log('处理结果:', result);  // 处理结果: HELLO
});

异步回调

// 模拟异步操作
function fetchData(callback) {
  setTimeout(() => {
    callback('数据加载完成');
  }, 1000);
}

console.log('开始加载...');
fetchData((result) => {
  console.log(result);  // 1秒后:数据加载完成
});
console.log('继续做其他事情...');
// 输出顺序:
// 开始加载...
// 继续做其他事情...
// 数据加载完成

回调地狱(Callback Hell)

// 多层嵌套的回调(难以维护)
function step1(callback) {
  setTimeout(() => callback('步骤1完成'), 1000);
}

function step2(data, callback) {
  setTimeout(() => callback(data + ' → 步骤2完成'), 1000);
}

function step3(data, callback) {
  setTimeout(() => callback(data + ' → 步骤3完成'), 1000);
}

// 回调地狱
step1((result1) => {
  console.log(result1);
  step2(result1, (result2) => {
    console.log(result2);
    step3(result2, (result3) => {
      console.log(result3);
      // 如果还有更多,会嵌套更深...
    });
  });
});

// 现代解决方案:使用 Promise 或 async/await
// step1()
//   .then(step2)
//   .then(step3)
//   .then(console.log);

立即执行函数(IIFE)

Immediately Invoked Function Expression,定义后立即执行的函数。

语法

(function() {
  // 代码
})();

// 或
(function() {
  // 代码
}());

用途:创建私有作用域

// 问题:变量污染全局作用域
var x = 10;
var y = 20;
function helper() { /* ... */ }
// x、y、helper 都在全局作用域中

// 解决:使用 IIFE 创建私有作用域
(function() {
  var x = 10;
  var y = 20;
  function helper() { /* ... */ }
  console.log(x + y);  // 30
})();

// console.log(x);  // ReferenceError: x is not defined

传递参数给 IIFE

(function(global, name) {
  console.log('Hello, ' + name);
  global.myVar = 'exported';  // 小心地导出到全局
})(window, 'Alice');

现代替代方案

// ES6 模块和块级作用域使得 IIFE 不那么必要了
{
  let x = 10;
  let y = 20;
  console.log(x + y);  // 30(使用块级作用域)
}
// x 和 y 在这里不可访问

综合示例

示例 1:函数组合(Pipe)

// 组合多个函数
function pipe(...fns) {
  return function(value) {
    return fns.reduce((current, fn) => fn(current), value);
  };
}

const add10 = (x) => x + 10;
const multiply2 = (x) => x * 2;
const subtract5 = (x) => x - 5;

const process = pipe(add10, multiply2, subtract5);

console.log(process(5));  // (5 + 10) * 2 - 5 = 25
console.log(process(10)); // (10 + 10) * 2 - 5 = 35

示例 2:简易事件发射器

function createEventEmitter() {
  let events = {};

  return {
    on(event, callback) {
      if (!events[event]) events[event] = [];
      events[event].push(callback);
    },
    emit(event, data) {
      if (events[event]) {
        events[event].forEach(callback => callback(data));
      }
    },
    off(event, callback) {
      if (events[event]) {
        events[event] = events[event].filter(cb => cb !== callback);
      }
    }
  };
}

let emitter = createEventEmitter();

emitter.on('message', (data) => console.log('收到:', data));
emitter.emit('message', 'Hello!');  // 收到: Hello!

示例 3:数据验证器

function createValidator() {
  let rules = [];

  return {
    addRule(rule) {
      rules.push(rule);
      return this;  // 支持链式调用
    },
    validate(data) {
      let errors = [];
      for (let rule of rules) {
        let result = rule(data);
        if (result) errors.push(result);
      }
      return {
        isValid: errors.length === 0,
        errors
      };
    }
  };
}

// 定义验证规则
const required = (data) => !data.name ? '姓名不能为空' : null;
const minAge = (data) => data.age < 18 ? '必须年满18岁' : null;

let validator = createValidator()
  .addRule(required)
  .addRule(minAge);

console.log(validator.validate({ name: 'Alice', age: 16 }));
// { isValid: false, errors: ["必须年满18岁"] }

示例 4:简易缓存函数

function memoize(fn) {
  let cache = new Map();

  return function(...args) {
    let key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log('从缓存读取:', key);
      return cache.get(key);
    }
    let result = fn.apply(this, args);
    cache.set(key, result);
    console.log('计算并缓存:', key);
    return result;
  };
}

// 使用
const slowAdd = (a, b) => {
  // 模拟耗时操作
  return a + b;
};

const memoizedAdd = memoize(slowAdd);

console.log(memoizedAdd(1, 2));  // 计算并缓存: [1,2] → 3
console.log(memoizedAdd(1, 2));  // 从缓存读取: [1,2] → 3

总结:函数速查表

概念 语法/说明
函数声明 function foo() {} — 有提升
函数表达式 const foo = function() {} — 无提升
箭头函数 const foo = () => {} — 无 this,简洁
默认参数 function foo(x = 0) {}
剩余参数 function foo(...args) {}
返回值 return value — 无 return 则返回 undefined
闭包 函数"记住"其词法作用域
高阶函数 接受或返回函数的函数
递归 函数调用自身
回调 作为参数传递的函数
IIFE (function() {})() — 立即执行
this 绑定 call、apply、bind