在js中继承有三种方法:原型链继承、借用构造函数继承、组合继承。
同样前两者都有一定的弊端,组合继承则解决了前两者的不足和结合了它们的优点。这里逐步讲解。
(1)原型链继承:利用原型让一个引用类型继承另一个引用类型的属性和方法。让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含指向另一个构造函数的指针。加入另一个原型又是另一个原型的实例,上述关系依然成立,如此层层递进,就构成了原型链。
function Parent(name,age) {
this.name = name;
this.age = age;
this.arr = [1,2,3];
}
Parent.prototype.sayAge = function() {
console.log(this.age);
}
function Child() {}
Child.prototype = new Parent("qpq",21);
var child1 = new Child();
var child2 = new Child();
child1.arr.push(4,5);
console.log(child1.__proto__);
//Parent {name: "qpq", age: 21, arr: Array(5)}
console.log(child2.__proto__);
//Parent {name: "qpq", age: 21, arr: Array(5)}
原型链继承的问题:
a、若被继承者属性中包含引用类型中,则继承者的所有实例将对这个引用产生同化,及改变一个实例中的引用值其他实例中的引用值也发生改变。
从上述代码的两个输出结果可以看出,给实例child1中的arr数组添加了两个值,使之长度变为5,导致实例child2中的arr数组长度也变成了5.
b、在创建子类型的实例时,不能向超类型的构造函数中传递参数。因此很少单独使用原型链。
(2)借用构造函数:在子类构造函数中调用超类型的构造函数(使用call或apply改变this指向),解决上述原型链继承的两个问题。
function Parent(name,age) {
this.name = name;
this.age = age;
this.arr = [1,2,3];
this.sayAge = function() {
console.log(this.age);
}
}
function Child(name,age,height) {
Parent.call(this,name,age);
this.height = height;
}
//可以在调用超类型构造函数后再添加应该在子类型中定义的属性。定义在调用超类型构造函数之后是为了确保超类型构造函数不会重写子类型的属性。
var child1 = new Child("qpq",21,160);
var child2 = new Child("000",22,160);
child1.arr.push(4,5);
console.log(child1);
//Child {name: "qpq", age: 21, arr: Array(5), sayAge: ƒ, height: 160}
console.log(child2);
//Child {name: "000", age: 22, arr: Array(3), sayAge: ƒ, height: 160}
缺点:方法和属性全都在构造函数中定义,那么函数的复用性就没有任何优势。因此也很少单独使用该方法。
(3)组合继承:将原型链继承和借用构造函数继承组合到一起,发挥二者之长——使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。
function Parent(name,age) {
this.name = name;
this.age = age;
this.arr = [1,2,3];
this.sayAge = function() {
console.log(this.age);
}
}
Parent.prototype.sayAge = function() {
console.log(this.age);
}
function Child(name,age,height) {
Parent.call(this,name,age);
this.height = height;
}
var child1 = new Child("qpq",21,160);
var child2 = new Child("000",22,160);
child1.arr.push(4,5);
console.log(child1);
//Child {name: "qpq", age: 21, arr: Array(5), sayAge: ƒ, height: 160}
console.log(child2);
//Child {name: "000", age: 22, arr: Array(3), sayAge: ƒ, height: 160}
优点:既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。
同样前两者都有一定的弊端,组合继承则解决了前两者的不足和结合了它们的优点。这里逐步讲解。
(1)原型链继承:利用原型让一个引用类型继承另一个引用类型的属性和方法。让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含指向另一个构造函数的指针。加入另一个原型又是另一个原型的实例,上述关系依然成立,如此层层递进,就构成了原型链。
function Parent(name,age) {
this.name = name;
this.age = age;
this.arr = [1,2,3];
}
Parent.prototype.sayAge = function() {
console.log(this.age);
}
function Child() {}
Child.prototype = new Parent("qpq",21);
var child1 = new Child();
var child2 = new Child();
child1.arr.push(4,5);
console.log(child1.__proto__);
//Parent {name: "qpq", age: 21, arr: Array(5)}
console.log(child2.__proto__);
//Parent {name: "qpq", age: 21, arr: Array(5)}
原型链继承的问题:
a、若被继承者属性中包含引用类型中,则继承者的所有实例将对这个引用产生同化,及改变一个实例中的引用值其他实例中的引用值也发生改变。
从上述代码的两个输出结果可以看出,给实例child1中的arr数组添加了两个值,使之长度变为5,导致实例child2中的arr数组长度也变成了5.
b、在创建子类型的实例时,不能向超类型的构造函数中传递参数。因此很少单独使用原型链。
(2)借用构造函数:在子类构造函数中调用超类型的构造函数(使用call或apply改变this指向),解决上述原型链继承的两个问题。
function Parent(name,age) {
this.name = name;
this.age = age;
this.arr = [1,2,3];
this.sayAge = function() {
console.log(this.age);
}
}
function Child(name,age,height) {
Parent.call(this,name,age);
this.height = height;
}
//可以在调用超类型构造函数后再添加应该在子类型中定义的属性。定义在调用超类型构造函数之后是为了确保超类型构造函数不会重写子类型的属性。
var child1 = new Child("qpq",21,160);
var child2 = new Child("000",22,160);
child1.arr.push(4,5);
console.log(child1);
//Child {name: "qpq", age: 21, arr: Array(5), sayAge: ƒ, height: 160}
console.log(child2);
//Child {name: "000", age: 22, arr: Array(3), sayAge: ƒ, height: 160}
缺点:方法和属性全都在构造函数中定义,那么函数的复用性就没有任何优势。因此也很少单独使用该方法。
(3)组合继承:将原型链继承和借用构造函数继承组合到一起,发挥二者之长——使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。
function Parent(name,age) {
this.name = name;
this.age = age;
this.arr = [1,2,3];
this.sayAge = function() {
console.log(this.age);
}
}
Parent.prototype.sayAge = function() {
console.log(this.age);
}
function Child(name,age,height) {
Parent.call(this,name,age);
this.height = height;
}
var child1 = new Child("qpq",21,160);
var child2 = new Child("000",22,160);
child1.arr.push(4,5);
console.log(child1);
//Child {name: "qpq", age: 21, arr: Array(5), sayAge: ƒ, height: 160}
console.log(child2);
//Child {name: "000", age: 22, arr: Array(3), sayAge: ƒ, height: 160}
优点:既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。