前言前端
又到周五了,这周是否是被这一系列的文章看的有点晕,若是拿着一本书会以为挺多的,不必定能看完,但天天跟着阅读一篇可能就不会以为多了,你以为呢?今天继续由前端早读课专栏做者@HetfieldJoe带来连载《你不懂JS》的分享。编程
正文从这开始~设计模式
你不懂JS:this与对象原型 第四章:混合(淆)“类”的对象数组
接着咱们上一章对对象的探索,咱们很天然的将注意力转移到“面向对象(OO)编程”,与“类(class)”。咱们先将“面向类”做为设计模式来看看,以后咱们再考察“类”的机制:“实例化(instantiation)”, “继承(inheritance)”与“相对多态(relative polymorphism)”。数据结构
咱们将会看到,这些概念并非很是天然地映射到JS的对象机制上,以及许多JavaScript开发者为了克服这些挑战所作的努力(mixins等)。框架
注意: 这一章花了至关一部分时间(前一半!)在着重解释“面向对象编程”理论上。在后半部分讨论“Mixins(混合)”时,咱们最终会将这些理论与真实且实际的JavaScript代码联系起来。可是这里首先要蹚过许多概念和假想代码,因此可别迷路了——坚持下去!ide
类(Class)理论函数式编程
“类/继承”描述了一种特定的代码组织和结构形式——一种在咱们的软件中对真实世界的建模方法。函数
OO或者面向类的编程强调数据和操做它的行为之间有固有的联系(固然,依数据的类型和性质不一样而不一样!),因此合理的设计是将数据和行为打包在一块儿(也称为封装)。这有时在正式的计算机科学中称为“数据结构”。工具
好比,表示一个单词或短语的一系列字符一般称为“string(字符串)”。这些字符就是数据。但你几乎历来不关心数据,你老是想对数据 作事情, 因此能够 向 数据实施的行为(计算它的长度,在末尾添加数据,检索,等等)都被设计成为String类的方法。
任何给定的字符串都是这个类的一个实例,这个类是一个整齐的集合包装:字符数据和咱们能够对它进行的操做功能。
类还隐含着对一个特定数据结构的一种 分类 方法。咱们这么作的方法是,将一个给定的结构考虑为一个更加泛化的基础定义的具体种类。
让咱们经过一个最常被引用的例子来探索这种分类处理。一辆 车 能够被描述为一“类”更泛化的东西——载具——的具体实现。
咱们在软件中经过定义Vehicle类和Car类来模型化这种关系。
Vehicle的定义可能会包含像动力(引擎等),载人能力等等,这些都是行为。咱们在Vehicle中定义的都是全部(或大多数)不一样类型的载具(飞机,火车,机动车)都共同拥有的东西。
在咱们的软件中为每一种不一样类型的载具一次又一次地重定义“载人能力”这个基本性质可能没有道理。反而,咱们在Vehicle中把这个能力定义一次,以后当咱们定义Car时,咱们简单地指出它从基本的Vehicle定义中“继承”(或“扩展”)。Car的定义就是特化了通常的Vehicle定义。
虽然Vehicle和Car用方法的形式集约地定义了行为,但一个实例中的数据就像一个惟一的车牌号同样属于一辆具体的车。
这样,类,继承,和实例化就诞生了。
另外一个关于类的关键概念是“多态(polymorphism)”,它描述这样的想法:一个来自于父类的泛化行为能够被子类覆盖,从而使它更加具体。实际上,相对多态让咱们在覆盖行为中引用基础行为。
类理论强烈建议父类和子类对相同的行为共享一样的方法名,以便于子类(差别化地)覆盖父类。咱们即将看到,在你的JavaScript代码中这么作会致使种种困难和脆弱的代码。
"类(Class)"设计模式
你可能从没把类当作一种“设计模式”考虑过,由于最多见的是关于流行的“面向对象设计模式”的讨论,好比“迭代器(Iterator)”,“观察者(Observer)”,“工厂(Factory)”,“单例(Singleton)”等等。当以这种方式表现时,几乎能够假定OO的类是咱们实现全部(高级)设计模式的底层机制,好像对全部代码来讲OO是一个给定的基础。
取决于你在编程方面接受过的正规教育的水平,你可能据说过“过程式编程(procedural programming)”:一种不用任何高级抽象,仅仅由过程(也就是函数)调用其余函数来构成的描述代码的方式。你可能被告知过,类是一个将过程式风格的“面条代码”转换为结构良好,组织良好代码的 恰当 的方法。
固然,若是你有“函数式编程(functional programming)”的经验,你可能知道类只是几种常见设计模式中的一种。可是对于其余人来讲,这多是第一次你问本身,类是否真的是代码的根本基础,或者它们是在代码顶层上的选择性抽象。
有些语言(好比Java)不给你选择,因此这根本没什么 选择性——一切都是类。其余语言如C/C++或PHP同时给你过程式和面向类的语法,在使用哪一种风格合适或混合风格上,留给开发者更多选择。
JavaScript的“类”
在这个问题上JavaScript属于哪一边?JS拥有 一些 像类的语法元素(好比new和instanceof)有一阵子了,并且在最近的ES6中,还有一些追加的,好比class关键字(见附录A)。
但这意味着JavaScript实际上 拥有 类吗?直白且简单:没有。
因为类是一种设计模式,你 能够,用至关的努力(咱们将在本章剩下的部分看到),近似实现不少经典类的功能。JS在经过提供看起来像类的语法,来努力知足用类进行设计的极其普遍的渴望。
虽然咱们好像有了看起来像类的语法,可是好像JavaScript机制在抵抗你使用 类设计模式,由于在底层,这些你正在上面工做的机制运行的十分不一样。语法糖和(极其普遍被使用的)JS“Class”库废了很大力气来把这些真实状况对你隐藏起来,但你早晚会面对现实:你在其余语言中遇到的 类 和你在JS中模拟的“类”不一样。
总而言之,类是软件设计中的一种可选模式,你能够选择在JavaScript中使用或不使用它。由于许多开发者都对面向类的软件设计情有独钟,咱们将在本章剩下的部分中探索一下,为了使用JS提供的东西维护类的幻觉要付出什么代价,和咱们经历的痛苦。
Class 机制
在许多面向类语言中,“标准库”都提供一个叫“栈”(压栈,弹出等)的数据结构,用一个Stack类表示。这个类拥有一组变量来存储数据,还拥有一组可公开访问的行为(“方法”),这些行为使你的代码有能力与(隐藏的)数据互动(添加或移除数据等等)。
可是在这样的语言中,你不是直接在Stack上操做(除非制造一个 静态的 类成员引用,但这超出了咱们要讨论的范围)。Stack类仅仅是 任何 的“栈”都会作的事情的一个抽象解释,但它自己不是一个“栈”。为了获得一个能够对之进行操做的实在的数据结构,你必须 实例化 这个Stack类。
建筑物
传统的"类(class)"和"实例(instance)"的比拟源自于建筑物的建造。
一个建筑师会规划出一栋建筑的全部性质:多宽,多高,在哪里有多少窗户,甚至墙壁和天花板用什么材料。在这个时候,她并不关心建筑物将会被建造在 哪里,她也不关心有 多少 这栋建筑的拷贝将被建造。
同时她也不关心这栋建筑的内容——家具,墙纸,吊扇等等——她仅关心建筑物含有何种结构。
她生产的建筑学上的蓝图仅仅是建筑物的“方案”。它们不实际构成咱们能够实在进入其中并坐下的建筑物。为了这个任务咱们须要一个建筑工。建筑工会拿走方案并精确地依照它们 建造 这栋建筑物。在真正的意义上,他是在将方案中意图的性质 拷贝 到物理建筑物中。
一旦完成,这栋建筑就是蓝图方案的一个物理实例,一个有望是实质完美的 拷贝。而后建筑工就能够移动到隔壁将它再重作一遍,建造另外一个 拷贝。
建筑物与蓝图间的关系是间接的。你能够检视蓝图来了解建筑物是如何构造的,但对于直接考察建筑物的每一部分,仅有蓝图是不够的。若是你想打开一扇门,你不得不走进建筑物自身——蓝图仅仅是为了用来 表示 门的位置而在纸上画的线条。
一个类就是一个蓝图。为了实际获得一个对象并与之互动,咱们必须从类中建造(也就是实例化)某些东西。这种“构建”的最终结果是一个对象,典型地称为一个“实例”,咱们能够按须要直接调用它的方法,访问它的公共数据属性。
这个对象是全部在类中被描述的特性的 拷贝。
你不太期望走进一栋建筑以后发现,一份用于规划这栋建筑物的蓝图被裱起来挂在墙上,虽然蓝图可能在办公室的公共记录的文件中。类似地,你通常不会使用对象实例来直接访问和操做类,可是这至少对于断定对象实例来自于 哪一个类 是可能的。
与考虑对象实例与它源自的类的任何间接关系相比,考虑类和对象实例的直接关系更有用。一个类经过拷贝操做被实例化为对象的形式。
如你所见,箭头由左向右,从上至下,这表示着概念上和物理上发生的拷贝操做。
构造器(Constructor)
类的实例由类的一种特殊方法构建,这个方法的名称一般与类名相同,称为 “构造器(constructor)”。这个方法的明确的工做,就是初始化实例所需的全部信息(状态)。
好比,考虑下面这个类的假想代码(语法是自创的):
为了 制造 一个CoolGuy实例,咱们须要调用类的构造器:
注意,CoolGuy类有一个构造器CoolGuy(),它实际上就是在咱们说new CoolGuy(..)时调用的。咱们从这个构造器拿回一个对象(类的一个实例),咱们能够调用showOff()方法,来打印这个特定的CoolGuy的特殊才艺。
显然,跳绳使Joe看起来很酷。
类的构造器 属于 那个类,几乎老是和类同名。同时,构造器大多数状况下老是须要用new来调用,以便使语言的引擎知道你想要构建一个 新的 类的实例。
类继承(Class Inheritance)
在面向类的语言中,你不只能够定义一个能够初始化它本身的类,你还能够定义另一个类 继承 自第一个类。
这第二个类一般被称为“子类”,而第一个类被称为“父类”。这些名词明显地来自于亲子关系的比拟,虽然这种比拟有些扭曲,就像你立刻要看到的。
当一个家长拥有一个和他有血缘关系的孩子时,家长的遗传性质会被拷贝到孩子身上。明显地,在大多数生物繁殖系统中,双亲都平等地贡献基因进行混合。可是为了这个比拟的目的,咱们假设只有一个亲人。
一旦孩子出现,他或她就从亲人那里分离出来。这个孩子受其亲人的继承因素的严重影响,可是独一无二。若是这个孩子拥有红色的头发,这并不意味这他的亲人的头发 曾经 是红色,或者会自动 变成 红色。
以类似的方式,一旦一个子类被定义,它就分离且区别于父类。子类含有一份从父类那里得来的行为的初始拷贝,但它能够覆盖这些继承的行为,甚至是定义新行为。
重要的是,要记住咱们在讨论父 类 和子 类,而不是物理上的东西。这就是这个亲子比拟让人糊涂的地方,由于咱们实际上应当说父类就是亲人的DNA,而子类就是孩子的DNA。咱们不得不从两套DNA制造出(也就是初始化)人,用获得的物理上存在的人来与之进行谈话。
让咱们把生物学上的亲子放在一边,经过一个稍稍不一样的角度来看看继承:不一样种类型的载具。这是用来理解继承的最经典(也是争议不断的)的比拟。
让咱们从新审视本章前面的Vehicle和Car的讨论。考虑下面表达继承的类的假想代码:
注意: 为了简洁明了,这些类的构造器被省略了。
咱们定义Vehicle类,假定它有一个引擎,有一个打开打火器的方法,和一个行驶的方法。但你永远也不会制造一个泛化的“载具”,因此在这里它只是一个概念的抽象。
而后咱们定义了两种具体的载具:Car和SpeedBoat。它们都继承Vehicle的泛化性质,但以后它们都对这些性质进行了合适的特化。一辆车有4个轮子,一艘快艇有两个引擎,意味着它须要在打火时须要特别注意要启动两个引擎。
多态(Polymorphism)
Car定义了本身的drive()方法,它覆盖了从Vehicle继承来的同名方法。可是,Car的drive()方法调用了inherited:drive(),这表示Car能够引用它继承的,覆盖以前的原版drive()。SpeedBoat的pilot()方法也引用了它继承的drive()拷贝。
这种技术称为“多态(polymorphism)”,或“虚拟多态(virtual polymorphism)”。对咱们当前的状况更具体一些,咱们称之为“相对多态(relative polymorphism)”。
多态这个话题比咱们能够在这里谈到的内容要宽泛的多,但咱们当前的“相对”意味着一个特殊层面:任何方法均可以引用位于继承层级上更高一层的其余方法(同名或不一样名)。咱们说“相对”,由于咱们不绝对定义咱们想访问继承的哪一层(也就是类),而实质上在说“向上一层”来相对地引用。
在许多语言中,在这个例子中使用inherited:的地方使用了super关键字,它的基于这样的想法:一个“超类(super class)”是当前类的父亲/祖先。
多态的另外一个方面是,一个方法名能够在继承链的不一样层面上有多种定义,并且在解析哪一个方法在被调用时,这些定义能够适当地被自动选择。
在咱们上面的例子中,咱们看到这种行为发生了两次:drive()在Vehicle和Car中定义, 而ignition()在Vehicle和SpeedBoat中定义。
注意: 另外一个传统面向类语言经过super给你的能力,是从子类的构造器中直接访问父类构造器。这很大程度上是对的,由于对真正的类来讲,构造器属于这个类。然而在JS中,这是相反的——实际上认为“类”属于构造器(Foo.prototype...类型引用)更恰当。由于在JS中,父子关系仅存在于它们各自的构造器的两个.prototype对象间,构造器自己不直接关联,并且没有简单的方法从一个中相对引用另外一个(参见附录A,看看ES6中用super“解决”此问题的class)。
能够从ignition()中具体看出多态的一个有趣的含义。在pilot()内部,一个相对多态引用指向了(继承的)Vehicle版本的drive()。而这个drive()仅仅经过名称(不是相对引用)来引用ignition()方法。
语言的引擎会使用哪个版本的ignition()?是Vehicle的仍是SpeedBoat的?它会使用SpeedBoat版本的ignition()。 若是你 能 初始化Vehicle类自身,而且调用它的drive(),那么语言引擎将会使用Vehicle的ignition()定义。
换句话说,ignition()方法的定义,根据你引用的实例是哪一个类(继承层级)而 多态(改变)。
这看起来过于深刻学术细节了。不过为了好好地与JavaScript的[[Prototype]]机制的相似行为进行对比,理解这些细节仍是很重要的。
若是类是继承而来的,对这些类自己(不是由它们建立的对象)有一个方法能够 相对地 引用它们继承的对象,这个相对引用一般称为super。
记得刚才这幅图:
注意对于实例化(a1,a2,b1,和b2) 和 继承(Bar),箭头如何表示拷贝操做。
从概念上讲,看起来子类Bar可使用相对多态引用(也就是super)来访问它的父类Foo的行为。然而在现实中,子类不过是被给与了一份它从父类继承来的行为的拷贝而已。若是子类“覆盖”一个它继承的方法,原版的方法和覆盖版的方法实际上都是存在的,因此它们都是能够访问的。
不要让多态把你搞糊涂,使你认为子类是连接到父类上的。子类获得一份它须要从父类继承的东西的拷贝。类继承意味着拷贝。
多重继承(Multiple Inheritance)
能回想起咱们早先提到的亲子和DNA吗?咱们说过这个比拟有些奇怪,由于生物学上大多数后代来自于双亲。若是类能够继承自其余两个类,那么这个亲子比拟会更合适一些。
有些面向类的语言容许你指定一个以上的“父类”来进行“继承”。多重继承意味着每一个父类的定义都被拷贝到子类中。
表面上看来,这是对面向类的一个强大的加成能力,给咱们能力去将更多功能组合在一块儿。然而,这无疑会产生一些复杂的问题。若是两个父类都提供了名为drive()的方法,在子类中的drive()引用将会解析为哪一个版本?你会老是不得不手动指明哪一个父类的drive()是你想要的,从而失去一些多态继承的优雅之处吗?
还有另一个所谓的“钻石问题”:子类“D”继承自两个父类(“B”和“C”),它们两个又继承自共通的父类“A”。若是“A”提供了方法drive(),而“B”和“C”都覆盖(多态地)了这个方法,那么当“D”引用drive()时,它应当使用那个版本呢(B:drive()仍是C:drive())?
事情会比咱们这样窥豹一斑能看到的复杂得多。咱们在这里把它们记下来,以便于咱们能够将它和JavaScript机制的工做方式比较。
JavaScript更简单:它不为“多重继承”提供原生机制。许多人认为这是好事,由于省去的复杂性要比“减小”的功能多得多。可是这并不能阻挡开发者们用各类方法来模拟它,咱们接下来就看看。
Mixins(混合)
当你“继承”或是“实例化”时,JavaScript的对象机制不会 自动地 执行拷贝行为。很简单,在JavaScript中没有“类”能够拿来实例化,只有对象。并且对象也不会被拷贝到另外一个对象中,而是被 连接在一块儿(详见第五章)。
由于在其余语言中观察到的类的行为意味着拷贝,让咱们来看看JS开发者如何在JavaScript中 模拟 这种 缺失 的类的拷贝行为:mixins(混合)。咱们会看到两种“mixin”:明确的(explicit) 和 隐含的(implicit)。
明确的 Mixins(Explicit Mixins)
让咱们再次回顾前面的Vehicle和Car的例子。由于JavaScript不会自动地将行为从Vehicle拷贝到Car,咱们能够建造一个工具来手动拷贝。这样的工具常常被许多包/框架称为extend(..),但为了说明的目的,咱们在这里叫它mixin(..)。
注意: 重要的细节:咱们谈论的再也不是类,由于在JavaScript中没有类。Vehicle和Car分别只是咱们实施拷贝的源和目标对象。
Car如今拥有了一份从Vehicle获得的属性和函数的拷贝。技术上讲,函数实际上没有被复制,而是指向函数的 引用 被复制了。因此,Car如今有一个称为ignition的属性,它是一个ignition()函数引用的拷贝;并且它还有一个称为engines的属性,持有从Vehicle拷贝来的值1。
Car已经 有了drive属性(函数),因此这个属性引用没有被覆盖(参见上面mixin(..)的if语句)。
重温"多态(Polymorphism)"
咱们来考察一下这个语句:Vehicle.drive.call( this )。我将之称为“显式假想多态(explicit pseudo-polymorphism)”。回想咱们前一段假想代码的这一行是咱们称之为“相对多态(relative polymorphism)”的inherited:drive()。
JavaScript没有能力实现相对多态(ES6以前,见附录A)。因此,由于Car和Vehicle都有一个名为drive()的函数,为了在它们之间区别调用,咱们必须使用绝对(不是相对)引用。咱们明确地用名称指出Vehicle对象,而后在它上面调用drive()函数。
但若是咱们说Vehicle.drive(),那么这个函数调用的this绑定将会是Vehicle对象,而不是Car对象(见第二章),那不是咱们想要的。因此,咱们使用.call( this )(见第二章)来保证drive()在Car对象的环境中被执行。
注意: 若是Car.drive()的函数名称标识符没有与Vehicle.drive()的重叠(也就是“遮蔽”;见第五章),咱们就不会有机会演示“方法多态(method polymorphism)”。由于那样的话,一个指向Vehicle.drive()的引用会被mixin(..)调用拷贝,而咱们可使用this.drive()直接访问它。被选用的标识符重叠 遮蔽 就是为何咱们不得不使用更复杂的 显式假想多态(explicit pseudo-polymorphism) 的缘由。
在拥有相对多态的面向类的语言中,Car和Vehicle间的链接被创建一次,就在类定义的顶端,这里是维护这种关系的惟一场所。
可是因为JavaScript的特殊性,显式假想多态(由于遮蔽!) 在每个你须要这种(假想)多态引用的函数中 创建了一种脆弱的手动/显式连接。这可能会显著地增长维护成本。并且,虽然显式假想多态能够模拟“多重继承”的行为,但这只会增长复杂性和代码脆弱性。
这种方法的结果一般是更加复杂,更难读懂,并且 更难维护的代码。应当尽量地避免使用显式假想多态,由于在大部分层面上它的代价要高于利益。
混合拷贝(Mixing Copies)
回忆上面的mixin(..)工具:
如今,咱们考察一下mixin(..)如何工做。它迭代sourceObj(在咱们的例子中是Vehicle)的全部属性,若是在targetObj (在咱们的例子中是Car)中没有名称与之匹配的属性,它就进行拷贝。由于咱们是在初始对象存在的状况下进行拷贝,因此咱们要当心不要将目标属性覆盖掉。
若是在指明Car的具体内容以前,咱们先进行拷贝,那么咱们就能够省略对targetObj检查,可是这样作有些笨拙且低效,因此一般不优先选用:
不论哪一种方法,咱们都显式地将Vehicle中的非重叠内容拷贝到Car中。“mixin”这个名称来自于解释这个任务的另外一种方法:Car混入Vehicle的内容,就像你吧巧克力碎片混入你最喜欢的曲奇饼面团。
这个拷贝操做的结果,是Car将会独立于Vehicle运行。若是你在Car上添加属性,它不会影响到Vehicle,反之亦然。
注意: 这里有几个小细节被忽略了。仍然有一些微妙的方法使两个对象在拷贝完成后还能互相“影响”对方,好比他们共享一个共通对象(好比数组)的引用。
因为两个对象还共享它们的共通函数的引用,这意味着 即使手动将函数从一个对象拷贝(也就是混入)到另外一个对象中,也不能 实际上模拟 发生在面向类的语言中的从类到实例的真正的复制。
JavaScript函数不能真正意义上地被复制(以标准,可靠的方式),因此你最终获得的是同一个共享的函数对象(函数是对象;见第三章)的 被复制的引用。举例来讲,若是你在一个共享的函数对象(好比ignition())上添加属性来修改它,Vehicle和Car都会经过这个共享的引用而受影响。
在JavaScript中明确的mixin是一种不错的机制。可是它们显得言过其实。和将一个属性定义两次相比,将属性从一个对象拷贝到另外一个对象并不会产生多少 实际的 好处。这对咱们刚才提到的给函数对象引用增长的微妙变化来讲,显得尤其正确。
若是你明确地将两个或更多对象混入你的目标对象,你能够 某种程度上模拟 “多重继承”的行为,可是在将方法或属性从多于一个源对象那里拷贝过来时,没有直接的办法能够解决名称的冲突。有些开发者/包使用“late binding(延迟绑定)”和其余诡异的替代方法来解决问题,但从根本上讲,这些“技巧” 一般 得不偿失(并且低效!)。
要当心的是,仅在明确的mixin可以实际提升代码可读性时使用它,而若是你发现它使代码变得更很难追溯,或在对象间创建了没必要要或笨重的依赖性时,要避免使用这种模式。
若是正确使用mixin使你的问题变得比之前 困难,那么你可能应当中止使用mixin。实际上,若是你不得不使用复杂的包/工具来处理这些细节,这可能标志着你正走在更困难,也许不必的道路上。在第六章中,咱们将试着提取一种更简单的方法来实现咱们指望的结果,同时免去这些周折。
寄生继承(Parasitic Inheritance)
明确的mixin模式的一个变种,在某种意义上是明确的而在某种意义上是隐含的,称为“寄生继承(Parasitic Inheritance)”,它主要是由Douglas Crockford推广的。
这是它如何工做:
如你所见,咱们一开始从“父类”(对象)Vehicle制造了一个定义的拷贝,以后将咱们的“子类”(对象)定义混入其中(按照须要保留父类的引用),最后将组合好的对象car做为子类实例传递出去。
注意: 当咱们调用new Car()时,一个新对象被建立并被Car的this所引用(见第二章)。可是因为咱们没有使用这个对象,而是返回咱们本身的car对象,因此这个初始化建立的对象就被丢弃了。因此,Car()能够不用new关键字调用,就能够实现和上面代码相同的功能,并且还能够节省对象的建立和回收。
隐含的 Mixin(Implicit Mixins)
隐含的mixin和前面解释的 显式假想多态 是紧密相关的。因此它们须要注意相同的事项。
考虑这段代码:
Something.cool.call( this )既能够在“构造器”调用中使用(最多见的状况),也能够在方法调用中使用(如这里所示),咱们实质上“借用”了Something.cool()函数并在Another环境下,而非Something环境下调用它(经过this绑定,见第二章)。结果是,Something.cool()中进行的赋值被实施到了Another对象而非Something对象。
那么,这就是说咱们将Something的行为“混入”了Another。
虽然这种技术看起来有效利用了this再绑定的功能,也就是生硬地调用Something.cool.call( this ),可是这种调用不能被做为相对(也更灵活的)引用,因此你应当 提升警戒。通常来讲,尽可能避免使用这种结构 来保持代码干净并且容易维护。
复习
类是一种设计模式。许多语言提供语法来启用天然而然的面向类的软件设计。JS也有类似的语法,可是它的行为和你在其余语言中熟悉的工做原理 有很大的不一样。
类意味着拷贝。
当一个传统的类被实例化时,就发生了类的行为向实例中拷贝。当类被继承时,也发生父类的行为向子类的拷贝。
多态(在继承链的不一样层级上拥有同名的不一样函数)也许看起来意味着一个从子类回到父类的相对引用连接,可是它仍然只是拷贝行的的结果。
JavaScript 不会自动地 (像类那样)在对象间建立拷贝。
mixin模式经常使用于在 某种程度上 模拟类的拷贝行为,可是这一般致使像显式假想多态那样(OtherObj.methodName.call(this, ...))难看并且脆弱的语法,这样的语法又常致使更难懂和更难维护的代码。
显式mixin和类 拷贝 又不彻底相同,由于对象(和函数!)仅仅是共享的引用被复制,不是对象/函数自身被复制。不注意这样的微小之处一般是各类陷阱的根源。
通常来说,在JS中模拟类一般会比解决当前 真正 的问题埋下更多的坑。