开发者社区> 哈你真皮> 正文

JavaScript中的工厂方法、构造函数与class

简介: JavaScript中的工厂方法、构造函数与class 本文转载自:众成翻译 译者:谢于中 链接:http://www.
+关注继续查看

JavaScript中的工厂方法、构造函数与class

本文转载自:众成翻译
译者:谢于中
链接:http://www.zcfy.cc/article/1129
原文:https://medium.com/javascript-scene/javascript-factory-functions-vs-constructor-functions-vs-classes-2f22ceddf33e#.wby148xu6

在ES6出现之前,人们常常疑惑JavaScript中的工厂模式和构造函数模式到底有什么区别。ES6中提出了class关键字,许多人认为这解决了构造函数模式中的诸多问题。事实上,问题并没有得到解决。下面就让我们来看看工厂模式、构造函数模式和class的一些重要区别。

首先,让我们看看这三种方式的例子:

// class
class ClassCar {
  drive () {
    console.log('Vroom!');
  }
}

const car1 = new ClassCar();
console.log(car1.drive());


// constructor(构造函数模式)
function ConstructorCar () {}

ConstructorCar.prototype.drive = function () {
  console.log('Vroom!');
};

const car2 = new ConstructorCar();
console.log(car2.drive());


// factory (工厂模式)
const proto = {
  drive () {
    console.log('Vroom!');
  }
};

function factoryCar () {
  return Object.create(proto);
}

const car3 = factoryCar();
console.log(car3.drive());

这些方式都将方法存储于共享的原型中,然后通过构造函数的闭包有选择的支持私有数据。换句话说,他们几乎拥有相同的特性,所以通常来说也能交替使用。

> 在JavaScript中,任何函数都能返回一个新的对象。当这个函数不是构造函数或class时,它就叫做工厂函数。

ES6中的class其实是构造函数的语法糖,所以它具有构造函数的一切优点和缺点:

class Foo {}
console.log(typeof Foo); // function

构造函数和class的优点

  • 大多数书本都教了class或者构造函数。
  • this指向新的对象。
  • 一些人喜欢myFoo = new Foo()这种写法。
  • 构造函数和class存在许多微小的优化,不过除非你已经定量的分析了代码,并且这些优化确实对程序的性能很重要,否则这不应该被列入考虑范围。

构造函数和class的缺点

1.需要new

在ES6之前,漏写new是一个普遍的bug。为了对付它,许多人使用了以下方式:

function Foo() {
  if (!(this instanceof Foo)) { return new Foo(); }
}

在ES6(ES2015)中,如果你试图不使用new来调用class构造函数,将抛出错误。只有用工厂模式包裹class,才有可能避免必须使用new。对于未来版本的JavaScript,有人提出建议,希望能够定制忽略new时class构造函数的行为。不过这种方式依然增加了使用class时的开销(这也意味着会有更少的人使用它)。

2. 调用API时,实例化的细节被泄露(从new的角度展开)

构造函数的调用方法与构造函数的实现方式紧密耦合。当你需要其具有工厂方法的灵活性时,重构起来将会是巨大的变化。将class放入工厂方法进行重构是非常常见的,它甚至被写入了Martin Fowler, Kent Beck, John Brant, William Opdyke, 和 Don Roberts的 “Refactoring: Improving the Design of Existing Code”

3. 构造函数模式违背了开/闭原则

由于对new的要求,构造函数违背了开/闭原则:即一个API应该对扩展开放,对改装关闭。

我的意见是,既然从类到工厂方法的重构是非常常见的,那么应该将不应该造成任何破坏作为所有构造函数进行扩展时的标准。

如果你开放了一个构造函数或者类,而用户使用了这个构造函数,在这之后,如果需要增加这个方法的灵活性,(例如,换成使用对象池的方式进行实现,或者跨执行上下文的实例化,或者使用替代原型来拥有更多的继承灵活性),都需要用户同时进行重构。

不幸的是,在JavaScript中,从构造函数或者类切换到工厂方法需要进行巨大的改变

// Original Implementation:
// 原始实现:

// class Car {
//   drive () {
//     console.log('Vroom!');
//   }
// }

// const AutoMaker = { Car };

// Factory refactored implementation:
// 工厂函数重构实现:
const AutoMaker = {
  Car (bundle) {
    return Object.create(this.bundle[bundle]);
  },

  bundle: {
    premium: {
      drive () {
        console.log('Vrooom!');
      },
      getOptions: function () {
        return ['leather', 'wood', 'pearl'];
      }
    }
  }
};

// The refactored factory expects:
// 重构后的方法希望这样调用
const newCar = AutoMaker.Car('premium');
newCar.drive(); // 'Vrooom!'

// But since it's a library, lots of callers
// in the wild are still doing this:
// 但是由于这是一个库,许多用户依然这样使用
const oldCar = new AutoMaker.Car();

// Which of course throws:
// TypeError: Cannot read property 'undefined' of
// undefined at new AutoMaker.Car
// 这样的话,就会抛出:
// TypeError: Cannot read property 'undefined' of
// undefined at new AutoMaker.Car

在上面的例子中,我们首先提供了一个类,但是接下来希望提供不同的汽车种类。于是,工厂方法为不同的汽车种类使用了不同的原型。我曾经使用这种技术来存储不同的播放器接口,然后通过要处理的文件格式选择合适的原型。

4. 使用构造函数会导致instanceof具有欺骗性

与工厂方法相比,构造函数带来的巨大变化就是instanceof的表现。人们有时使用instanceof进行类型检查。这种方式其实是经常会出问题的,我建议你避免使用instanceof

> instanceof会撒谎。

// instanceof is a prototype identity check.
// NOT a type check.

// instanceof是一个原型检查
// 而不是类型检查

// That means it lies across execution contexts,
// when prototypes are dynamically reassigned,
// and when you throw confusing cases like this
// at it:

function foo() {}
const bar = { a: 'a'};

foo.prototype = bar;

// Is bar an instance of foo? Nope!
console.log(bar instanceof foo); // false

// Ok... since bar is not an instance of foo,
// baz should definitely not be an instance of foo, right?
const baz = Object.create(bar);

// ...Wrong.
console.log(baz instanceof foo); // true. oops.

instanceof进行类型检查的方式和强类型语言不一样,它会将对象的[[Prototype]]对象和Constructor.prototype属性进行一致性检查。

例如,当执行上下文发生变化时,instanceof会发生错误。当Constructor.prototype变化了之后,instanceof一样不会正常工作。

当你从一个class或构造函数开始(这将会返回指向 Constructor.prototypethis),然后转而探索另外一个对象(没有指向 Constructor.prototype),这同样会导致instanceof的失败。这种情况在将构造函数转换为工厂函数时会出现。

简而言之,instanceof是另外一种将构造函数转换为工厂函数时会发生的巨大变化

使用class的优点

  • 方便的,自包含的语法。
  • 是JavaScript中使用类的一种单一、规范的方式。在ES6之前,在一些流行的库中已经出现了其实现方式。
  • 对于有基于类的语言的背景的人来说,会更加熟悉。

使用class的缺点

除了具有构造函数的缺点外,还有:

  • 用户可能会尝试使用extends关键字来创建导致问题的多层级的类。

多层级的类将会导致许多在面向对象程序设计中广为人知的问题,包括脆弱的基类问题,香蕉猴子雨林问题,必要性重复问题等等。不幸的是,class可以用来extends就像球可以用来扔,椅子可以用来坐一样自然。想了解更多内容,请阅读“The Two Pillars of JavaScript: Prototypal OO” and “Inside the Dev Team Death Spiral”.

值得指出的是,构造函数和工厂函数都有可能导致有问题的层次继承,但通过extends关键字,class提供了一个让你犯错的功能可见性。换句话说,它鼓励你思考不灵活的的而且通常是错误的is-a关系,而不是更加灵活的has-a 或者 can-do组件化关系。

> 功能可见性是让你能够执行一定动作的机会。例如,旋钮可以用来旋转,杠杆可以用来拉,按钮可以用来按,等等。

使用工厂方法的优点

工厂方法比构造函数或类都要灵活,并且它不会诱惑人们使用extends来构造太深的继承层级。你可以使用多种方法来继承工厂函数。特别的,如果想了解组合式工厂方法,请查看Stamp Specification

1. 返回任意对象与使用任意原型

例如,你可以通过同一API轻松的创建多种类型的对象,例如,一个能够针对不同类型视频实例化播放器的媒体播放器,活着能够出发DOM事件或web socket事件的事件库。

工厂函数还能跨越之行上下文来实例化对象,充分利用对象池,并且允许更灵活的原型模型继承。

2. 没有重构的忧虑

你永远不需要从一个工厂转换到一个构造函数,所以重构将永远不会是一个问题。

3. 没有new

关于要不要使用new只有一个选择,那就是不要用。(这将会使this表现不好,原因见下一点)。

4. 标准的this行为

this和它通常的表现一样,所以你可以用它来获取其父级对象。例如,在player.create()中,this指向player,正如其他方法调用那样。call()apply()也会同样的指向this

5. 不会有欺骗性的instanceof问题

6. 有些人喜欢myFoo = createFoo()这种写法

工厂方法的缺点

  • 不会创建一个指向Factory.prototype的链接——但这其实是件好事,因为这样你就不会得到一个具有欺骗性的instanceof。相反,instanceof会一直失败。详情见工厂方法的优点。
  • this不会指向工厂方法中的新对象。详情见工厂方法的优点。
  • 在经过微优化的基准中,工厂方法可能会稍微比构造函数模式慢一些。如果这对你有影响,请务必在你程序的上下文中进行测试。

结论

在我看来,class或许有简单的语法形式,但这不能弥补它引诱粗心的用户在类继承中犯错的事实。对于未来它也是有风险的,因为你有可能会想将其升级成为一个工厂函数,而由于new关键字,你的所有调用都将和构造函数紧密耦合,于是从class向工厂方法迁移将会是一个巨大的改变。

你也许会想可以只重构调用部分,不过在大的团队中,或者你使用的class是公共API的一部分,你就有可能要破坏不在你掌控中的代码。换句话说,不能假设只重构调用部分永远是一个可选项。

关于工厂方法,有趣的事情在于它们不仅更加强大和灵活,而且是鼓励整个团队,以及所有API用户使用简单、灵活和安全的模式的最简单的方法。

关于工厂函数的好处,特别是关于对象组合的能力,还有许多内容可以详述。想要了解更多内容,以及这种方式与类继承的区别,请阅读“3 Different Kinds of Prototypal Inheritance”.


For more training in in prototypal inheritance techniques, factory functions, and object composition, be sure to check out “The Two Pillars of JS: Composition with Prototypes” — free for members. Not a member yet?

Learn JavaScript with Eric Elliott


_Eric Elliott_**_ is the author of “Programming JavaScript Applications” (O’Reilly), and “Learn JavaScript with Eric Elliott”. He has contributed to software experiences for **_Adobe Systems_, _Zumba Fitness_, _The Wall Street Journal, ESPN_, _BBC_, and top recording artists including _Usher_, _Frank Ocean_,_Metallica_, and many more._

He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【Groovy】MOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 通过 MetaClass#invokeMethod 方法调用类其它方法 )
【Groovy】MOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 通过 MetaClass#invokeMethod 方法调用类其它方法 )
31 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
28139 0
JS-获取class类名为某个的元素-【getClass】函数封装
原理: /*  * 根据class获取元素. * 原理是,取出oparent下的所有元素,组成数组,然后遍历类名,全等判断。*/ 源码   1 1 function getClass(oParent,clsName){ 2    var oParent = document.
1032 0
JavaScript创建对象(二)——构造函数模式
在JavaScript创建对象(一)—— 工厂模式中留下了一个问题,就是创建一个对象怎么判断一个对象的类型。换句话说使用下面这种方式: function createPerson(name, age, job){ var o = new Object(); o.
959 0
【Groovy】MOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 重写 MetaClass#invokeMethod 方法拦截 JDK 中已经定义的函数 )
【Groovy】MOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 重写 MetaClass#invokeMethod 方法拦截 JDK 中已经定义的函数 )
18 0
Java 获取成员变量&构造函数
获取成员变量&构造函数 一、成员变量是java.lang.reflect.Field的对象 1、Field类封装了关于成员变量的操作 2、Field[] fs = c.getFields()方法获取所有public的成员变量Field[]信息 3、c.
729 0
《JavaScript启示录》——1.3 JavaScript原生/内置对象构造函数
JavaScript语言包含9个原生(或内置)对象构造函数。JavaScript使用这些对象来构建JavaScript语言。“构建”的意思是指,这些对象是用于表达JavaScript代码中的对象值,以及协调语言中的多个特性。
1483 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
20200 0
+关注
354
文章
1
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载