Javascript面向对象编程(二):构造函数的继承
本文摘自Javascript面向对象编程(二):构造函数的继承。写本篇文章主要是为了更好的理解和巩固这篇文章所讲的构造函数的继承。这个系列文章Javascript 面向对象编程(一):封装、Javascript面向对象编程(二):构造函数的继承、Javascript面向对象编程(三):非构造函数的继承把JavaScript面向对象讲的非常好,强烈建议阅读这个系列文章,不用再看本篇文章了。 下面对通过构造函数实现继承的知识进行总结。比如,现在有一个“动物"对象的构造函数。如下:
function Animal(){
this.species = "动物";
}
还有一个"猫"对象的构造函数。
function Cat(name,color){怎样才能使“猫”继承”动物“呢?
this.name = name;
this.color = color;
}
方式一:构造函数绑定
第一种方法也是最简单的一种方法就是使用call()或appley()方法将父对象的构造函数绑定到子对象上,即在子对象的构造函数上加一行代码:function Animal(){
this.species = "动物";
}
function Cat(name,color){
Animal.apply(this);
this.name = name;
this.color = color;
}
var cat1 = new Cat();
console.log(cat1.species);//=> 动物
方法二:prototype模式
方法二更为常见。就是使prototype模式。如果”猫“的构造函数的prototype属性指向“动物”的一个实例,那么”猫“就继承了“动物”。代码如下:function Animal(){这种方式有个缺点,如果我有一个“狗”也需要继承“动物”,那么我们还得创建一个动物的实例(new Animal),有多少个子类需要继承"动物",就需要创建多少个动物的实例。这显然会造成内存的浪费,需要执行和创建"动物"的实例,效率不高。
this.species = "动物";
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
function Cat(name,color){
this.name = name;
this.color = color;
}
var cat1 = new Cat('花猫','黑色');
console.log(cat1.species);//=> 动物
这里在顺便说一下 var cat1 = new Cat(‘花猫’,'黑色');都做了些什么:
1)创建了一个新对象。
2)将Cat()作为新对象的方法调用(注意:这时this关键字就指向了这个新对象,并初始化了name和color属性)。
3)然后将这个新对象返回。
Cat()构造函数的每个实例都拥有一个name和color来保持自己的状态,是不可继承的。而所有的实例都继承species属性。
方法三:直接继承父类的prototype对象
方法三是对方法二的改进。由于Animal对象中不变的属性和方法可以直接写入Animal.prototype。我们可以让Cat跳过Animal的实例,直接继承Animal.prototype。首先我们先要对Animal构造函数进行一些改动:function Animal(){}然后,将Cat.prototype对象指向Animal.prototype对象,这样就完成了继承。代码如下:
Animal.prototype.species = "动物";
function Animal(){}方法三和方法二相比较的优点是执行效率较高,不用执行和建立Animal实例,比较省内存。但是缺点是Cat.prototype和Animal.prototype指向了同一个对象,任何对Cat.prototype的修改都会反映在Animal.prototype上。
Animal.prototype.species = "动物";
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
function Cat(name,color){
this.name = name;
this.color = color;
}
var cat1 = new Cat("大猫","黑色");
console.log(cat1.species);//=> 动物
所以上面代码中的Cat.prototype.constructor = Cat;实际上也把Animal.prototype.constructor属性也改掉了。这样做显然是不行滴。
方法四:利用空对象作为中介
由于方法二和方法三存在上述缺点,所以有了方法四。如果理解了方法二和方法三,方法四也是很容易理解的,它去除了二、三的缺点。示例代码如下:var F = function(){}//空的函数对象,new F()时几乎不占内存我们把上面的代码封装成一个函数,这样便于使用。示例代码如下:
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
function extend(Child,Parent){我们这样来使用extend()函数。示例代码如下:
var F = function(){}//空的函数对象,new F()时几乎不占内存
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;//这里只是修改了new F()实例对象的constructor属性,并不修改Animal.prototype的constructor属性
}
function Animal(){}extend()方法就是YUI库实现继承的方式。
Animal.prototype.species = "动物";
function Cat(name,color){
this.name = name;
this.color = color;
}
extend(Cat,Animal);
var cat1 = new Cat("大猫","黑色");
console.log(cat1.species);//=> 动物
console.log(cat1.constructor === Caat);//=>true
console.log(Animal.prototype.constructor === Animal);//=>true
方法五:拷贝继承
上面的几种方法都是采用prototype(原型对象)实现继承的。我们换一种思路,纯粹采用"拷贝"的方法实现继承。简单的说就是把父对象的所有属性和方法拷贝给子类对象,这不也能实现继承吗?所以有了第五种方法。首先还是将不变的、共享的属性和方法都放到Animal.prototype对象中。
function Animal(){}然后在写一个函数实现属性和方法拷贝的目的。
Animal.prototype.species = "动物";
function extend2(Child,Parent){我们这样来使用extend2函数。
var c = Child.prototype;
var p = Parent.prototype;
for(var i in p){
if(c.hasOwnProperty(i)) continue;//如果子类对象已经包含了父类对象属性或方法将不执行拷贝(这里看自己需要)
c[i] = p[i];
}
}
function Animal(){}
Animal.prototype.species = "动物";
function Cat(name,color){
this.name = name;
this.color = color;
}
extend2(Cat,Animal);
var cat1 = new Cat("大猫","黑色");
console.log(cat1.species);//=> 动物