JavaScript闭合的例子(附代码)

209 阅读5分钟

最终,你将会接触到JavaScript闭包的概念。我想给你一个逐步的演练,告诉你如何实现一个JavaScript闭包。在这个过程中,你会发现为什么用JavaScript Closure实现某些东西是有意义的。整个源代码可以在GitHub上找到。如果你想沿着这条路进行编码,请确保在之前建立一个JavaScript项目

为什么要用JavaScript闭包?

假设我们有下面这个JavaScript函数,它只是为我们返回一个对象。这个对象的属性是基于传入函数的参数:

function getEmployee(name, country) {
  return { name, country };
}

const employeeOne = getEmployee('Robin', 'Germany');
const employeeTwo = getEmployee('Markus', 'Canada');

const employees = [employeeOne, employeeTwo];

在我们的例子中,该函数为一个雇员对象创建一个对象。这个函数可以用来逐一创建多个对象。之后如何处理这些对象由你自己决定。例如,把它们放在一个数组中,得到你公司的雇员名单。

为了区分我们的雇员,我们应该给他们一个雇员编号(标识符)。这个标识符应该在内部分配 -- 因为从外部调用函数时,我们不希望关心这个数字:

function getEmployee(name, country) {
  let employeeNumber = 1;
  return { employeeNumber, name, country };
}

const employeeOne = getEmployee('Robin', 'Germany');
const employeeTwo = getEmployee('Markus', 'Canada');

const employees = [employeeOne, employeeTwo];

console.log(employees);

// [
//   { employeeNumber: 1, name: 'Robin', country: 'Germany' },
//   { employeeNumber: 1, name: 'Markus', country: 'Canada' },
// ]

目前,每个雇员都有一个1的雇员编号,这是不对的。它应该是一个唯一的标识符。通常情况下,一个员工的编号只是为公司中每一个加入的员工递增一个。然而,如果不能从外部做一些事情,这个函数就不知道它已经创建了多少个雇员。它并没有保持状态的跟踪。

由于函数不保留任何内部状态,我们需要将变量移到函数之外,在函数内为每一个创建的雇员增量。我们通过在每次函数被调用时递增这个数字来保持状态。

let employeeNumber = 1;

function getEmployee(name, country) {
  return { employeeNumber: employeeNumber++, name, country };
}

const employeeOne = getEmployee('Robin', 'Germany');
const employeeTwo = getEmployee('Markus', 'Canada');

const employees = [employeeOne, employeeTwo];

console.log(employees);

// [
//   { employeeNumber: 1, name: 'Robin', country: 'Germany' },
//   { employeeNumber: 2, name: 'Markus', country: 'Canada' },
// ]

注意:++运算符(称为增量运算符)将一个整数增加1。如果使用后缀(例如:myInteger++ ),它将增加整数,但返回增加前的值。如果使用前缀(例如:++myInteger ),它将增加整数并返回增加后的值。相比之下,JavaScript中也存在一个递减操作符

为了实现这个功能,我们做了一个关键步骤。我们把这个变量移到了函数的范围之外,以便跟踪它的状态。之前它是由函数内部管理的,因此只有函数知道这个变量。现在我们把它移到外面,让它在全局范围内可用。

现在有可能用新的全局范围的变量把事情搞乱。

let employeeNumber = 1;

function getEmployee(name, country) {
  return { employeeNumber: employeeNumber++, name, country };
}

const employeeOne = getEmployee('Robin', 'Germany');
employeeNumber = 50;
const employeeTwo = getEmployee('Markus', 'Canada');

const employees = [employeeOne, employeeTwo];

console.log(employees);

// [
//   { employeeNumber: 1, name: 'Robin', country: 'Germany' },
//   { employeeNumber: 50, name: 'Markus', country: 'Canada' },
// ]

之前这是不可能的,因为雇员编号被隐藏在函数的作用域中--由于变量的作用域,在函数的外部环境中是无法访问的。尽管我们的功能是有效的,但前面的代码片段清楚地表明,我们在这里有一个潜在的陷阱。

我们在之前的代码片断中所做的一切,都是在将我们的变量的作用域从函数的作用域变为全局作用域。一个JavaScript闭包将解决我们的变量的作用域问题,使其不能从函数的外部访问,但使函数可以跟踪其内部状态。从根本上说,编程中作用域的存在为闭包提供了呼吸的空间。

例子中的JavaScript闭包

一个JavaScript闭包解决了我们的变量范围的问题。闭包使我们有可能用函数中的一个变量来跟踪内部状态,而不需要放弃这个变量的局部范围。

function getEmployeeFactory() {
  let employeeNumber = 1;
  return function(name, country) {
    return { employeeNumber: employeeNumber++, name, country };
  };
}

const getEmployee = getEmployeeFactory();

const employeeOne = getEmployee('Robin', 'Germany');
const employeeTwo = getEmployee('Markus', 'Canada');

const employees = [employeeOne, employeeTwo];

console.log(employees);

// [
//   { employeeNumber: 1, name: 'Robin', country: 'Germany' },
//   { employeeNumber: 2, name: 'Markus', country: 'Canada' },
// ]

新的函数变成了一个高阶函数,因为第一次调用它就会返回一个函数。这个返回的函数可以像我们之前那样用来创建我们的雇员。然而,由于周围的函数在返回的函数周围创建了一个有状态的环境--在这种情况下是有状态的雇员编号--所以它被称为闭包。

"闭包 "是指指向独立(自由)变量的函数。换句话说,闭包中定义的函数'记住'了创建它的环境"。(来源:MDN网页文档)

从外面看,不可能再去搞什么雇员编号了。它不在全局范围内,而是在我们函数的闭包中。一旦你创建了你的getEmployee 函数,你可以给它起任何名字,雇员编号就会作为状态在内部保存。

注意:值得一提的是,前面我们的例子中的JavaScript闭包的实现在软件开发中也被称为 "工厂模式"。基本上,外部函数是我们的工厂函数,内部函数是我们从这个工厂的规范中创建一个 "项目"(这里是雇员)的函数。


我希望这个简短的演练能够帮助你理解一个JavaScript闭包的例子。我们从我们的问题入手--变量的范围和对函数内部状态的跟踪--并通过为其实现一个闭包来摆脱这个问题。