1.原型链继承
每个构造函数都有一个原型对象,原型对象有一个属性指回构造函数,实例内部也有一个属性指向原型对象。那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
//创建一个Animal
function Animal(){
this.name='animal';
}
Animal.prototype.getAnimalName=function(){
console.log(this.name+'getAnimalName');
}
//创建Dog
function Dog(){
this.name='dog';
}
//原型链继承 Dog继承来自Animal原型对象和实例的方法属性
Dog.prototype=new Animal();
//检查Dog的原型对象是否是指向Animal的原型对象 不建议使用__proto__ 很多浏览器已废弃
console.log(Dog.prototype.__proto__ === Animal.prototype);
//要在继承之后再去原型对象上定义自己所需的属性和方法
Dog.prototype.getDogName=function(){
console.log(this.name+'getDogName');
}
var dog=new Dog();
dog.getAnimalName();
dog.getDogName();
这个案例中实现继承的关键,是 Dog 没有使用默认原型,而是将其替换成了一个新的对象。这个新的对象恰好是 Animal 的实例。
还要注意,由于 Dog.prototype 的 constructor 属性被重写指向Animal,所以 d1.constructor 也指向 Animal,想要指回Dog可以修改Dog.prototype.constructor=Dog。
属性和方法的搜索会一直持续到原型链的末端。
1.2.默认原型
实际上,原型链中还有一环。默认情况下,所有引用类型都继承自 Object,这也是通过原型链实现的。任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默认方法的原因。
Dog 继承 Animal,而 Animal 继承 Object。在调用 d1.toString()时,实际上调用的是保存在Object.prototype 上的方法。
可以通过instanceof运算符用来检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
function Animal() {
this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
console.log(this.name + 'getAnimalName');
}
// 创建Animal的实例
var a1 = new Animal()
a1.getAnimalName(); //animalgetAnimalName
function Dog() {
this.name = 'dog';
}
Dog.prototype = new Animal();
// 新方法
Dog.prototype.getDogName = function () {
console.log(this.name + 'getDogName');
}
// 覆盖父类已有的方法
Dog.prototype.getAnimalName = function () {
console.log('我覆盖了父类的方法');
}
var d1 = new Dog();
d1.getAnimalName(); // 我覆盖了父类的方法
d1.getDogName();
Animal的getAnimalName()是原型链已经存在的方法但在这里会被Dog后面同名的方法遮蔽。并没有将Animal原型对象上的getAnimal()方法替换掉,Animal的实例调用该方法还原本的东西。
1.3原型链的破坏
以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。
function Animal(){
this.name='animal';
}
Animal.prototype.getAnimalName=function(){
console.log(this.name);
}
function Dog(){
this.name='dog';
}
//继承
Dog.prototype=new Animal();
//原型链被破坏 相当Dog.prototype=new Object();Dog。prototype会指向Object的原型对象
Dog.prototype={
getDogName(){
console.log(this.name);
},
someOtherMethod(){
return false;
}
};
var d1=new Dog();
d1.getAnimalName();//报错
原型链继承的优点:不用每创建一个实例对象就需要创建一个方法属性,原型对象的方法属性可以共享,解决了内存资源浪费的问题。
缺点:
1.子类不能给父类构造函数传递参数
2.正因为属性方法共享导致引用值会在所有实例间共享,让不同的实例拿到相同的引用值。如下案例:
function Animal(){
this.category=['cat','rabbit'];
}
function Dog(){}
//继承Animal
Dog.prototype=new Animal();
var d1=new Dog();
d1.category.push('dog');
console.log(d1.category);//['cat','rabbit','dog']
var d2=new Dog();
console.log(d2.category);//['cat','rabbit','dog']
2.经典继承
使用apply()和 call()方法以新创建的对象为上下文执行构造函数。改变this的指向来拿到父类构造函数的属性方法。
function Animal(){
this.cetagory=['cat','rabbit'];
}
function Dog(){
//通过call()、apply() 来继承Animal
Animal.call(this);
}
//所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal()最后,d1去调用Animal方法时,Animal内部的this指向就指向了d1。那么Animal内部this上的所有属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的categorys属性副本。他们互不影响。
var d1=new Dog();
d1.category.push('dog');
console.log(d1.category);//['cat','rabbit','dog']
var d2=new Dog();
console.log(d2.category);//['cat','rabbit']
经典继承的缺点:子类也不能访问父类原型上定义的方法;必须在构造函数中定义方法,因此函数不能复用;每个子类的实例都持有父类的实例方法的副本,浪费内存,影响性能,而且无法实现父类的实例方法的复用。
优点:就是可以在子类构造函数中向父类构造函数传参。
function Animal(name){
this.name=name;
}
function Dog(name,age){
//继承Animal 并传参
Animal.call(this,name);
//实例属性
this.age=age;
}
var d1=new Dog('dog',10);
console.log(d1.name);//dog
console.log(d1.age);//10
3.组合继承
组合继承(有时候也叫伪经典继承)综合了原型链和经典继承函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过经典继承函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function Animal(name){
this.name=name;
this.category=['cat','rabbit'];
}
Animal.prototype.sayName=function(){
console.log(this.name);
}
function Dog(name,age){
//继承Animal属性
Animal.call(this,name);
this.age=age;
}
//继承Animal原型上的属性方法
Dog.prototype=new Animal();
Dog.prototype.sayAge=function(){
console.log(this.age);
}
var d1=new Dog('dog',20);
d1.category.push('monkey');
console.log(d1.category);//['cat','rabbit','monkey']
d1.sayName();//dog
d1.sayAge();//20
var d2=new Dog('mouse',5);
console.log(d2.category);//['cat','rabbit]
d2.sayName();//mouse
d2.syaAge();//5