基于原型的继承与基于.class的继承

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

在Python中,C++等。有类和实例,作为单独的概念。为了执行继承,必须使用基类创建一个新类,然后可以使用该新类生成派生实例。

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


答案 1

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

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

  1. 封装数据以及对数据的关联操作,这些操作称为数据成员和成员函数,或数据和方法等。
  2. 继承,能够说这些对象就像其他对象集一样,除了这些变化
  3. 多态性(“许多形状”),其中对象自己决定要运行哪些方法,以便您可以依靠语言来正确路由请求。

现在,就比较而言:

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

后来的OO语言希望能够使用静态类型检查,所以我们在编译时得到了固定类集的概念。在开放级版本中,您具有更大的灵活性;在较新版本中,您可以在编译器上检查某些类型的正确性,否则这些正确性将需要测试。

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

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

最终使用Javascript / ECMA脚本的原因基本上是,当我们在10年前开始使用这个脚本时,我们处理的是功能要强大得多的计算机和复杂得多的浏览器。选择基于原型的方法意味着解释器可以非常简单,同时保留对象方向的理想属性。


答案 2

一个比较,稍微偏向于基于原型的方法,可以在论文《自我:简单的力量》中找到。该论文提出了以下支持原型的论点:

通过复制创建。从原型创建新对象是通过一个简单的操作来完成的,复制,用一个简单的生物学隐喻,克隆。从类创建新对象是通过实例化完成的,实例化包括对类中格式信息的解释。实例化类似于从平面图建造房屋。复制作为比实例化更简单的比喻吸引着我们。

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

支持独一无二的对象。Self提供了一个框架,可以很容易地包含具有自己行为的独一无二的对象。由于每个对象都有命名的槽,并且槽可以保存状态或行为,因此任何对象都可以具有唯一的槽或行为。基于类的系统是为存在许多具有相同行为的对象的情况而设计的。没有语言支持对象拥有自己独特的行为,并且创建一个保证只有一个实例的类是很尴尬的[想想单例模式]。自我没有受到这些缺点的影响。任何对象都可以使用自己的行为进行自定义。唯一对象可以保存唯一行为,不需要单独的“实例”。

消除元回归。在基于类的系统中,没有一个对象是自给自足的;需要另一个对象(其类)来表达其结构和行为。这导致了一个概念上无限的元回归:a是类的一个实例,它是元类的一个实例,它是元metaclass的一个实例,无穷大。另一方面,在基于原型的系统中,对象可以包含自己的行为;不需要其他物体来为它注入生命。原型消除了元回归。pointPointPointPoint

Self可能是第一个实现原型的语言(它也开创了其他有趣的技术,如JIT,后来进入了JVM),所以阅读其他Self论文也应该有启发性。