[ 原型继承的方式 | 青训营笔记 ]

114 阅读4分钟

原型继承的方式

1. 原型链继承

💡 方式: 将构造函数的实例赋值给原型对象

// 父类构造函数
function superf() {
    this.name = "xuxiaotong";
    this.color = ["red","green"]
}
// 子类构造函数
function subf() {
    this.age = "21"; 
}
// 原型链继承
subf.prototype = new superf();//subf继承了superf,通过原型,形成链条
// 子类实例
var test = new subf();
console.log(test.__proto__.__proto__ === superf.prototype) //true

保留了 instanceof 操作符和 isPrototypeOf() 方法判断继承关系的能力

console.log(superf.prototype.isPrototypeOf(test))  // true
console.log(test instanceof superf) // true

存在问题:

  1. 引用共享: 原型中包含的所有引用值会在所有实例间共享

    test.color.push("balck")
    var test1 = new subf()
    console.log(test1.color) // [ 'red', 'green', 'balck' ] 
    
  2. 超类无法传参: 子类在实例化时不能给父类的构造函数传参,无法在不影响所有对象实例的情况下把参数传进父类的构造函数中

2. 盗用构造函数

💡 方式: 在子类构造函数中调用父类的构造函数,使用 apply()call() 方法以新创建的对象为上下文执行构造函数

// 父类构造函数
function superf(name) {
    this.name = name
    this.color = ["red","green"]
    this.sayColor = () => {
        console.log(this.color)
    }
}
// 父类原型上的方法
superf.prototype.sayName = function(){
    console.log(this.name)
}
​
// 子类
function subf(name, age) {
    superf.call(this, name) // 盗用构造函数
    this.age = age
}

优点:

  1. 在子类实例化时可以向父类构造函数传参

    let test = new subf("Tom", 23)
    console.log(test.age)  // 23
    console.log(test.name)  // Tom
    
  2. 没有引用共享的问题: superf.call(this, name) 为子类的实例创建的新对象的上下文执行父类的初始化代码,每个实例都有自己的 color 属性

    test.color.push("balck")
    var test1 = new subf()
    console.log(test1.color) // [ 'red', 'green'] 
    

存在问题:

  1. 必须在构造函数中定义方法,通过父类原型定义的方法无法被子类访问到,无法复用
test.sayColor()   // [ 'red', 'green', 'balck' ]
test1.sayColor()  // [ 'red', 'green'] 
// console.log(test.sayName())  // 子类实例对象调用父类原型上的方法报错

3. 组合继承

💡 方式:

  1. 在子类构造函数中调用父类的构造函数
  2. 把父类的实例对象赋值给子类的原型对象
function superf(name) {
    this.name= name
}
superf.prototype.getName = function() {
    return this.name
}
// 盗用构造函数继承父类构造函数上的属性和方法
function subf(name,age) {
    superf.call(this, name)
    this.age = age
}
// 使用原型链继承父类原型上的属性和方法
subf.prototype = new superf()
var test = new subf("Tom", 23)
console.log(test.getName()) // Tom

弥补了原型链继承和盗用构造函数的不足

保留了 instanceof 操作符和 isPrototypeOf() 方法判断继承关系的能力

console.log(superf.prototype.isPrototypeOf(test))  // true
console.log(test instanceof superf) // true

存在问题: 调用了两次父类构造函数,存在效率问题

4. 原型式继承

💡 方式:

  1. 创建一个临时构造函数
  2. 将传入的对象赋值给这个构造函数的原型
  3. 返回临时构造函数的一个实例
function obj(o) {
    function F(){}
    F.prototype = o
    return new F()
}
let person = {
    name: "Tom",
    hobby: ["sing", "dance"]
}
let person1 = obj(person)
person1.name = "Ami"
person1.hobby.push("swim")
let person2 = obj(person)
person2.name = "John"
person2.hobby.push("read")
console.log(person.name)  // Tom
console.log(person1.name) // Ami
console.log(person2.name) // John
console.log(person.hobby) // [ 'sing', 'dance', 'swim', 'read' ]
console.log(person1.hobby)// [ 'sing', 'dance', 'swim', 'read' ]
console.log(person2.hobby)// [ 'sing', 'dance', 'swim', 'read' ]

es5 通过 增加 Object.create(obj[, {新增的属性}]) 方法将原型式继承的概念规范化了。

  1. 当只有一个参数时,Object.create()obj() 方法的效果相同

  2. Object.create() 第二个参数新增的属性会遮蔽原型上的同名属性

    let person3 = Object.create(person, {
        name: {value:"Samu"},
    })
    console.log(person3.name) // Samu
    

使用场景: 有一个对象,想在已有对象的基础上创建一个新的对象

存在问题: 引用共享问题,属性中包含的引用值始终会在相关对象空间中共享

5. 寄生式继承

💡 方式: 创建一个实现继承的函数,以某种方式增强对象,然后返回对象

function createAnother(original) {
    let clone = Object(original)  // 1.调用函数创建一个新的对象
    clone.sayHi = function(){  // 2. 增强对象
        console.log("Hi")
    }
    return clone
}
let person = {
    name: "Tom",
    hobby: ["sing", "dance"]
}
let person1 = createAnother(person)
person1.sayHi()  // Hi

注意: 这里调用函数创建一个新的对象不一定要通过 Object(original),其他返回新对象的函数都可以

使用场景: 适合主要关注对象,不在乎类型、构造函数的场景

存在问题:

  1. 引用共享

    let person2 = createAnother(person)
    person1.hobby.push("swim")
    console.log(person1.hobby)// [ 'sing', 'dance', 'swim' ]
    console.log(person2.hobby)// [ 'sing', 'dance', 'swim']
    
  2. 复用问题

6. 寄生式组合继承

💡 方式:

  1. 盗用构造函数继承属性
  2. 寄生式继承父类原型
  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);  //1. 盗用构造函数继承属性
    this.age = age;
}
function inheritPrototype(subType, superType){
    // 2. 寄生式继承父类原型
    var prototype = Object(superType.prototype); // 创建父类原型的副本
    prototype.constructor = subType; // 让副本的 constructor 属性指向子类
    subType.prototype = prototype; // 让子类原型指向副本
}
inheritPrototype(SubType, SuperType);//实现继承
SubType.prototype.sayAge = function(){
    console.log(this.age);
};
let test = new SubType("Tom", 23)
test.sayName()  // Tom 

优点:

  1. 解决了组合式继承两次调用构造函数的问题
  2. 保留了 instanceof 操作符和 isPrototypeOf() 方法判断继承关系的能力

使用场景: 引用类型继承的最佳模式