闭包

闭包是打包在一起(封闭)的函数及其周围状态(词法环境)的引用的组合。换句话说,闭包使你可以从内部函数访问外部函数的作用域。在 JavaScript 中,每次创建函数时都会创建闭包。

¥A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

词汇范围

¥Lexical scoping

考虑以下示例代码:

¥Consider the following example code:

js
function init() {
  var name = "Mozilla"; // name is a local variable created by init
  function displayName() {
    // displayName() is the inner function, that forms the closure
    console.log(name); // use variable declared in the parent function
  }
  displayName();
}
init();

init() 创建一个名为 name 的局部变量和一个名为 displayName() 的函数。displayName() 函数是在 init() 内部定义的内部函数,并且仅在 init() 函数体内可用。请注意,displayName() 函数没有自己的局部变量。但是,由于内部函数可以访问外部函数的变量,因此 displayName() 可以访问在父函数 init() 中声明的变量 name

¥init() creates a local variable called name and a function called displayName(). The displayName() function is an inner function that is defined inside init() and is available only within the body of the init() function. Note that the displayName() function has no local variables of its own. However, since inner functions have access to the variables of outer functions, displayName() can access the variable name declared in the parent function, init().

使用 这个 JSFiddle 链接 运行代码,并注意 displayName() 函数中的 console.log() 语句成功显示了 name 变量的值,该变量是在其父函数中声明的。这是词法作用域的一个示例,它描述了解析器在函数嵌套时如何解析变量名称。词法一词指的是词法作用域使用源代码中声明变量的位置来确定该变量的可用位置。嵌套函数可以访问在其外部作用域中声明的变量。

¥Run the code using this JSFiddle link and notice that the console.log() statement within the displayName() function successfully displays the value of the name variable, which is declared in its parent function. This is an example of lexical scoping, which describes how a parser resolves variable names when functions are nested. The word lexical refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available. Nested functions have access to variables declared in their outer scope.

在这个特定的示例中,该作用域称为函数作用域,因为该变量是可访问的,并且只能在声明它的函数体内访问。

¥In this particular example, the scope is called a function scope, because the variable is accessible and only accessible within the function body where it's declared.

使用 let 和 const 确定作用域

¥Scoping with let and const

传统上(ES6 之前),JavaScript 只有两种作用域:函数作用域和全局作用域。使用 var 声明的变量可以是函数范围的变量,也可以是全局范围的变量,具体取决于它们是在函数内部还是在函数外部声明。这可能很棘手,因为带有大括号的块不会创建作用域:

¥Traditionally (before ES6), JavaScript only had two kinds of scopes: function scope and global scope. Variables declared with var are either function-scoped or global-scoped, depending on whether they are declared within a function or outside a function. This can be tricky, because blocks with curly braces do not create scopes:

js
if (Math.random() > 0.5) {
  var x = 1;
} else {
  var x = 2;
}
console.log(x);

对于使用块创建作用域的其他语言(例如 C、Java)的人来说,上面的代码应该在 console.log 行上抛出错误,因为我们超出了任一块中 x 的作用域。但是,由于块不会为 var 创建作用域,因此此处的 var 语句实际上创建了一个全局变量。下面还介绍了 一个实际的例子,它说明了与闭包结合使用时如何导致实际的错误。

¥For people from other languages (e.g. C, Java) where blocks create scopes, the above code should throw an error on the console.log line, because we are outside the scope of x in either block. However, because blocks don't create scopes for var, the var statements here actually create a global variable. There is also a practical example introduced below that illustrates how this can cause actual bugs when combined with closures.

在 ES6 中,JavaScript 引入了 letconst 声明,除了 暂时性死区 之外,它们还允许你创建块作用域变量。

¥In ES6, JavaScript introduced the let and const declarations, which, among other things like temporal dead zones, allow you to create block-scoped variables.

js
if (Math.random() > 0.5) {
  const x = 1;
} else {
  const x = 2;
}
console.log(x); // ReferenceError: x is not defined

本质上,块最终在 ES6 中被视为作用域,但前提是你使用 letconst 声明变量。另外,ES6 引入了 modules,它引入了另一种作用域。闭包能够捕获所有这些作用域中的变量,我们稍后会介绍。

¥In essence, blocks are finally treated as scopes in ES6, but only if you declare variables with let or const. In addition, ES6 introduced modules, which introduced another kind of scope. Closures are able to capture variables in all these scopes, which we will introduce later.

关闭

¥Closure

考虑以下代码示例:

¥Consider the following code example:

js
function makeFunc() {
  const name = "Mozilla";
  function displayName() {
    console.log(name);
  }
  return displayName;
}

const myFunc = makeFunc();
myFunc();

运行此代码与上面的 init() 函数的前面示例具有完全相同的效果。不同(且有趣)的是 displayName() 内部函数在执行之前从外部函数返回。

¥Running this code has exactly the same effect as the previous example of the init() function above. What's different (and interesting) is that the displayName() inner function is returned from the outer function before being executed.

乍一看,这段代码仍然有效似乎不太直观。在某些编程语言中,函数内的局部变量仅在该函数执行期间存在。一旦 makeFunc() 完成执行,你可能会认为 name 变量将不再可访问。然而,由于代码仍然按预期工作,因此 JavaScript 中的情况显然不是这样。

¥At first glance, it might seem unintuitive that this code still works. In some programming languages, the local variables within a function exist for just the duration of that function's execution. Once makeFunc() finishes executing, you might expect that the name variable would no longer be accessible. However, because the code still works as expected, this is obviously not the case in JavaScript.

原因是 JavaScript 中的函数形成了闭包。闭包是函数和声明该函数的词法环境的组合。该环境由创建闭包时范围内的任何局部变量组成。在本例中,myFunc 是对运行 makeFunc 时创建的函数 displayName 实例的引用。displayName 的实例维护对其词法环境的引用,其中存在变量 name。因此,当调用 myFunc 时,变量 name 仍然可用,而 "Mozilla" 则传递给 console.log

¥The reason is that functions in JavaScript form closures. A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created. In this case, myFunc is a reference to the instance of the function displayName that is created when makeFunc is run. The instance of displayName maintains a reference to its lexical environment, within which the variable name exists. For this reason, when myFunc is invoked, the variable name remains available for use, and "Mozilla" is passed to console.log.

这是一个稍微有趣的例子 - makeAdder 函数:

¥Here's a slightly more interesting example—a makeAdder function:

js
function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2)); // 7
console.log(add10(2)); // 12

在此示例中,我们定义了一个函数 makeAdder(x),它接受单个参数 x,并返回一个新函数。它返回的函数接受单个参数 y,并返回 xy 的和。

¥In this example, we have defined a function makeAdder(x), that takes a single argument x, and returns a new function. The function it returns takes a single argument y, and returns the sum of x and y.

本质上,makeAdder 是一个函数工厂。它创建可以为其参数添加特定值的函数。在上面的示例中,函数工厂创建了两个新函数,一个将其参数加 5,另一个将其参数加 10。

¥In essence, makeAdder is a function factory. It creates functions that can add a specific value to their argument. In the above example, the function factory creates two new functions—one that adds five to its argument, and one that adds 10.

add5add10 均形成闭包。它们共享相同的函数体定义,但存储不同的词法环境。在 add5 的词法环境中,x 是 5,而在 add10 的词法环境中,x 是 10。

¥add5 and add10 both form closures. They share the same function body definition, but store different lexical environments. In add5's lexical environment, x is 5, while in the lexical environment for add10, x is 10.

实用的闭包

¥Practical closures

闭包很有用,因为它们允许你将数据(词法环境)与对该数据进行操作的函数关联起来。这与面向对象编程有明显的相似之处,在面向对象编程中,对象允许你将数据(对象的属性)与一个或多个方法关联起来。

¥Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow you to associate data (the object's properties) with one or more methods.

因此,你可以在通常仅使用单个方法的对象的任何地方使用闭包。

¥Consequently, you can use a closure anywhere that you might normally use an object with only a single method.

你可能想要执行此操作的情况在网络上尤其常见。用前端 JavaScript 编写的大部分代码都是基于事件的。你定义一些行为,然后将其附加到用户触发的事件(例如单击或按键)。该代码作为回调(响应事件而执行的单个函数)附加。

¥Situations where you might want to do this are particularly common on the web. Much of the code written in front-end JavaScript is event-based. You define some behavior, and then attach it to an event that is triggered by the user (such as a click or a keypress). The code is attached as a callback (a single function that is executed in response to the event).

例如,假设我们要向页面添加按钮来调整文本大小。一种方法是指定 body 元素的字体大小(以像素为单位),然后使用相对 em 单位设置页面上其他元素(例如标题)的大小:

¥For instance, suppose we want to add buttons to a page to adjust the text size. One way of doing this is to specify the font-size of the body element (in pixels), and then set the size of the other elements on the page (such as headers) using the relative em unit:

css
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

此类交互式文本大小按钮可以更改 body 元素的 font-size 属性,并且由于相对单位,页面上的其他元素会进行调整。

¥Such interactive text size buttons can change the font-size property of the body element, and the adjustments are picked up by other elements on the page thanks to the relative units.

这是 JavaScript:

¥Here's the JavaScript:

js
function makeSizer(size) {
  return function () {
    document.body.style.fontSize = `${size}px`;
  };
}

const size12 = makeSizer(12);
const size14 = makeSizer(14);
const size16 = makeSizer(16);

size12size14size16 现在分别是将正文文本大小调整为 12、14 和 16 像素的函数。你可以将它们附加到按钮,如以下代码示例所示。

¥size12, size14, and size16 are now functions that resize the body text to 12, 14, and 16 pixels, respectively. You can attach them to buttons as demonstrated in the following code example.

js
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;
html
<button id="size-12">12</button>
<button id="size-14">14</button>
<button id="size-16">16</button>

使用 JSFiddle 运行代码。

¥Run the code using JSFiddle.

使用闭包模拟私有方法

¥Emulating private methods with closures

Java 等语言允许将方法声明为私有,这意味着它们只能由同一类中的其他方法调用。

¥Languages such as Java allow you to declare methods as private, meaning that they can be called only by other methods in the same class.

classes 之前,JavaScript 没有声明 私有方法 的原生方法,但可以使用闭包来模拟私有方法。私有方法不仅可用于限制对代码的访问。它们还提供了管理全局命名空间的强大方法。

¥JavaScript, prior to classes, didn't have a native way of declaring private methods, but it was possible to emulate private methods using closures. Private methods aren't just useful for restricting access to code. They also provide a powerful way of managing your global namespace.

下面的代码演示了如何使用闭包来定义可以访问私有函数和变量的公共函数。请注意,这些关闭遵循 模块设计模式

¥The following code illustrates how to use closures to define public functions that can access private functions and variables. Note that these closures follow the Module Design Pattern.

js
const counter = (function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.

在前面的示例中,每个闭包都有自己的词法环境。不过,这里有一个由三个函数共享的词法环境:counter.incrementcounter.decrementcounter.value

¥In previous examples, each closure had its own lexical environment. Here though, there is a single lexical environment that is shared by the three functions: counter.increment, counter.decrement, and counter.value.

共享词法环境是在匿名函数的主体中创建的,该函数在定义后立即执行(也称为 IIFE)。词法环境包含两个私有项:一个名为 privateCounter 的变量和一个名为 changeBy 的函数。你无法从匿名函数外部访问这些私有成员中的任何一个。相反,你可以使用从匿名封装器返回的三个公共函数来访问它们。

¥The shared lexical environment is created in the body of an anonymous function, which is executed as soon as it has been defined (also known as an IIFE). The lexical environment contains two private items: a variable called privateCounter, and a function called changeBy. You can't access either of these private members from outside the anonymous function. Instead, you can access them using the three public functions that are returned from the anonymous wrapper.

这三个公共函数形成共享相同词汇环境的闭包。由于 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。

¥Those three public functions form closures that share the same lexical environment. Thanks to JavaScript's lexical scoping, they each have access to the privateCounter variable and the changeBy function.

js
const makeCounter = function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
};

const counter1 = makeCounter();
const counter2 = makeCounter();

console.log(counter1.value()); // 0.

counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.

counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.

请注意两个计数器如何保持彼此的独立性。每个闭包通过其自己的闭包引用不同版本的 privateCounter 变量。每次调用其中一个计数器时,其词法环境都会通过更改该变量的值而发生变化。对一个闭包中变量值的更改不会影响另一个闭包中的值。

¥Notice how the two counters maintain their independence from one another. Each closure references a different version of the privateCounter variable through its own closure. Each time one of the counters is called, its lexical environment changes by changing the value of this variable. Changes to the variable value in one closure don't affect the value in the other closure.

注意:以这种方式使用闭包提供了通常与面向对象编程相关的好处。特别是数据隐藏和封装。

¥Note: Using closures in this way provides benefits that are normally associated with object-oriented programming. In particular, data hiding and encapsulation.

关闭作用域链

¥Closure scope chain

每个闭包都有三个作用域:

¥Every closure has three scopes:

  • 本地范围(自己的范围)
  • 封闭范围(可以是块、函数或模块范围)
  • 全球范围

一个常见的错误是没有意识到,在外部函数本身是嵌套函数的情况下,对外部函数作用域的访问包括外部函数的封闭作用域,从而有效地创建了一系列函数作用域。为了进行演示,请考虑以下示例代码。

¥A common mistake is not realizing that in the case where the outer function is itself a nested function, access to the outer function's scope includes the enclosing scope of the outer function—effectively creating a chain of function scopes. To demonstrate, consider the following example code.

js
// global scope
const e = 10;
function sum(a) {
  return function (b) {
    return function (c) {
      // outer functions scope
      return function (d) {
        // local scope
        return a + b + c + d + e;
      };
    };
  };
}

console.log(sum(1)(2)(3)(4)); // 20

你也可以不使用匿名函数来编写:

¥You can also write without anonymous functions:

js
// global scope
const e = 10;
function sum(a) {
  return function sum2(b) {
    return function sum3(c) {
      // outer functions scope
      return function sum4(d) {
        // local scope
        return a + b + c + d + e;
      };
    };
  };
}

const sum2 = sum(1);
const sum3 = sum2(2);
const sum4 = sum3(3);
const result = sum4(4);
console.log(result); // 20

在上面的示例中,有一系列嵌套函数,所有这些函数都可以访问外部函数的作用域。在这种情况下,我们可以说闭包可以访问所有外部函数作用域。

¥In the example above, there's a series of nested functions, all of which have access to the outer functions' scope. In this context, we can say that closures have access to all outer function scopes.

闭包也可以捕获块作用域和模块作用域中的变量。例如,以下代码在块作用域变量 y 上创建一个闭包:

¥Closures can capture variables in block scopes and module scopes as well. For example, the following creates a closure over the block-scoped variable y:

js
function outer() {
  let getY;
  {
    const y = 6;
    getY = () => y;
  }
  console.log(typeof y); // undefined
  console.log(getY()); // 6
}

outer();

模块上的闭包可能会更有趣。

¥Closures over modules can be more interesting.

js
// myModule.js
let x = 5;
export const getX = () => x;
export const setX = (val) => {
  x = val;
};

这里,模块导出一对 getter-setter 函数,它们关闭模块范围的变量 x。即使其他模块无法直接访问 x,也可以使用函数对其进行读写。

¥Here, the module exports a pair of getter-setter functions, which close over the module-scoped variable x. Even when x is not directly accessible from other modules, it can be read and written with the functions.

js
import { getX, setX } from "./myModule.js";

console.log(getX()); // 5
setX(6);
console.log(getX()); // 6

闭包也可以关闭导入的值,这被视为实时 bindings,因为当原始值更改时,导入的值也会相应更改。

¥Closures can close over imported values as well, which are regarded as live bindings, because when the original value changes, the imported one changes accordingly.

js
// myModule.js
export let x = 1;
export const setX = (val) => {
  x = val;
};
js
// closureCreator.js
import { x } from "./myModule.js";

export const getX = () => x; // Close over an imported live binding
js
import { getX } from "./closureCreator.js";
import { setX } from "./myModule.js";

console.log(getX()); // 1
setX(2);
console.log(getX()); // 2

在循环中创建闭包:一个常见的错误

¥Creating closures in loops: A common mistake

在引入 let 关键字之前,当你在循环内创建闭包时,会出现一个常见的闭包问题。为了进行演示,请考虑以下示例代码。

¥Prior to the introduction of the let keyword, a common problem with closures occurred when you created them inside a loop. To demonstrate, consider the following example code.

html
<p id="help">Helpful notes will appear here</p>
<p>Email: <input type="text" id="email" name="email" /></p>
<p>Name: <input type="text" id="name" name="name" /></p>
<p>Age: <input type="text" id="age" name="age" /></p>
js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    // Culprit is the use of `var` on this line
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function () {
      showHelp(item.help);
    };
  }
}

setupHelp();

尝试在 JSFiddle 中运行代码。

¥Try running the code in JSFiddle.

helpText 数组定义了三个有用的提示,每个提示都与文档中输入字段的 ID 相关联。该循环循环遍历这些定义,将 onfocus 事件连接到每个显示相关帮助方法的定义。

¥The helpText array defines three helpful hints, each associated with the ID of an input field in the document. The loop cycles through these definitions, hooking up an onfocus event to each one that shows the associated help method.

如果你尝试此代码,你会发现它无法按预期工作。无论你关注哪个字段,都会显示有关你年龄的信息。

¥If you try this code out, you'll see that it doesn't work as expected. No matter what field you focus on, the message about your age will be displayed.

原因是分配给 onfocus 的函数形成了闭包;它们由 setupHelp 函数范围内的函数定义和捕获的环境组成。循环创建了三个闭包,但每个闭包共享同一个词法环境,该环境具有一个值不断变化的变量 (item)。这是因为变量 item 是用 var 声明的,因此由于提升而具有函数作用域。item.help 的值是在执行 onfocus 回调时确定的。因为此时循环已经运行完毕,所以 item 变量对象(由所有三个闭包共享)一直指向 helpText 列表中的最后一个条目。

¥The reason for this is that the functions assigned to onfocus form closures; they consist of the function definition and the captured environment from the setupHelp function's scope. Three closures have been created by the loop, but each one shares the same single lexical environment, which has a variable with changing values (item). This is because the variable item is declared with var and thus has function scope due to hoisting. The value of item.help is determined when the onfocus callbacks are executed. Because the loop has already run its course by that time, the item variable object (shared by all three closures) has been left pointing to the last entry in the helpText list.

这种情况下的一种解决方案是使用更多闭包:特别是,使用前面描述的函数工厂:

¥One solution in this case is to use more closures: in particular, to use a function factory as described earlier:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function makeHelpCallback(help) {
  return function () {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

使用 这个 JSFiddle 链接 运行代码。

¥Run the code using this JSFiddle link.

这按预期工作。makeHelpCallback 函数不是所有回调都共享一个词法环境,而是为每个回调创建一个新的词法环境,其中 help 引用 helpText 数组中的相应字符串。

¥This works as expected. Rather than the callbacks all sharing a single lexical environment, the makeHelpCallback function creates a new lexical environment for each callback, in which help refers to the corresponding string from the helpText array.

使用匿名闭包编写上述内容的另一种方法是:

¥One other way to write the above using anonymous closures is:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    (function () {
      var item = helpText[i];
      document.getElementById(item.id).onfocus = function () {
        showHelp(item.help);
      };
    })(); // Immediate event listener attachment with the current value of item (preserved until iteration).
  }
}

setupHelp();

如果你不想使用更多的闭包,你可以使用 letconst 关键字:

¥If you don't want to use more closures, you can use the let or const keyword:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  const helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (let i = 0; i < helpText.length; i++) {
    const item = helpText[i];
    document.getElementById(item.id).onfocus = () => {
      showHelp(item.help);
    };
  }
}

setupHelp();

此示例使用 const 而不是 var,因此每个闭包都会绑定块作用域变量,这意味着不需要额外的闭包。

¥This example uses const instead of var, so every closure binds the block-scoped variable, meaning that no additional closures are required.

另一种替代方法是使用 forEach() 迭代 helpText 数组并将监听器附加到每个 <input>,如下所示:

¥Another alternative could be to use forEach() to iterate over the helpText array and attach a listener to each <input>, as shown:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  helpText.forEach(function (text) {
    document.getElementById(text.id).onfocus = function () {
      showHelp(text.help);
    };
  });
}

setupHelp();

性能考虑

¥Performance considerations

如前所述,每个函数实例管理自己的作用域和闭包。因此,如果特定任务不需要闭包,则在其他函数中创建不必要的函数是不明智的,因为这会对处理速度和内存消耗方面的脚本性能产生负面影响。

¥As mentioned previously, each function instance manages its own scope and closure. Therefore, it is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

例如,当创建新的对象/类时,方法通常应与对象的原型相关联,而不是定义到对象构造函数中。原因是每当调用构造函数时,方法都会被重新分配(即,对于每个对象创建)。

¥For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation).

考虑以下情况:

¥Consider the following case:

js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function () {
    return this.name;
  };

  this.getMessage = function () {
    return this.message;
  };
}

因为前面的代码没有利用在这个特定实例中使用闭包的好处,所以我们可以重写它以避免使用闭包,如下所示:

¥Because the previous code does not take advantage of the benefits of using closures in this particular instance, we could instead rewrite it to avoid using closures as follows:

js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName() {
    return this.name;
  },
  getMessage() {
    return this.message;
  },
};

但是,不建议重新定义原型。以下示例将附加到现有原型:

¥However, redefining the prototype is not recommended. The following example instead appends to the existing prototype:

js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function () {
  return this.name;
};
MyObject.prototype.getMessage = function () {
  return this.message;
};

在前面的两个示例中,继承的原型可以由所有对象共享,并且方法定义不需要在每次对象创建时都出现。更多信息请参见 继承和原型链

¥In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See Inheritance and the prototype chain for more.