Javascript 零基础教程

JavaScript 函数作为返回值

从其他函数中返回函数是 JavaScript 的一项强大功能,也是函数式编程的基础。

它允许你创建动态且灵活的代码,其中函数的行为可以根据其创建时的上下文进行定制。

本章节将探讨“函数作为返回值”的概念,演示如何有效地创建和使用它们。

1. 理解函数作为返回值

在 JavaScript 中,函数是一等公民,这意味着它们可以像任何其他变量一样被对待。它们可以被赋值给变量,作为参数传递给其他函数(如上一章所见),也可以作为值从其他函数中返回。

当一个函数返回另一个函数时,被返回的函数可以“记住”它被创建时的环境。这个概念与闭包 (Closures) 密切相关。

1.1 基础示例

让我们从一个简单的例子开始:

function multiplier(factor) {
  // 这个函数返回另一个函数。
  return function(x) {
    // 被返回的函数将 'x' 乘以来自外部函数作用域的 'factor'。
    return x * factor;
  };
}

// 创建一个乘以 5 的函数
const multiplyBy5 = multiplier(5);

// 创建一个乘以 10 的函数
const multiplyBy10 = multiplier(10);

// 使用被返回的函数
console.log(multiplyBy5(3));  // 输出: 15
console.log(multiplyBy10(3)); // 输出: 30

在这个例子中,multiplier 是一个接受 factor 作为参数并返回一个函数的函数。被返回的函数接受一个参数 x 并返回 xfactor 的乘积。

注意 multiplyBy5 是如何“记住”factor 是 5,而 multiplyBy10 是如何“记住”factor 是 10 的,即使 multiplier 函数已经执行完毕。这是因为闭包的存在,但我们不需要深入研究闭包就能理解这个概念。这里的关键点是我们正在即时创建专用函数

1.2 更详细的示例

假设我们要创建一个函数,它可以生成带有不同前缀的问候消息(例如 "Hello", "Good morning", "Good evening")。

function createGreeter(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}

const sayHello = createGreeter("Hello");
const sayGoodMorning = createGreeter("Good morning");

console.log(sayHello("Alice"));       // 输出: Hello, Alice!
console.log(sayGoodMorning("Bob"));    // 输出: Good morning, Bob!

在这里,createGreeter 是一个接受 greeting 字符串作为输入并返回一个函数的函数。被返回的函数接受 name 作为输入并返回完整的问候消息。这使我们可以轻松创建具有不同前缀的不同问候函数。

1.3 带条件逻辑的示例

你也可以在外部函数中使用条件逻辑来决定返回哪个函数。

function createValidator(type) {
  if (type === 'email') {
    return function(value) {
      // 简单的电子邮件验证正则
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return emailRegex.test(value);
    };
  } else if (type === 'phoneNumber') {
    return function(value) {
      // 简单的电话号码验证正则 (美国格式)
      const phoneRegex = /^\d{3}-\d{3}-\d{4}$/;
      return phoneRegex.test(value);
    };
  } else {
    return function(value) {
      return true; // 默认验证:总是返回 true
    };
  }
}

const validateEmail = createValidator('email');
const validatePhone = createValidator('phoneNumber');
const validateAnything = createValidator('anything');

console.log(validateEmail("test@example.com")); // 输出: true
console.log(validateEmail("invalid-email"));   // 输出: false
console.log(validatePhone("123-456-7890")); // 输出: true
console.log(validatePhone("1234567890"));   // 输出: false
console.log(validateAnything("any value")); // 输出: true

在这个例子中,createValidator 接受一个 type 参数,并根据该类型返回一个验证函数。如果类型是 'email',它返回一个验证电子邮件地址的函数。如果类型是 'phoneNumber',它返回一个验证电话号码(特定格式)的函数。否则,它返回一个总是返回 true 的函数。

2. 实战示例与演示

2.1 创建计数器函数工厂

这是使用函数作为返回值的经典示例,常用于演示闭包。

function createCounter() {
  let count = 0; // 'count' 被封闭在 createCounter 的作用域中
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getValue: function() {
      return count;
    }
  };
}

const counter1 = createCounter();
const counter2 = createCounter();

console.log(counter1.increment()); // 输出: 1
console.log(counter1.increment()); // 输出: 2
console.log(counter2.increment()); // 输出: 1  (counter2 是独立的)
console.log(counter1.decrement()); // 输出: 1
console.log(counter1.getValue()); // 输出: 1

在这里,createCounter 返回一个包含三个函数的对象:incrementdecrementgetValue。使用 createCounter() 创建的每个计数器实例都有其自己独立的 count 变量,该变量无法从计数器对象外部访问。这是一种封装状态和行为的强大方式。返回的对象暴露了允许交互和修改该内部状态的方法。

2.2 使用返回函数进行柯里化 (Currying)

柯里化是一种将具有多个参数的函数转换为一系列函数的技术,每个函数只接受一个参数。从其他函数返回的函数通常用于实现柯里化。

function add(x) {
  return function(y) {
    return function(z) {
      return x + y + z;
    };
  };
}

const add5 = add(5);
const add5and10 = add5(10);
const result = add5and10(2);
console.log(result); // 输出: 17

// 或者,一次性调用所有函数:
console.log(add(5)(10)(2)); // 输出: 17

在这个例子中,add 是一个柯里化函数,它一次接受一个参数并返回一个新函数,直到提供所有参数。最后一个函数返回所有参数的总和。这对于通过预设某些参数来创建专用函数非常有用。