在JavaScript中,每个对象都同时是实例和类。要进行继承,您可以使用任何对象实例作为原型。
在Python、C++等语言中,存在类和实例这两个概念。为了进行继承,您必须使用基类创建一个新的类,然后使用该类来生成派生实例。
为什么JavaScript会朝着这个方向(基于原型的面向对象)发展?相对于传统的基于类的面向对象,基于原型的面向对象有哪些优点(和缺点)?
在JavaScript中,每个对象都同时是实例和类。要进行继承,您可以使用任何对象实例作为原型。
在Python、C++等语言中,存在类和实例这两个概念。为了进行继承,您必须使用基类创建一个新的类,然后使用该类来生成派生实例。
为什么JavaScript会朝着这个方向(基于原型的面向对象)发展?相对于传统的基于类的面向对象,基于原型的面向对象有哪些优点(和缺点)?
这里大约有一百个术语问题,主要集中在某个人(不是你)试图让他们的想法听起来像是最好的。
所有面向对象的语言都需要处理几个概念:
现在,在比较方面:
首先是整个“类”与“原型”之争。 这个想法最初始于Simula,其中基于类的方法中,每个类代表了共享相同状态空间(读作“可能的值”)和相同操作的一组对象,从而形成了等价类。 如果您回顾Smalltalk,因为您可以打开一个类并添加方法,这实际上与您在Javascript中所做的相同。
后来的面向对象语言希望能够使用静态类型检查,因此我们得到了在编译时固定类集合的概念。 在开放类版本中,您拥有更多的灵活性; 在新版本中,您可以在编译器上检查某些类型的正确性,否则需要进行测试。
在“基于类”的语言中,复制发生在编译时。 在原型语言中,操作存储在原型数据结构中,在运行时进行复制和修改。 抽象地说,一个类仍然是所有共享相同状态空间和方法的对象等价类。 当您向原型添加一个方法时,实际上就是在创建一个新等价类的元素。
那么,为什么要这样做呢?主要是因为它在运行时提供了一种简单、逻辑、优雅的机制。现在,要创建一个新对象或者创建一个新类,你只需要执行深度复制,复制所有数据和原型数据结构。然后你就能获得继承和多态性:方法查找始终通过按名称向字典请求方法实现。
之所以出现在JavaScript/ECMAScript中,基本上是因为在10年前我们刚开始接触这个领域时,我们面对的计算机和浏览器都远不如今天这么强大和复杂。选择基于原型的方法意味着解释器可以非常简单,同时保留面向对象编程的有益特性。
一篇略带偏见支持原型方法的比较可以在论文《自我:简单之力》中找到,链接为Self: The Power of Simplicity。该论文提出了以下原型方法的优势:
通过复制创建。使用原型创建新对象是通过一个简单的操作——复制,以及一个简单的生物学隐喻——克隆来完成的。而使用类创建新对象则需要实例化,其中包括解释类中的格式信息。实例化类似于按照图纸建造房屋。相比之下,复制比实例化更为简单易懂。
现有模块的例子。原型比类更具体,因为它们是对象的实例而不是格式和初始化的描述。这些示例可以帮助用户通过使它们更易于理解来重复使用模块。基于原型的系统允许用户检查一个典型的代表,而不要求他从描述中理解它。
支持独特对象。Self提供了一个框架,可以轻松地包括具有自己行为的独特对象。由于每个对象都有命名的插槽,插槽可以保存状态或行为,因此任何对象都可以拥有独特的插槽或行为。基于类的系统设计用于具有相同行为的许多对象的情况。语言中没有支持对象具有自己独特行为的语法,而且创建一个保证只有一个实例的类也很麻烦(例如单例模式)。Self没有这些缺点。任何对象都可以使用其自己的行为进行自定义。一个独特的对象可以持有独特的行为,而不需要单独创建一个“实例”。
消除元反归。在基于类的系统中,没有任何对象可以是自给自足的;它需要另一个对象(它的类)来表达其结构和行为。这导致了一个概念上的无限元反归:一个
point
是类Point
的一个实例,而Point
则是另一个类的实例。这个过程似乎没有止境。相比之下,原型方法不存在这样的问题,因为每个对象都是自我包容的。对象的行为和结构都被包含在它们自己的原型中,不需要引用其他对象。
元类
Point
的实例,是元元类Point
的实例,如此循环无限。另一方面,在基于原型的系统中,对象可以包含自己的行为;不需要其他对象来使它运作。原型消除了元回归(meta-regress)。
Self 可能是第一个实现原型的语言(也开创了其他有趣的技术,如 JIT,后来被引入 JVM),因此阅读 其他 Self 论文 也会具有指导意义。
funcName.prototype.myNewMethod= function{ console.log("hello world")}
GEB
? - Alex