JavaScript函数


函数的定义与调用

在 JavaScript 中,定义函数有几种方式。

1. 函数声明 (Function Declaration)

这是最常见的定义函数的方式。

// 函数声明
function greet(name) {
  return "你好," + name + "!";
}

// 调用函数
console.log(greet("小明")); // 输出: 你好,小明!

特点:

  • 函数声明在执行上下文加载时就被解析,这意味着你可以在声明函数之前调用它(这被称为函数提升)。

    console.log(sayHello("张三")); // 输出: Hello, 张三!
    
    function sayHello(name) {
      return "Hello, " + name + "!";
    }
    

2. 函数表达式 (Function Expression)

函数表达式是将函数作为表达式的一部分创建的。通常,这意味着将一个匿名函数赋值给一个变量。

// 函数表达式
const sayGoodbye = function(name) {
  return "再见," + name + "!";
};

// 调用函数
console.log(sayGoodbye("李华")); // 输出: 再见,李华!

特点:

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

    // console.log(farewell("王五")); // 这会报错:ReferenceError: Cannot access 'farewell' before initialization
    
    const farewell = function(name) {
      return "Farewell, " + name + "!";
    };
    
    console.log(farewell("王五")); // 输出: Farewell, 王五!
    

3. 箭头函数 (Arrow Function)

ES6 (ECMAScript 2015) 引入了箭头函数,提供了一种更简洁的函数定义方式。

// 箭头函数
const add = (a, b) => a + b;

// 调用函数
console.log(add(5, 3)); // 输出: 8

// 带有多个参数或更复杂逻辑的箭头函数
const multiply = (x, y) => {
  const result = x * y;
  return `结果是:${result}`;
};
console.log(multiply(4, 6)); // 输出: 结果是:24

特点:

  • 语法简洁: 特别适用于简单的回调函数。
  • 没有自己的 this 箭头函数没有自己的 this 绑定,它会捕获其定义时的上下文的 this 值。这与普通函数(它们有自己的 this 绑定)不同。
  • 不能作为构造函数: 箭头函数不能使用 new 关键字来创建实例。
  • 没有 arguments 对象: 箭头函数没有自己的 arguments 对象,但可以使用剩余参数 (rest parameters) 来替代。

函数的参数

函数可以接受零个或多个参数。

1. 默认参数 (Default Parameters)

ES6 允许为函数参数指定默认值。

function greetUser(name = "访客") {
  return "欢迎," + name + "!";
}

console.log(greetUser("小红")); // 输出: 欢迎,小红!
console.log(greetUser());       // 输出: 欢迎,访客!

2. 剩余参数 (Rest Parameters)

剩余参数允许你将不定数量的参数表示为一个数组。

function sumAll(...numbers) {
  let total = 0;
  for (const num of numbers) {
    total += num;
  }
  return total;
}

console.log(sumAll(1, 2, 3));         // 输出: 6
console.log(sumAll(10, 20, 30, 40));  // 输出: 100

3. arguments 对象 (在非箭头函数中)

在传统的(非箭头)函数中,arguments 是一个类数组对象,包含了函数被调用时传入的所有参数。

function showArgs() {
  console.log(arguments);
}

showArgs(1, "hello", true);
// 输出: [Arguments] { '0': 1, '1': 'hello', '2': true }

注意: 箭头函数没有 arguments 对象。


函数的返回值

函数可以使用 return 语句返回一个值。如果没有 return 语句,或者 return 语句后面没有表达式,函数会隐式返回 undefined

function calculateArea(width, height) {
  if (width <= 0 || height <= 0) {
    return "宽度和高度必须是正数!"; // 返回错误信息
  }
  return width * height; // 返回计算结果
}

console.log(calculateArea(10, 5)); // 输出: 50
console.log(calculateArea(-2, 3)); // 输出: 宽度和高度必须是正数!
console.log(calculateArea(7, 0)); // 输出: 宽度和高度必须是正数!

function doNothing() {
  // 没有return语句
}
console.log(doNothing()); // 输出: undefined

函数作为一等公民 (First-Class Functions)

在 JavaScript 中,函数被视为“一等公民”,这意味着它们可以:

  1. 赋值给变量 (如函数表达式所示)。
  2. 作为参数传递给其他函数 (高阶函数)。
  3. 作为函数的返回值 (高阶函数)。

作为参数传递 (回调函数)

function operate(num1, num2, operation) {
  return operation(num1, num2);
}

const addOperation = (a, b) => a + b;
const subtractOperation = (a, b) => a - b;

console.log(operate(10, 5, addOperation));      // 输出: 15
console.log(operate(10, 5, subtractOperation)); // 输出: 5

// 也可以直接传入匿名函数
console.log(operate(8, 2, (x, y) => x * y)); // 输出: 16

这里的 operation 就是一个回调函数 (Callback Function),它在 operate 函数内部被调用。

作为返回值 (闭包)

当一个函数返回另一个函数时,并且内部函数仍然可以访问外部函数的变量时,就形成了闭包 (Closure)

function createCounter() {
  let count = 0; // 这是一个局部变量

  return function() { // 返回一个匿名函数
    count++;
    return count;
  };
}

const counter1 = createCounter();
console.log(counter1()); // 输出: 1
console.log(counter1()); // 输出: 2

const counter2 = createCounter(); // 创建一个新的计数器实例
console.log(counter2()); // 输出: 1
console.log(counter1()); // 输出: 3 (counter1 保持自己的状态)

在这个例子中,匿名函数形成了闭包,即使 createCounter 已经执行完毕,它仍然能够访问和修改 count 变量。


立即执行函数表达式 (IIFE - Immediately Invoked Function Expression)

IIFE 是一种定义后立即执行的函数。它常用于创建独立的作用域,避免变量污染全局环境。

(function() {
  const message = "这是一个IIFE的私有变量";
  console.log(message); // 输出: 这是一个IIFE的私有变量
})();

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

递归函数

递归函数是指在函数内部调用自身的函数。通常用于解决可以分解为相同子问题的问题。

function factorial(n) {
  // 基本情况 (终止条件)
  if (n === 0 || n === 1) {
    return 1;
  }
  // 递归调用
  return n * factorial(n - 1);
}

console.log(factorial(5)); // 输出: 120 (5 * 4 * 3 * 2 * 1)
console.log(factorial(0)); // 输出: 1

注意: 编写递归函数时,必须有一个基本情况 (base case) 来终止递归,否则会导致无限循环(栈溢出)。