目录:
以下将介绍对象之间“继承”的五种方法。
目前我们有一个父类为Animal类,我们需要在它的基础上,继承创建一个Cat类,即让Cat类继承Animal类。
function Animal(){
this.species = "动物";
this.eat = function(){
console.log("eat");
}
}
一、构造函数绑定
第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
function Cat(name,color){
Animal.call(this);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
但是调用了Animal构造函数不等于继承了Animal,Cat创建的对象的原型是:
new Cat() ----> Cat.prototype ---> Object.prototype ----> null
cat1实例对象仅仅只是获得了Animal构造函数中的属性和方法,它并没有获取到Animal原型对象中的属性和方法,我们要的原型链应该是:
new Cat() ----> Cat.prototype ----> Animal.prototype ---> Object.prototype ----> null
因此,第一种方法不推荐。
二、prototype模式
第二种方法更常见,使用prototype属性。
如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
function Cat(name,color){
Animal.call(this);
this.name = name;
this.color = color;
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
1、Cat.prototype = new Animal(); 将Cat的prototype对象指向一个Animal的实例。
2、Cat.prototype.constructor = Cat;任何一个prototype对象都有一个constructor属性,指向它的构造函数。当我们定义了Cat.prototype = new Animal(); 时,此时的Cat.prototype.constructor指向Animal,因此我们需要重新将constructor纠正回Cat。
第二种方式的完美的实现了让Cat类继承Animal类,但是需要创建两次Animal,比较浪费资源。
三、直接继承prototype
由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
首先将Animal对象改写
function Animal(){ }
Animal.prototype.species = "动物";
Animal.prototype.eat = function(){
console.log("eat");
}
然后,将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。同时无法继承到Animal构造函数中的一切属性和方法。
因此,第三种方法不推荐。
四、利用空对象作为中介
由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。
function Cat(name,color){
Animal.call(this);
this.name = name;
this.color = color;
}
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。同时可以通过添加Animal.call(this);选择是否将Animal构造函数内的属性和方法都保存在与Cat中。
五、寄生组合式继承:保证原型继承中父级引用对象属性的独立性
function Cat(name,color){
Animal.call(this);
this.name = name;
this.color = color;
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
Cat直接以Animal.prototype的原型为原型,而不是new Animal(),那么也就不会继承Animal自己的属性,而又完美继承了Animal原型上的eat方法。通过借用构造函数又实现了引用属性的独立性。
六、ES6继承通过Babel编译成ES5的实现方法
通过使用ES6语法编写继承类,然后使用Babel编译成ES5语法的实现方式是:
function _inherits(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
subClass.__proto__ = superClass;
}
function Supper() {};
var Sub = function (_Supper) {
_inherits(Sub, _Supper);
function Sub() {
return Sub.__proto__.call(this);
}
return Sub;
}(Supper);
var demo = new Sub();
七、 拷贝继承
我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?
首先将Animal对象改写
function Animal(){ }
Animal.prototype.species = "动物";
Animal.prototype.eat = function(){
console.log("eat");
}
然后,再写一个函数,实现属性拷贝的目的。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。这种方式只会将Animal.prototype中的属性和方法拷贝到子类中,而且十分的不方便。
因此,这种方法不推荐。