1.通过原型链继承
通过把构造函数A的原型指向构造函数B的实例上,实现A的实例继承B如下所示
function SuperType() {
this.superName = 'super'
}
SuperType.prototype.getSuperValue = function() {
return this.superName;
}
function SubType() {
this.subName = 'sub'
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subName;
}
let sub = new SubType();
console.log(sub.getSuperValue()); // super
缺点:(1)B的实例如果存在引用类型的属性,即A的原型对象上存在引用类型的属性,那么A的实例对该引用类型的属性的修改会影响到A的所有实例。(2)子类型在实例化时不能给构造函数传参。
2.盗用构造函数
通过在子类中调用父类构造函数来实现的
function SuperType(name) {
this.name = name;
}
function SubType() {
// 继承SuperType并传参
SuperType.call(this, 'zhangsan')
this.age = 29;
}
let instance = new SubType();
console.log(instance.name); // zhangsan
console.log(instance.age); // 29
优点:解决了不能向父类传递参数的问题。
缺点:无法实现函数方法的复用,子类对象无法访问父类的原型对象的方法。
3.组合继承
结合了原型链和盗用构造函数两种方法实现的继承,如下代码所示:
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
// 盗用构造函数
SuperType.call(this, name);
this.age = age;
}
// 原型链继承
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
console.log(this.age);
};
let instance1 = new SubType("zhangsan", 20);
instance1.colors.push("black");
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
instance1.sayName(); // "zhangsan";
instance1.sayAge(); // 20
let instance2 = new SubType("lisi", 22);
console.log(instance2.colors); // ['red', 'blue', 'green']
instance2.sayName(); // "lisi";
instance2.sayAge(); // 22
优点:既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
缺点:调用了两次父类的构造函数,导致子类的原型对象中多了一些不必要的属性。
4.原型式继承
思想:在一个对象的基础上再创建一个新对象 原型式继承通过Object.create()方法实现,该方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。
let person = {
name: "Nicholas",
friends: ["zhangsan"]
};
let p1 = Object.create(person);
p1.name = "Greg";
p1.friends.push("lisi");
let p2 = Object.create(person);
p2.name = "Linda";
p2.friends.push("wangwu");
console.log(person.friends); // ['zhangsan', 'lisi', 'wangwu']
Object.create(person)实际上返回一个新对象,该新对象的原型是person。
优点:适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。
缺点:属性中包含引用值时始终会在相关对象间共享,跟使用原型模式是一样的。
5.寄生式继承
思想:封装一个继承过程的函数,该函数接收一个对象,复制该对象的副本(继承),对副本对象进行扩展,最后返回副本对象
function createAnother(oriObj) {
let clone = Object.create(oriObj); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 扩展一个方法
console.log("hi");
};
return clone; // 返回这个对象
}
let person = {
name: "Nicholas",
friends: ["zhangsan", "lisi"]
};
let p1 = createAnother(person);
p1.sayHi(); // "hi"
代码中Object.create()函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用。
优点:适合主要关注对象,而不在乎类型和构造函数的场景。
缺点:扩展的方法不能复用。
5.寄生式组合继承
思路:不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。其实就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
// 盗用构造函数
SuperType.call(this, name);
this.age = age;
}
// 原型链继承
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
};
let instance1 = new SubType("zhangsan", 20);
instance1.colors.push("black");
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
instance1.sayName(); // "zhangsan";
instance1.sayAge(); // 20
let instance2 = new SubType("lisi", 22);
console.log(instance2.colors); // ['red', 'blue', 'green']
instance2.sayName(); // "lisi";
instance2.sayAge(); // 22
优点:解决了组合式继承的缺点,即避免了在子类的原型对象上创建不必要的属性的问题。