基于原型与基于类的继承

IT技术 javascript oop inheritance prototype-programming
2021-02-04 13:25:55

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

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

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

3个回答

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

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

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

现在,就比较而言:

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

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

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

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

最终出现在 Javascript/ECMA 脚本中的原因基本上是,当我们 10 年前开始使用它时,我们处理的计算机功能要弱得多,浏览器也不那么复杂。选择基于原型的方法意味着解释器可以非常简单,同时保留了面向对象的理想特性。

@Stephano,它们并没有那么明显:Python、Ruby、Smalltalk 使用字典进行方法查找,而 javascript 和 Self 有类。在某种程度上,您可能会争辩说,区别只是面向原型的语言公开了它们的实现。所以最好不要把它变成大问题:这可能更像是 EMACS 和 vi 之间的争论。
2021-03-15 13:25:55
是的,那个段落读起来好像我不是这个意思?Dahl 和 Nyqvist 提出了“类”作为具有相同方法签名的事物的集合。
2021-03-20 13:25:55
那个改变说得更好吗?
2021-03-27 13:25:55
不,抱歉,CLOS 来自 80 年代后期的Dreamsongs.com/CLOS.html Smalltalk 来自 1980 en.wikipedia.org/wiki/Smalltalk和 Simula 从 1967-68 en.wikipedia.org/wiki/Simula完全面向对象
2021-03-28 13:25:55
有用的答案+1评论中不太有用的垃圾。我的意思是首先是 CLOS 还是 Smalltalk 有区别吗?无论如何,这里的大多数人都不是历史学家。
2021-03-31 13:25:55

可以在论文Self: The Power of Simplicity 中找到稍微偏向于基于原型的方法的比较该论文提出了以下支持原型的论点:

通过复制创造从原型创建新对象是通过简单的操作、复制和简单的生物学比喻,即克隆来完成的。从类创建新对象是通过实例化完成的,其中包括对类中格式信息的解释。实例化类似于根据计划建造房屋。复制对我们来说是一个比实例化更简单的比喻。

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

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

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

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

RE:消除元回归:在基于类的 Common Lisp 对象系统中,a 是 class 的point一个实例Point,它是 metaclass 的一个实例standard-class,它是它自身的一个实例,无限制。
2021-03-19 13:25:55
指向自我论文的链接已死。工作链接:自我:简单的力量| 自我书目
2021-03-26 13:25:55

你应该看看Douglas Crockford 写的本关于 JavaScript好书它很好地解释了 JavaScript 创建者所采取的一些设计决策。

JavaScript 的重要设计方面之一是其原型继承系统。对象是 JavaScript 中的一等公民,以至于常规函数也被实现为对象(准确地说是“函数”对象)。在我看来,当它最初设计为在浏览器中运行时,它旨在用于创建许多单例对象。在浏览器 DOM 中,您会发现窗口、文档等都是单例对象。此外,JavaScript 是松散类型的动态语言(与 Python 是强类型的动态语言相反),因此,通过使用“原型”属性实现了对象扩展的概念。

所以我认为在 JavaScript 中实现的基于原型的 OO 有一些优点:

  1. 适用于松散类型的环境,无需定义显式类型。
  2. 使实现单例模式变得非常容易(在这方面比较 JavaScript 和 Java,你就会知道我在说什么)。
  3. 提供在不同对象的上下文中应用对象方法的方法,从对象动态添加和替换方法等(在强类型语言中是不可能的)。

以下是原型 OO 的一些缺点:

  1. 没有简单的方法来实现私有变量。使用Crockford的魔法使用闭包来实现私有变量是可能的,但它绝对不像在 Java 或 C# 中使用私有变量那么简单。
  2. 我还不知道如何在 JavaScript 中实现多重继承(它的value)。
在 js 中,做私有变量的方法是使用闭包,这与您选择的继承类型无关。
2021-03-25 13:25:55
Crockford 在破坏 JavaScript 方面做了很多工作,因为一种相当简单的脚本语言已经变成了对其内部结构的大师级迷恋。JS 没有真正的私有关键字范围或真正的多重继承:不要试图伪造它们。
2021-04-06 13:25:55
只需使用私有变量的命名约定,就像 Python 那样。
2021-04-10 13:25:55