原型继承
优点:同一个原型对象
缺点:不能修改原型对象,会影响所有实例
function Animal(){
this.type = "动物"
};
function Cat(name,color){
this.name = name;
this.color = color;
};
Cat.prototype = new Animal();
var c1 = new Cat('x','白色');
var c2 = new Cat('t','黑色');
c1.type //'动物'
可以看出Cat实例继承了Animal的type属性
拓展:
function Animal(){ //动物对象
this.type = '动物'
};
function Cat(name,color){ //猫对象
this.name = name;
this.color = color;
this.type='我是猫';
};
Cat.prototype = new Animal(); //猫的原型对象指向了动物函数
var cat1 = new Cat("小黄","黄色");
var cat2 = new Cat("小黑","黑色");
console.log(cat1.type); // '我是猫'
console.log(cat2.type); // '我是猫'
//想获取Animal成员值
console.log(cat1.__proto__.type); //'动物'
console.log(cat2.__proto__.type); //'动物'
从上面代码块的输出可以看出,如果当前构造器和父级Animal里面都有的属性,cat1.type取到的是当前构造器Cat里面的type属性,如果想要拿到原型对象Animal里面的,必须通过cat1.__proto__.type取到。如果用cat1.__proto__.type='xx'来修改原型对象上的属性,那么所有实例都会受到影响,这也是原型继承的一大弊端。
当我们访问一个原型对象的属性时,__proto__是一级级来获取,当继承关系很复杂,不知道究竟是怎么样继承关系,只能一层一层向上查找。
构造函数继承
优点:不存在修改原型对象影响所有实例,各自拥有独立属性
缺点:父类的成员会被创建多次,存在冗余且不是同一个原型对象
★通过call/apply只能拷贝成员,原型对象不会被拷贝。
function Animal(){
this.type = "动物"
};
function Cat(name,color){
Animal.apply(this); //将Animal对象的成员复制到Cat对象上
this.name = name;
this.color = color;
};
var cat1 = new Cat("小黄","黄色");
var cat2 = new Cat("小黑","黑色");
cat1.type = '我是小黄';
cat2.__proto__.type = '我是动物';
console.log(cat1.type); //'我是小黄' cat1被修改
console.log(cat2.type); //"动物"
从上面代码块的输出可以看出,由于是有apply()改变this的指向,相当于将Animal对象的成员复制到Cat对象上。当cat2通过__proto__改变了父类Animal的type属性时,不会影响自身的type的属性。只能通过cat1.type = ‘’来改变,此时Animal和Cat并不是指向同一个原型对象,每次创建一个子类父类成员都要被创建一次,存在冗余,这也是构造函数继承的一大弊端。
组合继承
有没有一种方法既能规避原型继承的子类实例的属性容易受到原型对象的影响,以及构造函数使子类和父类不指向同一个原型对象,存在冗余的缺陷呢?
function Animal(){
this.type = '动物'
};
Animal.prototype.eat = function(){console.log('吃猫粮')};
function Cat(name,color){
this.name = name;
this.color = color;
Animal.call(this);
};
Cat.prototype = new Animal();
var cat1 = new Cat("小黄","黄色");
var cat2 = new Cat("小黑","黑色");
cat1.type = '我是小黄'; //修改当前构造器中的属性
cat2.__proto__.type = '我是动物';//修改了原型对象的值,但并不影响cat1,cat2的值
console.log(cat1.type); //'我是小黄' //原型对象的值变化,并不影响构造函数值
console.log(cat2.type); //'动物'
console.log(cat2.__proto__.type); //'我是动物
cat1.eat(); //还可以调用原型对象中eat()方法
通过上面代码块的输出可以看到,cat2.__proto__.type = '我是动物'对原型属性进行修改,并没有影响到实例cat2里面的type,说明子类的实例不会受父类原型上的对象属性的改变而影响。Cat.prototype = new Animal()让他们属于同一个原型对象,这样在cat的实例对象里面仍然是可以取到在父级Animal的原型对象上的eat()方法,这样解决了构造函数继承方法种由于子类和父类不指向同一原型对象,每次创建一个子类父类成员都要被创建一次,存在冗余的弊端。
Class继承
class A {
constructor(){
this.a = 'a';
}
};
class B extends A{ //表示通过extends关键字 ,继承A类所有的属性和方法
constructor(){
super(); //表示父类的构造函数
this.b = 'b';
}
};
var a = new A();
var b = new B();
b.a //"a"
B类通过extends关键字,继承了A类所有的属性和方法。
子类必须在constructor种调用super()方法,否则新建实例会报错。super既可以当作函数用,也可以当作对象用,作为函数使用时,代表父类父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。
那么super作为对象是用的具体实例是怎么样的呢?
class A{
p(){
return 2;
}
}
class B extends A{
constructor(){
super();
console.log(super.p());//2 super此时作为对象,其实就是A类
}
}
let b = new B;
此时super.p()的super就是作为对象,其实就相当于时classA。
拓展:
class A {
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
return this.x
}
};
class B extends A{ //表示通过extends关键字 ,继承A类所有的属性和方法
constructor(x,y,b){
super(x,y); //表示父类的构造函数
this.b = b;
}
};
let b = new B('333','444','abc');
b.x //'333'
b.y //'444'
b.toString() //'abc'
super()里面需要加参数才能将数据传给继承了A类的属性及方法。