基于原型和基于类的继承方式

234

在JavaScript中,每个对象都同时是实例和类。要进行继承,您可以使用任何对象实例作为原型。

在Python、C++等语言中,存在类和实例这两个概念。为了进行继承,您必须使用基类创建一个新的类,然后使用该类来生成派生实例。

为什么JavaScript会朝着这个方向(基于原型的面向对象)发展?相对于传统的基于类的面向对象,基于原型的面向对象有哪些优点(和缺点)?


14
JavaScript受到Self的影响,Self是第一种具有原型继承的语言。在那个时候,经典继承是风头正劲,最初引入的是Simula。然而,经典继承太过复杂。然后David Ungar和Randall Smith在阅读《GEB》后有了一个顿悟 -“最具体的事件可以作为一类事件的一般示例。”他们意识到面向对象编程不需要类。因此,Self诞生了。要了解原型继承如何优于经典继承,请阅读此文:https://dev59.com/GXE85IYBdhLWcg3wYigB#16872315 =) - Aadit M Shah
1
@AaditMShah 什么/谁是 GEB - Alex
6
@Alex GEB是Douglas Hofstadter所写的一本书,它缩写自Gödel Escher Bach。Kurt Gödel是一位数学家,Escher是一位艺术家,Bach则是一位钢琴家。 - Aadit M Shah
4个回答

230

这里大约有一百个术语问题,主要集中在某个人(不是你)试图让他们的想法听起来像是最好的。

所有面向对象的语言都需要处理几个概念:

  1. 数据的封装以及相关操作,不同的称呼包括数据成员和成员函数,或者数据和方法等。
  2. 继承,能够表示这些对象与另一组对象相似,但存在某些变化。
  3. 多态(“多种形式”),其中一个对象决定自己要运行哪些方法,因此您可以依赖语言来正确路由您的请求。

现在,在比较方面:

首先是整个“类”与“原型”之争。 这个想法最初始于Simula,其中基于类的方法中,每个类代表了共享相同状态空间(读作“可能的值”)和相同操作的一组对象,从而形成了等价类。 如果您回顾Smalltalk,因为您可以打开一个类并添加方法,这实际上与您在Javascript中所做的相同。

后来的面向对象语言希望能够使用静态类型检查,因此我们得到了在编译时固定类集合的概念。 在开放类版本中,您拥有更多的灵活性; 在新版本中,您可以在编译器上检查某些类型的正确性,否则需要进行测试。

在“基于类”的语言中,复制发生在编译时。 在原型语言中,操作存储在原型数据结构中,在运行时进行复制和修改。 抽象地说,一个类仍然是所有共享相同状态空间和方法的对象等价类。 当您向原型添加一个方法时,实际上就是在创建一个新等价类的元素。

那么,为什么要这样做呢?主要是因为它在运行时提供了一种简单、逻辑、优雅的机制。现在,要创建一个新对象或者创建一个新类,你只需要执行深度复制,复制所有数据和原型数据结构。然后你就能获得继承和多态性:方法查找始终通过按名称向字典请求方法实现。

之所以出现在JavaScript/ECMAScript中,基本上是因为在10年前我们刚开始接触这个领域时,我们面对的计算机和浏览器都远不如今天这么强大和复杂。选择基于原型的方法意味着解释器可以非常简单,同时保留面向对象编程的有益特性。


1
那个更改是否表达得更好? - Charlie Martin
2
不好意思,CLOS是来自80年代末的技术。Smalltalk则是从1980年开始流行的编程语言。而Simula则是在1967-68年间首次引入了完整的面向对象编程概念。 - Charlie Martin
3
@Stephano,它们并不像你想象的那么不同:Python、Ruby、Smalltalk使用字典来查找方法,而JavaScript和Self有类。在某种程度上,你可以认为区别只是基于原型的语言暴露了它们的实现方式。因此最好不要把这个问题过分炒作:这可能更像是EMACS和vi之间的争论。 - Charlie Martin
1
我记得Joel和Jeff在SO播客中讨论了Javascript的开发。如果我没记错的话,Javascript最初被设想为一种函数式语言,但最后一刻的管理恐慌导致添加了面向对象编程的内容。 - Benedict Cohen
25
有用的回答。评论中没用的垃圾少一点更好。我的意思是,CLOS或Smalltalk先出现有什么区别吗?反正这里大部分人都不是历史学家。 - Adam Arold
显示剩余13条评论

44

一篇略带偏见支持原型方法的比较可以在论文《自我:简单之力》中找到,链接为Self: The Power of Simplicity。该论文提出了以下原型方法的优势:

通过复制创建。使用原型创建新对象是通过一个简单的操作——复制,以及一个简单的生物学隐喻——克隆来完成的。而使用类创建新对象则需要实例化,其中包括解释类中的格式信息。实例化类似于按照图纸建造房屋。相比之下,复制比实例化更为简单易懂。

现有模块的例子。原型比类更具体,因为它们是对象的实例而不是格式和初始化的描述。这些示例可以帮助用户通过使它们更易于理解来重复使用模块。基于原型的系统允许用户检查一个典型的代表,而不要求他从描述中理解它。

支持独特对象。Self提供了一个框架,可以轻松地包括具有自己行为的独特对象。由于每个对象都有命名的插槽,插槽可以保存状态或行为,因此任何对象都可以拥有独特的插槽或行为。基于类的系统设计用于具有相同行为的许多对象的情况。语言中没有支持对象具有自己独特行为的语法,而且创建一个保证只有一个实例的类也很麻烦(例如单例模式)。Self没有这些缺点。任何对象都可以使用其自己的行为进行自定义。一个独特的对象可以持有独特的行为,而不需要单独创建一个“实例”。

消除元反归。在基于类的系统中,没有任何对象可以是自给自足的;它需要另一个对象(它的类)来表达其结构和行为。这导致了一个概念上的无限元反归:一个point是类Point的一个实例,而Point则是另一个类的实例。这个过程似乎没有止境。相比之下,原型方法不存在这样的问题,因为每个对象都是自我包容的。对象的行为和结构都被包含在它们自己的原型中,不需要引用其他对象。

元类Point的实例,是元元类Point的实例,如此循环无限。另一方面,在基于原型的系统中,对象可以包含自己的行为;不需要其他对象来使它运作。原型消除了元回归(meta-regress)。

Self 可能是第一个实现原型的语言(也开创了其他有趣的技术,如 JIT,后来被引入 JVM),因此阅读 其他 Self 论文 也会具有指导意义。


5
关于元回归的消除:在基于类的通用Lisp对象系统中,pointPoint类的一个实例,Point类是standard-class元类的一个实例,而standard-class又是其自身的一个实例,无穷无尽。 - Max Nanasy
1
自我论文的链接已失效。有效链接:自我:简单之力 | 自我语言参考书目 - user1201917

24
你应该看一下Douglas Crockford写的关于JavaScript的优秀书籍。它非常好地解释了JavaScript创建者所做的一些设计决策。
JavaScript的重要设计方面之一是其原型继承系统。在JavaScript中,对象是第一类公民,以至于常规函数也被实现为对象(确切地说是'Function'对象)。我认为,当它最初设计用于在浏览器内运行时,它的目的是用于创建许多单例对象。在浏览器DOM中,您会发现窗口、文档等都是单例对象。此外,JavaScript是一种松散类型的动态语言(与强类型的动态语言Python相对),因此通过使用“prototype”属性实现了对象扩展的概念。
因此,我认为JavaScript中实现的基于原型的OO具有一些优点:
  1. 适合在松散类型的环境中使用,无需定义显式类型。
  2. 使实现单例模式变得非常容易(比较一下JavaScript和Java,你就知道我在说什么)。
  3. 提供了将对象的方法应用于不同对象的上下文中,从对象动态添加和替换方法等方式(这些在强类型语言中不可能实现)。
以下是基于原型的OO的一些缺点:
  1. 没有简单的方法来实现私有变量。可以使用Crockford的技巧和closures来实现私有变量,但这绝对不像在Java或C#中使用私有变量那样轻松。
  2. 我还不知道如何在JavaScript中实现多重继承(值得一提的是)。

2
只需使用私有变量的命名约定,就像Python一样。 - aehlke
1
在 JavaScript 中,实现私有变量的方式是使用闭包,这与您选择的继承类型无关。 - Benja
7
Crockford对JavaScript造成了很大的伤害,因为一个相当简单的脚本语言已经变成了一种对其内部机制着迷的自恋行为。JS没有真正的私有关键字作用域或真正的多重继承:不要试图伪造它们。 - Hal50000

1
主流的面向对象编程语言,如 C#或 Java,与基于原型的语言(例如 JavaScript)之间的区别在于能够在运行时修改对象类型,而在 C#或 Java 中,它们为了静态类型检查放弃了此功能,通过在编译时使类固定不变。JS 一直更接近 Alan Kay 最初的 OOP 设计和 Smalltalk 或 Simula 等语言,这是通过使蓝图本身成为实例来实现的,在基于原型的类型中,类型是实际的实例,可以在运行时访问和修改,在 JavaScript 中,使用 prototype 对象非常容易,因为每个对象类型都有这个对象。
例如:type funcName.prototype.myNewMethod= function{ console.log("hello world")}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接