ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 章节导航 [TOC] ### [构造函数有什么问题?](https://www.theodinproject.com/courses/javascript/lessons/factory-functions-and-the-module-pattern#null) 对象构造函数是开始组织代码的大约一百万种方法之一。它们在野外很常见,是的JavaScript语言的基本构建块。 *然而...* 有很多最人*反对*使用构造函数。他们的论点归结为这样一个事实:如果你不小心,在使用构造函数时,很容易在代码中引入错误[此](https://tsherif.wordpress.com/2013/08/04/constructors-are-bad-for-javascript/)。文章确实概述了问题的一个相当不错的工作(扰流警报:作者最终推荐工厂功能) 构造函数的一个最大问题是,它们虽然*看起来*像常规函数,但它们的行为根本不像常规函数。如果尝试您使用不带`new`关键字的构造函数,则您的程序将无法按预期工作,但它不会生成易于跟踪的错误消息。 主要的内容是,构造虽然不一定者的英文*邪恶的*,但它们不是唯一的方式,它们也可能不是最好的方式。当然,这并不意味着浪费时间了解它们!它们是真实世界代码中的常见模式,以及您将在网上遇到的许多教程。 ### [工厂功能介绍](https://www.theodinproject.com/courses/javascript/lessons/factory-functions-and-the-module-pattern#null) 工厂函数模式与构造函数类似,工厂但不是函数`new`用于创建³³对象,而是在调用函数时简单地设置并报道查看新对象。看看这个例子。 ~~~javascript const personFactory = (name, age) => { const sayHello = () => console.log('hello!'); return { name, age, sayHello }; }; const jeff = personFactory('jeff', 27); console.log(jeff.name); // 'jeff' jeff.sayHello(); // calls the function and logs 'hello!' ~~~ 供参考,这是使用构造函数模式创建的相同内容: ~~~javascript const Person = function(name, age) { this.sayHello = () => console.log('hello!'); this.name = name; this.age = age; }; const jeff = new Person('jeff', 27); ~~~ #### 对象速记 关于工厂功能示例中第3行的快速说明。2015年,JavaScript中添加了一个用于创建对象的便捷新简写。没有简写线3,看起来像这样: ~~~javascript return {name: name, age: age, sayHello: sayHello} ~~~ 简而言之,如果您要创建一个对象,其中您指的是与您正在创建的对象属性具有完全相同名称的变量,则可以将其压缩为: ~~~javascript return {name, age, sayHello} ~~~ 有了这些知识,请查看这个小小的黑客: ~~~javascript const name = "Maynard" const color = "red" const number = 34 const food = "rice" // logging all of these variables might be a useful thing to do, // but doing it like this can be somewhat confusing. console.log(name, color, number, food) // Maynard red 34 rice // if you simply turn them into an object with brackets, // the output is much easier to decipher: console.log({name, color, number, food}) // { name: 'Maynard', color: 'red', number: 34, food: 'rice' } ~~~ ### [范围和结束](https://www.theodinproject.com/courses/javascript/lessons/factory-functions-and-the-module-pattern#null) 从简单地阅读上面的例子开始,你可能已经处于很好的状态,开始在你的代码中使用工厂函数,但在我们到达那里之前,是时候深入研究一个非常重要的概念:**闭包**。 我们能够使封闭的意义之前,需要我们确保你有一个*真正*的把握好**范围**在的JavaScript中。范围是指代码中可以使用变量和函数之类的术语。   在以下示例中,您知道最后一行会记录什么吗? ~~~javascript let a = 17; const func = x => { let a = x; }; func(99); console.log(a); // ??????? ~~~ 是17还是99?你知道为什么吗?你可以编辑代码,以便打印其他值吗? 答案是17,并且它不是99的原因是在第4行,外部变量`a`没有被重新定义,而是在该函数的范围内创建了一个*new*`a`。最后,在大多数情况下确定范围并不是那么复杂,但*对于*理解即将出现的一些更高级的概念*至关重要*,因此请花时间了解以下资源中发生的情况。 1. [视频这个](https://www.youtube.com/watch?v=SBwoFkRjZvE)简单明了!从这里开始。 2. [译者](https://toddmotto.com/everything-you-wanted-to-know-about-javascript-scope/)从简单开始,重申视频所涵盖的内容,但更深入,更具体地说明了适当的术语。最后,定义他了**闭包***并*描述了**模块**模式,我们将很快谈论这两种模式。 * 上一篇文章很棒,但有一个不准确的声明: > JavaScript的的中作用域所有都是`Function Scope`*仅*创建³³的,不是它们由`for`或`while`循环或表达式语句创建³³的,如`if`或`switch`。新功能=新范围-这是规则 声明该*的英文*在当文章写2013属实,但ES6亦使不正确。阅读[这篇](http://wesbos.com/javascript-scoping/)文章以获得独家新闻! ### [私有变量和函数](https://www.theodinproject.com/courses/javascript/lessons/factory-functions-and-the-module-pattern#null) 现在我们已经巩固了您对JavaScript的范围的了解,请看一下这个例子: ~~~javascript const FactoryFunction = string => { const capitalizeString = () => string.toUpperCase(); const printString = () => console.log(`----${capitalizeString()}----`); return { printString }; }; const taco = FactoryFunction('taco'); printString(); // ERROR!! capitalizeString(); // ERROR!! taco.capitalizeString(); // ERROR!! taco.printString(); // this prints "----TACO----" ~~~ 由于范围的概念,创建³³内部的函数`FactoryFunction`都不能在函数本身之外访问,这就是上面第9行和第10行失败的原因。使用其中任何一个函数的唯一方法的英文`return`在对象中使用它们(见第4行),就是这可以我们调用`taco.printString()`但*不能*调用的原因`taco.capitalizeString()`。这里最重要的是,即使*我们*无法访问该`capitalizeString()`功能,`printString()`也可以。那就是关闭。 闭包的概念是这样一种想法,即函数保留其范围,即使它们被传递并在该范围之外调用。在这种情况下,即使在该函数之外调用它,`printString`也可以访问其中的所有内容`FactoryFunction`。 这是另一个例子: ~~~javascript const counterCreator = () => { let count = 0; return () => { console.log(count); count++; }; }; const counter = counterCreator(); counter(); // 0 counter(); // 1 counter(); // 2 counter(); // 3 ~~~ 在此示例中,`counterCreator`初始化局部变量(`count`)然后返回一个函数。要使用该功能,我们必须将其分配给变量(第9行)。然后,每次我们运行它`console.log`的函数`count`并递增它。如上所述,函数该`counter`的英文一个闭包。它可以访问变量,`count`并且可以打印状语从句:递增变量,但是我们的程序没有其他方法可以访问该变量。 在工厂功能的上下文中,闭包允许我们创建**私有**变量和函数。私有函数是在我们的对象的工作中使用的函数,这些函数不打算在我们的程序中的其他地方使用。换句话说,即使我们的对象可能只做一两件事,我们也可以自由地将我们的函数拆分起来(允许更干净,更容易阅读的代码),并且只导出程序其余部分的函数将要使用。将这个术语与之前我们的`printString`示例一起使用,`capitalizeString`的英文一个私有函数并且`printString`的英文公共的。 私有函数的概念非常有用,应该尽可能多地使用!对于程序所需的每一项功能,可能会有一些不需要在程序中使用的支持功能。将这些丢失并使其无法访问会使您的代码更容易重构,更容易测试,并且更容易为您和想要使用您的对象的任何其他人推理。 ### [返回工厂功能](https://www.theodinproject.com/courses/javascript/lessons/factory-functions-and-the-module-pattern#null) 现在我们已经将理论排除在外,让我们回到工厂功能。工厂只是简单的旧的JavaScript函数,它们返回我们在代码中使用的对象。使用工厂是组织和包含您正在编写的代码的有效方式。例如,如果我们正在编写任何类型的游戏,我们可能会希望对象描述我们的玩家并封装玩家可以做的所有事情(功能!) ~~~javascript const Player = (name, level) => { let health = level * 2; const getLevel = () => level; const getName = () => name; const die = () => { // uh oh }; const damage = x => { health -= x; if (health <= 0) { die(); } }; const attack = enemy => { if (level < enemy.getLevel()) { damage(1); console.log(`${enemy.getName()} has damaged ${name}`); } if (level >= enemy.getLevel()) { enemy.damage(1); console.log(`${name} has damaged ${enemy.getName()}`); } }; return {attack, damage, getLevel, getName} }; const jimmie = Player('jim', 10); const badGuy = Player('jeff', 5); jimmie.attack(badGuy); ~~~ 花一点时间来看看这个简单的例子,看看你是否能弄清楚发生了什么。 如果我们试着打电话会发生什么`jimmie.die()`?如果我们试图操纵健康`jimmie.health -= 1000`怎么办?当然,这些是我们没有公开曝光的事情所以我们会收到错误。这是一件非常好的事情!设置这样的对象使我们更容易使用它们,因为我们实际上已经考虑了如何以及何时想要使用这些信息。在这种情况下,我们将jimmie的健康隐藏为对象内部的私有变量,这意味着如果我们想要操作它,我们需要导出一个函数。从长远来看,这将使我们的代码*更*容易推理,因为所有逻辑都封装在适当的位置。 #### 与工厂的继承 在构造函数课程中,我们深入研究了Prototypes和Inheritance的概念,或者让我们的对象访问另一个Object的方法和属性。使用工厂时,有几种简单的方法可以实现这一目标。检查一下: ~~~javascript const Person = (name) => { const sayName = () => console.log(`my name is ${name}`) return {sayName} } const Nerd = (name) => { // simply create a person and pull out the sayName function! const {sayName} = Person(name) const doSomethingNerdy = () => console.log('nerd stuff') return {sayName, doSomethingNerdy} } const jeff = Nerd('jeff') jeff.sayName() //my name is jeff jeff.doSomethingNerdy() // nerd stuff ~~~ 这种模式*很棒,*因为它允许您选择要包含在新对象中的函数。如果你想继续把所有另一个对象放进去,你当然也可以这样做`Object.assign`(在[这里](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)阅读那个文档)。 ~~~javascript const Nerd = (name) => { const prototype = Person(name) const doSomethingNerdy = () => console.log('nerd stuff') return Object.assign({}, prototype, {doSomethingNerdy}) } ~~~ * 在继续之前看看[这篇](https://medium.com/javascript-scene/3-different-kinds-of-prototypal-inheritance-es6-edition-32d777fa16c9)文章。在文章的后半部分,作者介绍了一些我们在这里并没有真正谈论太多的事情,但如果你花一些时间搞清楚他在说什么,你就会得到回报。好东西! ### [模块模式](https://www.theodinproject.com/courses/javascript/lessons/factory-functions-and-the-module-pattern#null) > 快速旁注:ES6在JavaScript中引入了一个名为“模块”的新功能。这些本质上是用于在不同JavaScript文件之间导入和导出代码的语法。它们非常强大,我们将在以后覆盖它们。但是,它们*不是*我们在这里谈论的内容。 模块实际上与Factory功能非常相似。主要区别在于它们是如何创建的。 认识一个模块: ~~~javascript const calculator = (() => { const add = (a, b) => a + b; const sub = (a, b) => a - b; const mul = (a, b) => a * b; const div = (a, b) => a / b; return { add, sub, mul, div, }; })(); calculator.add(3,5) // 8 calculator.sub(6,2) // 4 calculator.mul(14,5534) // 77476 ~~~  这些概念与工厂函数完全相同,但是,不是创建我们可以反复使用的工厂来创建多个对象,而是模块模式将工厂包装在IIFE(立即调用的函数表达式)中。 * 在[本文中](http://adripofjavascript.com/blog/drips/an-introduction-to-iffes-immediately-invoked-function-expressions.html)阅读有关IIFE的[内容](http://adripofjavascript.com/blog/drips/an-introduction-to-iffes-immediately-invoked-function-expressions.html)。概念很简单..编写一个函数,将其包装在括号中,然后通过添加`()`到函数的末尾立即调用该函数。 在我们的计算器示例中,IIFE内部的函数是一个简单的工厂函数,就像我们之前定义的那样,但是我们可以继续将对象分配给变量,`calculator`因为我们不需要制作大量的计算器,我们只需要一个。就像工厂示例一样,我们可以拥有任意数量的私有函数和变量,它们保持整齐有序,隐藏在我们的模块中,只暴露我们实际想要在程序中使用的函数。 将程序的内部工作封装到对象中的一个有用的副作用是**命名空间**。命名空间是一种用于避免在程序中命名冲突的技术。例如,很容易想象您可以编写具有相同名称的多个函数的场景。在我们的计算器示例中,如果我们有一个向HTML显示添加内容的函数或者在用户输入时将数字和运算符添加到堆栈的函数,该怎么办?可以想象,我们想要调用所有这三个功能`add`,这些功能当然会给我们的程序带来麻烦。如果人人都被很好地封装对象的内部,然后我们就没有烦恼:`calculator.add()`,`displayController.add()`,`operatorStack.add()`。