JavaScript中的工厂函数vs构造函数vs class
原文链接:JavaScript Factory Functions vs Constructor Functions vs Classes
作者:Eric Elliott
译者:sunny
转载需提前联系译者,未经允许不得转载。
本文首发于前端指南
在ES6之前,JavaScript中的工厂函数和构造函数之间的差异令许多人困惑。由于ES6有了“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());
每种方式都用到了原型,并且有选择地使用构造函数来创建私有变量。换句话说,它们有很多相同的特性,大多数情况下都可以互换使用。
In JavaScript, any function can return a new object. When it’s not a constructor function or class, it’s called a factory function.
ES6的class是构造函数的语法糖,所以适用于构造函数的内容也适用于ES6的class:
class Foo {}
console.log(typeof Foo); // function
构造函数和class的优点
大部分书都会教你使用class或者是构造函数
'this'指向创建的新对象
有些人喜欢myFoo = new Foo()这样的写法
可能会有一些性能上的微弱优势,但是基本不需要担心,除非你对代码进行了分析并且证明这些差距对你而言非常重要。
构造函数和class的缺点
需要new
在ES6之前,忘记new是一种常见的bug。很多人都会用样板来解决这个问题:
function Foo() {if (!(this instanceof Foo)) { return new Foo(); }
}
在ES6(ES2015)中,如果你调用构造函数和class的时候忘记了new,会抛出错误。如果不将class包装在工厂函数中,那么难以避免强迫调用者使用new。也有人建议在未来的JavaScript版本中可以允许调用者自定义调用行为时可以省略new,但这也意味着会给每个使用它的class增加额外开销(也意味着很少使用它)。
实例化的细节被泄漏到了调用它的API(通过new)
所有的调用者都和构造函数的实现紧密耦合。如果你需要工厂的额外的灵活性,重构是一种突破性的变化。class到工厂的重构是常见的,他们出现在了Martin Fowler,Kent Beck,John Brant,William Opdyke和Don Roberts的创新重构的书:《Refactoring: Improving the Design of Existing Code》中。
构造函数违背了开闭原则
由于使用了new,构造器函数违背了开闭原则:接口对扩展开放,对修改关闭。
我认为class到工厂的重构是非常普遍的,它应该被作为构造函数扩展的标准。从class到工厂的升级本来不应该打破什么,但是在JavaScript中,它会。
如果你已经开始导出构造函数或是类,并且用户开始使用构造函数,慢慢你会意识到工厂的灵活性是非常重要的(例如:选择对象池来实现,或在执行上下文中实例化对象、或使用可选择的原型以获得更多的灵活性),你不能达到目标除非强制调用者重构。
不幸的是,在JavaScript中,从构造函数或class切换到工厂需要打破这种变化。
// 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
在上边这个例子中,我们开始时使用了class,但是我们想要提高可用性,增加不同的车子类型。为了实现这个目标,工厂为不同的车子提供了可选择的prototype。我曾经用这项技术实现了不同的媒体播放器的接口,根据需要控制的播放器来选择正确的prototype。
使用构造函数会导致“instanceof”的欺骗性
由构造器向工厂的重构其中一种突破性变化就是‘instanceof’。有时人们会试图用“instanceof”来检查代码中的数据类型。这就会导致问题,我建议你避免使用“instanceof”
// instanceof is a prototype identity check.
// NOT a type check.// 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对象与构造器的prototype属性进行比较。
在执行上下文中是不会起作用的例如(通常是bug、沮丧和不必要的限制的原因)。如果你的构造函数的prototype被替换,也不会发挥作用。
如果你在把构造函数转换成工厂方法的时候,使用class或者构造函数(返回“this”,与构造函数的prototype属性相关),然后切换到任意对象(没有与构造函数的prototype属性相关),也会导致失败。
简而言之,“instanceof”是从构造函数切换到工厂方法时的另一种突破性的变化。
使用class的优点
简便、独立的语法
单一的、规范的模仿JavaScript中类的方法。在ES6之前,还有几种流行库中的不同的实现方法。
人们更加熟悉基于类的语言。
使用类的缺点
所有构造函数的缺点,还有:
诱惑用户使用extends关键字创建多层次的class,很容易导致问题。
多层次的class会导致面向对象中一些众所周知的问题,包括:脆弱的基类问题、大猩猩香蕉问题、必要性导致的重复问题等等。不幸的是,class提供了像球可以投掷、椅子可以坐这样的扩展。更多详情,请阅读 “The Two Pillars of JavaScript: Prototypal OO” 和 “Inside the Dev Team Death Spiral”两篇文章。
构造函数和工厂都可以用来创建多层次的结构,但是class可以用extends关键字导致你走向错误的方向。换句话说,它鼓励你考虑不灵活的(通常是错的)is-a关系,而不是更灵活的has-a或者是can-do成分关系。
另一个提供的特性是支持执行特定行为的机会。例如,旋钮可以旋转,杠杆可以拉动,按钮可以按压等等。
使用工厂的优点
工厂比构造函数和class更加灵活,同时也不会用extends关键字和层次继承引导人们走向错误的方向。你可以使用不同的方法从工厂方法继承。特别是,用组合工工厂函数检查邮票规格。
返回任意对象,使用任意原型
例如:你可以很容易创建实现了同样接口的不同的对象。一种媒体播放器,它可以实例化多种视频的播放,这些视频在引擎下使用不同的API,它们的事件库使用了不同的Dom事件或web socket事件。
工厂函数也会根据执行上下文实例化对象,利用对象池的优势,这允许更灵活的基于原型的继承。
不用担心重构
你从来都不需要把工厂转换为构造函数,所以重构不会是问题。
不需要new
new没有歧义,不要使用(这会造成this指向不明确,请看下一点)。
标准的this
this指向出了它该指向的,所以你可以通过它来获取父对象。例如:player.create(),this指向了player,就像call和apply方法确定了this的指向。
不会有欺骗instanceof
有些人喜欢“myFoo=createFoo()”这种方式
工厂方法的缺点
不会创建对象与工厂的prototype之间的联系,但这实际上是一个好事情,因为你不会被instanceof欺骗。相反,isntanceof会失败。请看优点。
this没有指向工厂中的新对象。请看优点。
在微优化的基准测试中,它可能比构造函数的执行速度慢。如果非要测试的话,请确保你需要关注这个问题。
总结
在我看来,class确实有简便的语法,但是实际上它可能会诱导用户创造继承的class。这也是有风险的,在未来,你可能会升级为工厂,但是由于new关键字的使用,所有的调用者都与你的构造函数紧密耦合,从类转换为工厂,会导致突破性的变化。
你可能考虑你只需要重构调用部分,但是在大型团队中,或者你的class是公共API的一部分,你不可能去修改不在你控制下的代码。换句话说,你不能总是假设重构调用者是可能的选项。
工厂模式不仅仅更加强大灵活,同时也会鼓励整个团队、所有的API用户使用一种简单、灵活安全的模式。
关于工厂模式其实还有很多内容,尤其是关于使用邮票规格进行对象组合的效用。更多关于这个话题,以及与class的不同,请阅读“3 Different Kinds of Prototypal Inheritance”。
JavaScript中的工厂函数vs构造函数vs class相关推荐
- JavaScript中的普通函数与构造函数比较
问题 什么是构造函数? 构造函数与普通函数区别是什么? 用new关键字的时候到底做了什么? 构造函数有返回值怎么办? 构造函数能当普通函数调用吗? this this永远指向当前正在被执行的函数或方法 ...
- 如何使用JavaScript中的工厂函数构建可靠的对象
Discover Functional JavaScript was named one of the best new Functional Programming books by BookAut ...
- 【译】JavaScript 工厂函数 vs 构造函数
译者:前端小智 原文:medium.com/@chamikakas- 当谈到JavaScript语言与其他编程语言相比时,你可能会听到一些令人困惑东西,其中之一是工厂函数和构造函数. 想优质文章请猛戳 ...
- 工厂好的html页面,jquery中被誉为工厂函数的是什么?
jquery中被誉为工厂函数的是"$()".在jQuery中,无论我们使用哪种类型的选择符都需要从一个"$"符号和一对"()"开始.下面本篇 ...
- 如何找到JavaScript中的调用者函数?
function main() {Hello(); }function Hello() {// How do you find out the caller function is 'main'? } ...
- 在javascript中使用纯函数处理副作用
在javascript中使用纯函数处理副作用 今天给大家带来一片译文, 详情请点击这里.可能在墙内哦 开始了, 如果你点开这篇文章, 就证明你已经开始涉及函数式编程了, 这距离你知道纯函数的概念不会很 ...
- 理解javascript中的回调函数(callback)【转】
在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...
- 关于javascript中的回调函数
关于javascript中的回调函数 原文地址:http://blog.csdn.net/sicluoyi/article/details/1737969 考虑一个这样的例子: 假如某个项目的底层和高 ...
- 深入认识javascript中的eval函数
来源:http://wanyij.blog.51cto.com/46570/43794 发现为本文起一个合适的标题还不是那么容易,呵呵,所以在此先说明下本文的两个目的: (1)介绍javascript ...
最新文章
- linux下gcc编译conio.h问题
- java list 查找_java面试之容器
- mysql5.7 glibcxx_3.4.15_CentOS6.5 缺少 libstdc++.so.6(GLIBCXX_3.4.15)
- linux 重复执行脚本,防止shell脚本重复执行的代码
- 一、数据类型和运算符——3-数据类型
- java调用数组计算器_java按钮控件数组实现计算器界面示例分享
- jbpm6 mysql_JBPM6把默认的H2数据库替换成mySql
- springmvc 控制器 读取properties文件
- 成为Oracle 10g DBA之路
- i.MXRT1050 从外部QSPI Nor Flash的启动
- 写给那些要找电子发票的数据接口规范的程序猿
- 使用opencv进行车牌提取及识别
- 检查服务器端口占用,服务器中如何检查端口是否开放
- 人工智能能否在翻译中胜过人类?
- Android Studio实现一个记账本项目
- 苹果怎么开启开发者模式
- 红绿灯的html代码,红绿灯.html
- 【经验分享】58个硬件工程师基础知识面试题
- SAP 薪酬计算流程
- Louvain聚类算法
热门文章
- python能写软件吗-python代码能做成软件吗
- python timer使用-Python timer定时器两种常用方法解析
- python3读取excel数据-【Python3学习系列】——Python读取Excel
- Intel深度摄像头RealSense D345(实感双目摄像头)和目标检测结合使用
- mysql三范式和反三范式_数据库三范式和反三范式
- 判断 小程序_社区团购小程序商城系统,可以从哪些方面判断?
- UVa12467 Secret Word(kmp)
- UVa1346 - Songs(贪心算法)
- UVa11464 - Even Parity(递推法)
- UUID介绍与生成的方法