_小生_

掌握JavaScript:类和原型继承之间有什么区别?

原文链接: medium.com

“掌握JavaScript面试”是一系列帖子,旨在帮助候选人准备他们在面试高级JavaScript职位时可能遇到的常见问题。这些是我经常在面试中使用的问题。想从头开始?请参阅这里

注意:本文使用ES6示例。如果您尚未学习ES6,请参阅“如何学习ES6”

对象经常在JavaScript中使用,如何有效地使用它们将会为你 的工作效率有极大的帮组。事实上,糟糕的OO(面向对象)设计可能会导致项目失败。

与大多数其他语言不同,JavaScript的对象系统基于原型,而不是类。不幸的是,大多数JavaScript开发人员都不了解JavaScript的对象系统,或者如何充分利用它。有一些开发者确实理解它,但希望它表现得更像基于类的系统。结果是JavaScript的对象系统具有令人困惑的分裂个性,这意味着JavaScript开发人员需要了解原型和类

类和原型继承有什么区别?

这可能是一个棘手的问题,您可能需要通过后续问答来寻找答案,因此要特别注意学习差异,以及如何应用知识来编写更好的代码。

类继承: 类就像一个蓝图 - 要创建的对象的描述。类继承自类并创建子类关系:等级分类法。

实例通常使用带有new关键字的构造函数进行实例化。类继承也可以使用ES6中的class关键字。您可能从Java等语言中了解它们的类,JavaScript中的“class”与java中的“class”并不相同。而是使用构造函数。以下是ES6class关键字去构造函数:

class Foo { }
typeof Foo // 'function'

在JavaScript中,类继承是在原型继承之上实现的,但是并不意味着它做同样的事情:

JavaScript的类继承使用原型链将子Constructor.prototype连接到父Constructor.prototype以进行委派。通常,也会调用super()构造函数。这些步骤形成单祖先父/子层次结构创建OO设计中可用的最紧密耦合.

“类继承自类,创建子类关系:等级分类法。”

原型继承: 原型是工作对象实例. 对象直接从其他对象继承。

实例可以由许多不同的源对象组成,允许容易的选择性继承和扁平的[[Prototype]]委托层次结构。换句话说,类分类法不是原型OO 的自动副作用:这是一个关键的区别.

实例通常通过factory functions,对象文字或Object.create()实例化。

“原型是一个工作对象实例. 对象直接从其他对象继承。”

为什么这很重要?

继承基本上是一种代码重用机制:一种让不同类型的对象共享代码的方法。你共享代码的方式很重要,因为如果你弄错了,它会产生很多问题,具体来说:

类继承创建父/子对象分类法的副作用

这些分类法几乎不可能适用于所有新的用例,并且基类的广泛使用导致了脆弱的基类问题,**这使得当你弄错它们时很难修复它们。事实上,类继承在OO设计中引起许多众所周知的问题:

  • 紧耦合问题(类继承是oo设计中可用的最紧密耦合),这导致下一个......

  • 脆弱的基类问题

  • 不灵活的层次结构问题(最终,所有不断发展的层次结构对于新用途都是错误的)

  • 必要性重复问题(由于不灵活的层次结构,新的用例通常是通过复制而不是调整现有代码来实现的)

  • 大猩猩/香蕉问题(你想要的是香蕉,但你得到的是拿着香蕉和整个丛林的大猩猩)

我在演讲中更深入地讨论了一些问题,“经典继承已经过时:如何在Prototypal OO中思考”:[演讲](https://youtu.be/lKCCZTUx0sI)

所有这些问题的解决方案是支持对象组合而不是类继承。

“赞成对象组合而不是类继承。”“设计模式:可重用面向对象软件的元素”

总结:链接视频

所有继承都不好吗?

当人们说“赞成组成而不是继承”,这是“赞成组合超过继承”的缩写“。这是OO设计中的常识,因为类继承有许多缺陷并导致许多问题。人们常常在谈论类继承时忽略,这听起来像all inheritance很糟糕 - 但事实并非如此。

实际上有几种不同的继承,其中大多数都非常适合从多个组件对象组合复合对象。

三种不同的原型继承

在我们深入研究其他类型的继承之前,让我们仔细看看类继承的含义:

你可以在Codepen上试验这个例子.

BassAmp继承自GuitarAmp,而'ChannelStrip继承自'BassAmpGuitarAmp。这是OO设计出错的一个例子。ChannelStrip实际上不是一种GuitarAmp,实际上根本不需要BassAmp。一个更好的选择是创建一个ChannelStrip和GuitarAmp继承的新基类,但即便如此也有局限性。

最终,新的共享基类策略也会崩溃。

还有更好的方法。您可以使用对象组合继承您真正需要的东西:

在CodePen上进行试验.

如果你仔细观察,你可能会发现我们更具体地说明哪些对象获得哪些属性,因为有了合成。它不是真正的类继承选项。当你从一个类继承时,你得到了所有东西,即使你不想要它也是如此._

在这一点上,你可能会想到自己,“那很好,但原型在哪里?”

要理解这一点,您必须了解有三种不同的原型OO。

连续继承:通过复制源对象属性将要素直接从一个对象继承到另一个对象的过程。在JavaScript中,源原型通常被称为 mixins。从ES6开始,这个特性在JavaScript中有一个名为Object.assign()的便利工具。在ES6之前,这通常使用Underscore / Lodash的.extend()jQuery的$ .extend()等等......上面的组合示例使用了连接继承。

原型委托:在JavaScript中,对象可能具有委托原型的链接。如果在对象上找不到属性,则查找被委托委托原型,可能有一个链接到它自己的委托原型,依此类推,直到你到达Object.prototype,这是根代表。当你附加到Constructor.prototype并用new实例化时,这是连接起来的原型。您也可以使用Object.create()来实现此目的,甚至将此技术与串联混合,以便将多个原型展平为单个委托,或者在创建后扩展对象实例。

功能继承:在JavaScript中,任何函数都可以创建一个对象。当该函数不是构造函数(或class)时,它被称为工厂函数。功能继承的工作原理是从工厂生成对象,并通过直接为其分配属性来扩展生成的对象(使用连接继承)。Douglas Crockford创造了这个术语,但功能继承在JavaScript中已经普遍使用了很长时间。

正如您可能已经开始意识到的那样,连接继承是在JavaScript中实现对象组合的秘诀,这使得原型委派和功能继承更加有趣。

当大多数人想到JavaScript中的原型OO时,_他们会想到原型委托。现在你应该看到他们错过了很多东西。委托原型不是类继承的绝佳替代 - 对象组合是

为什么组合物对脆弱的基类问题免疫

要理解脆弱的基类问题及其不适用于组合的原因,首先您必须了解它是如何发生的:

  1. A是基类

  2. B继承自A

  3. C继承自B

  4. D继承自B

C调用super,它在B中运行代码。B调用super,它在A中运行代码。

AB包含CD所需的无关特征。D是一个新的用例,在A的初始代码中需要slightly different行为而不是C需要。所以新手开发人员去调整A'的初始化代码。__ ** \ _ C \ _中断因为它取决于现有行为**,并且D`开始工作。

我们这里有的是在“A”和“B”之间散布的特征,即“C”和“D”需要以各种方式使用。CD不使用'AB的每个特征......他们只想继承一些已经在'A和'B`中定义的东西。但是通过继承和调用“超级”,你不会选择你继承的东西。你继承了一切:

“......面向对象语言的问题在于,他们已经拥有了所有这些隐含的环境。你想要一个香蕉,但你得到的是拿着香蕉和整个丛林的大猩猩。“〜乔阿姆斯特朗 - ”工作中的编码员“

使用Composition 想象一下,你有功能而不是类:

feat1, feat2, feat3, feat4

C需要feat1feat3D需要feat1feat2feat4

const C = compose(feat1, feat3);const D = compose(feat1, feat2, feat4);

现在,想象一下你发现'D需要与'feat1略有不同的行为。它实际上并不需要改变feat1,相反,你可以制作feat1 **的定制版本并使用它。您仍然可以继承feat2feat4中的现有行为而不做任何更改:

const D = compose(custom1, feat2, feat4);

而且__ \ _ C \ _仍然不受影响

这种类继承不可能的原因是因为当你使用类继承时,你会购买整个现有的类分类。

如果您想稍微适应一个新的用例,您要么最终复制现有分类法的部分(必要性重复问题),要么重构依赖于现有分类法的所有内容以使分类法适应新用法案例由于脆弱的基类问题

组合物对两者都免疫。

你认为你知道原型,但......

如果你被教导构建类或构造函数并继承那些,你所教的是不是原型继承。你被教导如何使用原型模仿类继承**。请参阅“关于JavaScript中继承的常见误解”.

在JavaScript中,类继承依赖于很久以前内置于语言中的非常丰富,灵活的原型继承功能,但是当你使用类继承 - 甚至是在原型之上构建的ES6 +class继承时,你不是使用原型OO的全部功能和灵活性。事实上,你正在把自己画成角落,并且选择了所有的类继承问题

在JavaScript中使用类继承就像将新的特斯拉Model S推向经销商并将其换成生锈的1973 Ford Pinto。

探索系列