原型链继承
基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和函数(包括实例属性,函数和原型对象)。
function Animal () {
this.superType = 'Animal';
this.superSpeak=function(){
console.log('实例上的animal');
};
}
Animal.prototype.superSpeak = function () {
console.log(this.superType);
}
function Dog (name) {
this.name = name;
this.type = 'Dog';
this.getName=function(){
console.log(name);
}
}
//改变Dog的prototype指针,指向一个 Animal 实例
Dog.prototype = new Animal();//原型继承的关键,修改原型对象,使其指向另一个实例
//上面那行就相当于这么写
//var animal = new Animal();
//Dog.prototype = animal;
Dog.prototype.speak = function () {
console.log(this.type);
}
var doggie = new Dog('haDog');
doggie.getName(); //haDog 在当前实例中存在该函数,调用
doggie.superSpeak(); //实例上的animal 先到Animal的实例中查找函数(this指向的函数),有则调用(Animal的原型对象中的该同名函数被隐藏),无则继续向Animal的原型对象中查找该函数
console.log((doggie instanceof Dog)+","
+(doggie instanceof Animal)+","
+(doggie instanceof Object));
//true,true,true
上述代码中,Dog.prototype指向了一个Animal实例,在这个Animal实例中,其拥有所有该实例的属性和函数,以及Animal实例的原型对象所包含的所有属性和函数。所以Dog创建的实例包含自身实例属性和函数,以及原型对象(Animal实例)的所有属性和函数。
所以实现原型继承的实例,其属性和函数的搜索过程为:
- 当前实例中的属性和函数
- 覆盖 当前实例的原型对象 的实例中的属性和函数
- 覆盖 当前实例的原型对象 的实例中的原型对象
原型链继承很强大但也存在问题:
- 有些原型对象中的属性或函数不希望被共享
- 没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数
借用构造函数继承
使用借用构造函数实现继承,即在子类型构造函数的内部调用超类型构造函数,因为函数构造函数中的this指向是根据不同的运行环境来决定的,所以可以使用apply()和call()函数在(将来)新创建的对象上通过改写this指向来调用超类型构造函数,使得子类可以借用父类的构造函数创建自己的实例属性和函数(相当于在子类中执行父类构造函数,此时构造函数中this指向子类,创建的实例属性和函数自然属于子类)。
function Animal (weight) {
this.weight=weight;
}
function Dog (name,weight) {
this.name = name;//实例属性
Animal.call(this,weight);//继承了Animal,同时传递参数,此时Dog创建了属于自己的实例属性
//相当于在此处执行代码 this.weight=weight;(this指向当前Dog执行环境)
}
var doggie1 = new Dog('haDog',"100斤");//创建实例对象,在Dog构造函数中借用Animal构造函数完成参数传递
var doggie2 = new Dog('jinDog',"80斤");
console.log(doggie1.name+","+doggie1.weight);//haDog,100斤
console.log(doggie2.name+","+doggie2.weight);//jinDog,80斤
组合继承
仅仅是借用构造函数,属性和函数都在构造函数中定义,无法将函数复用共享,因此,将原型链继承和借用构造函数继承结合起来(组合继承)才能更好地编写程序。
function Animal(weight) {
this.weight=weight;
}
Animal.prototype.getWeight=function(){
console.log("Animal原型对象的getWeight函数:"+this.weight);
}
function Dog (name,weight) {
this.name = name;//实例属性
Animal.call(this,weight);//借用构造函数继承
}
Dog.prototype=new Animal();
var doggie1 = new Dog('haDog',"100斤");//原型链继承
var doggie2 = new Dog('jinDog',"80斤");
console.log(doggie1.name+","+doggie1.weight);//haDog,100斤
console.log(doggie2.name+","+doggie2.weight);//jinDog,80斤
doggie1.getWeight();//Animal原型的getWeight函数:100斤
doggie2.getWeight();//Animal原型的getWeight函数:80斤
组合继承是javascript中最常用的继承模式,且instanceof操作符和isPrototypeOf()函数也能够用于识别基于组合继承创建的对象。
组合继承的不足在于无论何种情况下,都会调用两次超类型的构造函数,一次是在子类型构造函数中借用超类的构造函数,一次是在修改子类型原型对象的时候需要创建超类的实例(超类构造函数创建)。
原型式继承
寄生式继承
寄生组合式继承
- 原型链继承,将父类的实例作为子类的原型,他的特点是实例是子类的实例也是父类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实现,缺点是来自原型对象的所有属性被所有实例共享,无法实现多继承,无法向父类构造函数传参。
- 构造继承,使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类,构造继承可以向父类传递参数,可以实现多继承,通过call多个父类对象。但是构造继承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数服用,每个子类都有父类实例函数的副本,影响性能
- 实例继承,为父类实例添加新特性,作为子类实例返回,实例继承的特点是不限制调用方法,不管是new 子类()还是子类()返回的对象具有相同的效果,缺点是实例是父类的实例,不是子类的实例,不支持多继承
- 拷贝继承:特点:支持多继承,缺点:效率较低,内存占用高(因为要拷贝父类的属性)无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
- 组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
- 寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点