javascript 原型继承(第四篇)---几种继承方式

来源:互联网 发布:js获取当前时间十分秒 编辑:程序博客网 时间:2024/06/05 23:59

今天要介绍的是,对象之间的”继承”的几种方式。

首先有一个问题,为什么继承还有几种方式呢?你看不管是java还是C++,继承就是继承,哪有几种继承方式,不过Javascript 是一种灵活的语言,之所以灵活,说不好听点就是设计得太简单,连基本的继承都要自己去实现,下面就说一下最常用的几种继承方式。

比如,现在有一个”动物”对象的构造函数。

function Animal(){    this.species = "动物";  }

还有一个”猫”对象的构造函数。

function Cat(name,color){    this.name = name;    this.color = color;  }

怎样才能使”猫”继承”动物”呢?

一、构造函数继承

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

function Cat(name,color){    Animal.apply(this, arguments);//在子对象增加这一行代码    this.name = name;    this.color = color;  }  var cat1 = new Cat("大毛","黄色");  alert(cat1.species); // 动物
二、原型链继承

第二种方法更常见,使用prototype属性,也就是常说的原型链继承。

如果”猫”的prototype对象,指向一个Animal的实例,那么所有”猫”的实例,就能继承Animal了。

   Cat.prototype = new Animal();  Cat.prototype.constructor = Cat;  var cat1 = new Cat("大毛","黄色");  alert(cat1.species); // 动物

代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。

Cat.prototype = new Animal();

它相当于完全删除了prototype 对象原先指向的值,然后指向了一个新值。但是,第二行又是什么意思呢?

Cat.prototype.constructor = Cat;//这一行什么意思呢?

原来,任何一个构造函数都有一个prototype属性,prototype的属性值指向一个对象,(prototype相当于一个指针,我们把它指向的对象暂叫prototype对象),而且这个prototype对象默认有一个constructor属性,指向这个构造函数本身。如下图所示,

SuperType是一个构造函数,右侧的方框就是它的原型,SuperType的prototype属性指向SuperType Prototype对象,SuperType Prototype对象的constructor属性指向它的构造函数。

如果没有”Cat.prototype = new Animal();”这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。

   Cat.prototype = new Animal();   alert(Cat.prototype.constructor == Animal); //true

更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。

alert(cat1.constructor == Cat.prototype.constructor); // true

因此,在运行”Cat.prototype = new Animal();”这一行之后,cat1.constructor也指向了Animal。

alert(cat1.constructor == Animal); // true

这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的,但是cat1.constructor却指向了Animal), 因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。 这就是第二行的意思。

这是很重要的一点,编程时务必要遵守, 在用原型链继承时,都要记着,如果替换了prototype对象

A.prototype = B;

那么,下一步必然是为prototype对象的constructor属性指回原来的构造函数。

A.prototype.constructor = A;
三、原型链继承的改进

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。

现在,我们先将Animal对象改写:

function Animal(){ }  Animal.prototype.species = "动物";

然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。

   Cat.prototype = Animal.prototype;  Cat.prototype.constructor = Cat;  var cat1 = new Cat("大毛","黄色");  alert(cat1.species); // 动物

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

所以,上面这一段代码其实是有问题的。请看第二行;

Cat.prototype.constructor = Cat;

这一句实际上把Animal.prototype对象的constructor属性也改掉了!

alert(Animal.prototype.constructor); // Cat

到这里,大家可以和这篇博客的第二部分的作一个对比,在第二部分中,实现继承的关键代码是

   Cat.prototype = new Animal();  Cat.prototype.constructor = Cat;

可以验证的是,

alert(Animal.prototype.constructor)//Animal

这就是

Cat.prototype = new Animal();//prototype相当于一个指针,指向一个对象,这里直接把new Animal()这个实例直接赋值给prototype指向的那个对象。

Cat.prototype = Animal.prototype;//这里便形成了指针的重定向,Cat的prototype指向了Animal的prototype所指向的那个对象,

之间的区别!所以这也就是Animal.prototype.constructor的指向一个没改变,一个改变了。

四、组合继承(最常用)

给Animal增加一个属性—-函数

function Animal(){     this.species = "动物";  }Animal.prototype.fun = function(){};//给Animal增加一个函数

实现Cat的继承,

function Cat(){    Super.call(this);   // 核心    this.name = name;   this.color = color}Cat.prototype = new Animal();    // 核心var cat1 = new Cat("大毛","黄色");alert(cat1.species); // 动物alert(sub1.fun === sub2.fun);   // true,说明sub1.fun和sub2.fun指向相同的地址,这便是组合继承的优点,各种继承方式的优缺点将在下一篇讲

到此,我们常用的javascript继承方式已经讲完了,相信也有人说还有拷贝继承的方式,但个人不太喜欢拷贝继承,即使是做js项目,以上的继承方式便足够了,我相信在实际编程中,你也只会用一种继承方式。


第五篇: javascript原型继承(第五篇)—几种继承方式的优缺点

参考自阮一峰的博客(另提一下,在阮一峰老师的这篇博客里的第四部分是有问题的,个人亲测,第五部分好像也有问题,也不太常用,就不放在我的博客里了)

原创粉丝点击