在 JavaScript 中,每个对象同时是一个实例和一个类。要进行继承,您可以使用任何对象实例作为原型。
在 Python、C++ 等中,有类和实例作为单独的概念。为了进行继承,您必须使用基类创建一个新类,然后可以使用该类生成派生实例。
为什么 JavaScript 会朝这个方向发展(基于原型的面向对象)?相对于传统的、基于类的 OO,基于原型的 OO 有哪些优点(和缺点)?
GEB
是什么/谁?
这里有大约一百个术语问题,主要是围绕某人(不是你)试图让他们的想法听起来像最好的。
所有面向对象的语言都需要能够处理几个概念:
数据的封装以及对数据的相关操作,除其他外,称为数据成员和成员函数,或称为数据和方法。继承,能够说这些对象就像其他对象集一样,除了这些变化多态性(“许多形状”),其中一个对象自己决定要运行哪些方法,这样你就可以依赖于语言正确路由您的请求。
现在,就比较而言:
首先是整个“类”与“原型”的问题。这个想法最初始于 Simula,其中使用基于类的方法,每个类表示一组共享相同状态空间(读取“可能值”)和相同操作的对象,从而形成一个等价类。如果您回顾一下 Smalltalk,因为您可以打开一个类并添加方法,这实际上与您在 Javascript 中可以执行的操作相同。
后来的 OO 语言希望能够使用静态类型检查,所以我们在编译时得到了固定类集的概念。在公开课版本中,您有更多的灵活性;在较新的版本中,您可以在编译器中检查某些需要测试的正确性。
在“基于类”的语言中,复制发生在编译时。在原型语言中,操作存储在原型数据结构中,在运行时被复制和修改。然而,抽象地说,一个类仍然是共享相同状态空间和方法的所有对象的等价类。当您向原型添加一个方法时,您实际上是在创建一个新的等价类的元素。
现在,为什么要这样做?主要是因为它在运行时提供了一种简单、合乎逻辑、优雅的机制。现在,要创建一个新对象或创建一个新类,您只需执行一次深拷贝,复制所有数据和原型数据结构。然后,您或多或少地免费获得继承和多态性:方法查找始终包括按名称向字典询问方法实现。
最终出现在 Javascript/ECMA 脚本中的原因基本上是,当我们在 10 年前开始使用它时,我们正在处理功能不那么强大的计算机和不那么复杂的浏览器。选择基于原型的方法意味着解释器可以非常简单,同时保留面向对象的理想属性。
可以在论文 Self: The Power of Simplicity 中找到一个稍微偏向于基于原型的方法的比较。该论文提出以下支持原型的论点:
通过复制创造。从原型创建新对象是通过一个简单的操作来完成的,复制,一个简单的生物学隐喻,克隆。从类创建新对象是通过实例化完成的,其中包括对类中格式信息的解释。实例化类似于根据计划建造房屋。复制对我们来说是一个比实例化更简单的隐喻。预先存在的模块的示例。原型比类更具体,因为它们是对象的示例,而不是格式和初始化的描述。这些示例可以通过使模块更易于理解来帮助用户重用模块。基于原型的系统允许用户检查典型代表,而不是要求他从描述中理解。支持独一无二的对象。 Self 提供了一个框架,可以轻松地包含具有自己行为的独一无二的对象。由于每个对象都有命名槽,并且槽可以保存状态或行为,因此任何对象都可以具有唯一的槽或行为。基于类的系统是为存在许多具有相同行为的对象的情况而设计的。没有语言支持一个对象拥有自己的独特行为,创建一个保证只有一个实例的类是很尴尬的[想想单例模式]。 Self 没有这些缺点。任何对象都可以根据自己的行为进行定制。唯一的对象可以保存唯一的行为,不需要单独的“实例”。消除元回归。基于类的系统中的任何对象都不能自给自足;需要另一个对象(它的类)来表达它的结构和行为。这导致了概念上无限的元回归:一个点是类 Point 的一个实例,它是元类 Point 的一个实例,它是一个元类 Point 的实例,无穷无尽。另一方面,在基于原型的系统中,一个对象可以包含它自己的行为。不需要其他物体来为它注入生命。原型消除了元回归。
Self 可能是第一种实现原型的语言(它还开创了其他有趣的技术,如 JIT,后来进入 JVM),因此阅读 the other Self papers 也应该是有益的。
point
是类 Point
的一个实例,它是元类 standard-class
的一个实例,它是它自身的一个实例,无限。
您应该在 Douglas Crockford 之前签出 great book on JavaScript。它很好地解释了 JavaScript 创建者所做的一些设计决策。
JavaScript 的重要设计方面之一是其原型继承系统。对象是 JavaScript 中的一等公民,以至于常规函数也被实现为对象(准确地说是“函数”对象)。在我看来,当它最初设计为在浏览器中运行时,它是用来创建大量单例对象的。在浏览器 DOM 中,您会发现窗口、文档等都是单例对象。此外,JavaScript 是松散类型的动态语言(与 Python 是强类型的动态语言相反),因此,通过使用“原型”属性实现了对象扩展的概念。
所以我认为在 JavaScript 中实现的基于原型的 OO 有一些优点:
适用于松散类型的环境,无需定义显式类型。实现单例模式非常容易(在这方面比较 JavaScript 和 Java,你就会知道我在说什么)。提供在不同对象的上下文中应用对象方法的方法,从对象动态添加和替换方法等(在强类型语言中不可能做到的事情)。
以下是原型 OO 的一些缺点:
实现私有变量的简单方法。使用 Crockford 使用闭包的魔法来实现私有变量是可能的,但它绝对不像在 Java 或 C# 中使用私有变量那么简单。我还不知道如何在 JavaScript 中实现多重继承(就其价值而言)。
主流 OOP 基于类的语言(如 c# 或 java)与基于原型的语言(如 javascript)之间的区别在于能够在运行时修改对象类型,而在 c# 或 java 中,它们通过固定类而放弃了这种能力,转而支持静态类型检查在编译时。 JS 一直更接近于 alan Kay 的 OOP 和 Smalltalk 或 simula 等语言的第一个原始设计。
这是通过使蓝图本身成为一个实例来实现的,基于原型的类型是可以在运行时访问和修改的实际实例,在 Javascript 中,使用原型对象非常容易,因为每个对象类型都有这个对象。
示例:输入 funcName.prototype.myNewMethod= function{ console.log("hello world")}