一、原型链继承
**将父类的实例作为子类的原型,**他的特点是实例是子类的实例也是父类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实现,缺点是来自原型对象的所有属性都被继承的实例共享,无法实现多继承,无法向父类构造函数传递。
function Animal(name) {
this.name = name
// 动态类型模式 利用原型共享方法
if (this.eat !== 'function') {
Animal.prototype.eat = function () {
console.log(`吃${this.name}`)
}
}
}
function Dog(age) {
this.age = age
}
// 用父类的实例来充当子类的原型对象
Dog.prototype = new Animal('动物')
var d = new Dog(3)
console.log(d.age) // 3
console.log(d.name) // '动物'
d.eat() // '吃'
二、构造继承
使用父类的构造函数来增强子类实例,既赋值父类的实例属性给子类。
构造继承可以向父类传递参数,可以实现多继承,通过call或apply多个父类对象。但是构造继承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数的复用,每个子类都有父类实例函数的副本,影响性能。
function Animal(name) {
this.name = name
// 动态类型模式 利用原型共享方法
if (this.eat !== 'function') {
Animal.prototype.eat = function () {
console.log('吃')
}
}
}
function Dog(name, age) {
// 借用父类的构造函数
Animal.call(this, name)
/* 借用构造函数Animal 相当于执行了以下操作:
this.name = name
Animal.prototype.eat = function() {
console.log('吃')
}
*/
this.age = age
}
var d = new Dog('哈士奇', 2)
console.log(d.name) // '哈士奇'
console.log(d.age) // 2
d.eat() // Uncaught TypeError: d.eat is not a function
这种方式与上种方式恰好相反。子类不会继承父类原型中的任何属性和方法(即父类的原型不会被共享),但是它可以在构建实例时向父类传参,将父类本身的属性带到子类实例本身上。
三、组合继承
通过调用父类构造,继承父类的属性并保存传参的优点,然后通过实例作为子类原型实现函数复用。可以继承父类原型上的属性,可以传参,可复用。每个新势力引入的构造函数属性是私有的。
function Animal(name) {
this.name = name
// 动态类型模式 利用原型共享方法
if (this.eat !== 'function') {
Animal.prototype.eat = function () {
console.log(`吃${this.name}`)
}
}
}
function Dog(name, age) {
// 1.借用父类的构造函数
Animal.call(this, name) // 第一次调用 Animal
this.age = age
}
// 2.用父类实例充当子类原型
Dog.prototype = new Animal('动物') // 第二次调用 Animal
Dog.prototype.constructor = Dog //手动指明构造函数
var d = new Dog('哈士奇', 2)
console.log(d.name) // '哈士奇'
console.log(d.age) // 2
d.eat() // '吃哈士奇'
四、原型继承
父类的引用类型属性会被所有子类实例共享,任何一个子类实例修改了父类的引用类型属性,其他子类实例都会受到影响
创建子类实例的时候,不能向父类传参
function object(o) {
function F() {};
F.prototype = o;
return new F();
}
var person = {
name: 'binguo',
values: ['one', 'two', 'three']
}
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.values.push("Rob");
console.log(person.values); //[ 'one', 'two', 'three', 'Rob' ]
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.values.push("Barbie");
console.log(person.values); //"Shelby,Court,Van,Rob,Barbie"
五、寄生继承
其实就是在原型式继承得到对象的基础上,在内部再以某种方式来增强对象
function createAnother(original) {
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ // 以某种方式来增强这个对象
alert("hi");
};
return clone; // 返回这个对象
}
寄生式继承与原型式继承紧密相关,与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。
六、寄生组合继承:
通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的时候,就不会初始化两次实例方法/属性,便面的组合继承的缺点。合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。
function inheritPrototype( subObj, superObj ){
var proObj = object( superObj.prototype ); //复制父类superObj的原型对象
proObj.constructor = subObj; //constructor指向子类构造函数
subObj.prototype = proObj; //再把这个对象给子类的原型对象
}
在函数内部,第一步是创建超类型原型的一个副本。
第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。
最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我们就可以用调用 inheritPrototype()函数的语句,去替换前面例子中为子类型原型赋值的语句了
// 红宝书上面的方式
function object(o) {
var F = function() {};
F.prototype = o;
return new F();
}
function inhert(subType,superType) {
var prototype = object(superType.prototype);
// 构造器重定向,如果没有通过实例找回构造函数的需求的话,可以不做构造器的重定向。但加上更严谨
prototype.constructor = subType;
subType.prototype = prototype;
}
function Super(name) {
this.name = name;
}
Super.prototype.sayName = function() {
console.log(this.name);
}
function Sub(name, age) {
Super.call(this, name);
this.age = age;
}
inhert(Sub, Super);
var sub = new Sub('Tom', 22);
sub.sayName();