JavaScript面向对象的继承机制——原型式继承


JavaScript面向对象的继承机制——原型式继承

前言

         上篇文章说了一下类式继承,今天来说一下JavaScript的另一种继承机制——原型式继承,之所以过了这么久才写原型式继承,一是因为时间问题,二是因为,我不知道该怎么组织语言来说清楚原型式继承,原型式继承本身的实现是很简单的,但是其牵涉到的概念以及原理却不好说明白。原型式继承与类式继承截然不同。原型的本质是就是一个对象,所以说起原型继承时,我们最好忘掉关于类式继承的类与实例的相关知识。

原型式继承

         在介绍原型式继承之前,有必要先说一下原型以及原型链的概念,因为原型式继承是基于原型与原型链实现的。

原型

         原型是什么?原型该如何定义?我们创建一个新的构造函数(为方便描述,这里称其为对象A)时,JavaScript都会根据特定的规则为该函数创建一个prototype属性,该属性为指向一个对象(对象B)的引用,这个对象就是函数的原型,而且这个原型包含且一个名为constructor属性,默认情况下,JavaScript产生的原型,其constructor属性的值为指向该函数的一个引用,用表达式表示即为:

         A. prototype==B;

         B. constructor==A;

         可是当我们改变prototype属性的值时,也就是变更其原型为其他对象(对象C)时,这时由于C可能没有constructor属性或者其constructor属性不再是A,我们需要再修正C的constructor属性的值:

         C. constructor=A;

         这一点在上一篇文章中《JavaScript面向对象继承机制——类式继承》也有说到。

         当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性,该内部属性在有的实现中名字是_proto_,并且可以访问到,但在有的实现中不可访问到,但确实存在),指向构造函数的原型,其用表达式表述为:

         var a1=new A();

         a1.proto_==A. prototype==B;

A、B、a1、a2(a1、a2为A的实例对象)的关系可以通过下图来进行表述:


JavaScript面向对象的继承机制——原型式继承
 

 

图画的不是很好,但我想基本能表达出图中几个对象之间的关系,在图中我额外加了两个对象,一个是Object对象,一个Object对象的原型。之所以加这两个对象,就是想表述所有函数的默认原型都是Object的实例。因此默认原型的内部指针_proto_指向Object.prototype,这也正是自定义类型默认都会有toString()方法与valueOf()方法等的根本原因。

关于实例对象与原型的观点我是这么看的:

原型本质上只是一个实例对象,而实例对象本质上也只是一个指向其原型的(不是构造函数),并持有一个成员列表的结构。

原型链

         理解了原型对于原型链的理解就轻松了,由于原型的本质是一个对象,而对象的本质是一个指向其原型的(不是构造函数),并持有一个成员列表的结构。也就是说,一个对象的原型,会有其自己的原型,如此递推,直到某个原型对象的原型为Object的实例对象,也即达到了最后一个对象节点,这就构成了一条链表结构,这就是原型链。

         当我们以如下代码的方式取得一个对象的属性值时:

         Var a=new A(…);

         var name=a.name;

         JavaScript会先在对象a的成员列表中查找name属性,如果找到了,则返回其值,如果没有找到,则会沿着对象a的内部属性_proto_属性,去其引用的原型对象b的成员列表中查找name属性,如果找到,则返回name的值,如果没有找到则继续去对象b的内部属性_proto_指向的原型的成员列表中去查找,如此反复,直到如果其中某个原型对象的原型为Object的实例对象,如果还是没有找到,则返回undefined,否则返回name属性的值。这就是整个原型链在JavaScript中最最基本的一种使用方式。

原型式继承

我们知道基于类的办法来创建一个对象包含两个步骤,第一,定义一个类;第二,实例化该类以创建一个新对象,用这种方式创建的对象都有一套该类的所有实例属性的副本,但每个实例方法却是所有实例对象共享的,只有一份,只是每个实例对象都有一个指向该实例方法的引用,实际上这个引用也可以看作是实例对象的实例属性。

使用原型式继承时,并不需要定义类,只需直接创建一个对象即可,这个对象随后可以被其他新的对象重用,这主要得益于原型链(关于原型链的有关概念,会在文章结尾给于解说,有兴趣的朋友可以先去看那部分的知识,再回头继续接下来的内容)的查找机制,该对象即被称为原型对象。原型对象为其他对象应有的结构提供了一个模型或者说原型,这也是原型式继承这个名称的由来。

接下来我们首先演示一下使用原型式继承来重新设计实现上篇文章中的Person和Man:

 

/**

 * Person原型对象

 */

 var Person={

    name:"default name",

    getName:function(){

       return this.name;

    }

};

 

这里并没有定义一个Person的类,只是定义了一个Person对象字面量。然后我们可以定义一个Man类继承Person:

/**

  * Man类继承自Person。

  * @param {Object} wife

  */

 function Man(wife){

    this.wife=wife;

    this.getWife=function(){

       return this.wife;

    }

 }

 Man.prototype=Person;

 

上面的代码最后的一句就实现了Man到Person的继承。非常简单的继承方式!

而实际上我们可以有一种更简单的方式来实现这种继承,即编写一个clone函数:

/**

  * 返回一个继承了Object对象的对象!

  * @param {Object} object

  */

 function clone(object){

    function F(){};

    F.prototype=object;

    return new F();

 }

 

如下要取得继承Person的对象只需下面一句话即可:

var a1=clone(Person);

alert(a1.getName());//输出:default name

 

原型式继承与类式继承对比

在JavaScript中类式继承与原型式继承是两种完全不同的继承方式,他们生成的对象也有不同的行为方式,两种继承方式各有其优缺点。

类式继承更加符合我们接触到的类似Java语言那样的继承机制,虽然只是一个模仿,但是更容易为熟悉Java等类似的面向对象的编程语言的程序员所接受。

原型式继承更能节约内存,原型链读取成员的方式使得所有克隆出来的对象都共享每个属性与方法的唯一一份实例。只有在直接设置了某个克隆出来的对象的属性或者方法时,情况才会发生改变,但是原型式继承的这种优势在某些情况下因为其对继承而来的成员的读与写的不对等性,在有共享的引用型属性时,这种优势又会变为原型式继承的一种劣势,限于时间、精力以及个人水平,这里不多做解说。有兴趣的兄弟们可以与我交流。

 

结语

         JavaScript看似只是一种脚本语言,可是其复杂性并不简单,因为JavaScript的入门极易造成了其给人极其简单的假象,实际上,JavaScript也是一门极其精深的编程语言,由于时间、精力、以及个人水平的问题,很多地方本人可能说的都不是很清晰,或者比较简略,但是我已经尽自己的能力来尽量的想方设法的去描述清楚自己想要表达的意思,如果有疑问的兄弟,可以与本人互相交流学习。

相关推荐