(JavaScript)对象继承的方式有哪些?

108 阅读6分钟

1. 原型链继承:

  • 特点: 使用原型链实现对象之间的继承关系。
  • 缺点: 引用类型的属性会被所有实例共享,容易造成修改的混乱,且在创建子类型时不能向超类型传递参数

类比: 想象成一个大家族,所有家庭成员共享同一份家谱,但无法为每个家庭成员定制个性化的信息。

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(this.name + ' is eating.');
};

function Dog(breed) {
    this.breed = breed;
}

Dog.prototype = new Animal('GenericAnimal');

var myDog = new Dog('Golden Retriever');
myDog.eat();  // 输出: GenericAnimal is eating.

向超类型传递参数

"向超类型传递参数" 意味着在使用构造函数模式实现继承时,能够将子类型的构造函数中接收的参数传递给超类型的构造函数,以便在创建子类型实例时能够对超类型进行初始化。

在JavaScript中,构造函数可以接收参数,并且通过使用 callapply 或者 bind 等方法,可以在一个构造函数中调用另一个构造函数,并传递参数。这样就实现了在子类型中调用超类型构造函数,并将参数传递给超类型。

以下是一个简单的示例,演示了如何向超类型传递参数:

// 超类型(父类)
function Animal(name) {
    this.name = name;
    this.energy = 100; // 默认能量值
}

// 超类型的方法
Animal.prototype.eat = function() {
    console.log(this.name + ' is eating.');
    this.energy += 10;
};

// 子类型(子类)
function Dog(name, breed) {
    // 在子类型构造函数中调用超类型构造函数,并传递参数
    Animal.call(this, name); // 在这里,this指向当前创建的Dog实例
    
    this.breed = breed;
}

// 创建子类型的实例
var myDog = new Dog('Buddy', 'Golden Retriever');

// myDog继承了父类的属性和方法
console.log(myDog.name);  // 输出: Buddy
console.log(myDog.energy); // 输出: 100

// 调用继承的方法
myDog.eat();  // 输出: Buddy is eating.
console.log(myDog.energy); // 输出: 110

在上面的例子中,Dog 构造函数调用了 Animal 构造函数,并通过 call(this, name)name 参数传递给了 Animal 构造函数。这样,myDog 实例既拥有了 Dog 构造函数中定义的属性 breed,又继承了 Animal 构造函数中的属性和方法。

在上面的代码中,Animal.call(this, name); 的目的是将 Animal 构造函数的上下文(this 的上下文)设置为当前正在创建的 Dog 实例。这样,当 Animal 构造函数内部使用 this 时,它指向的是新创建的 Dog 实例,而不是 Animal 构造函数内部的默认上下文。

2. 构造函数继承:

  • 特点: 通过在子类型构造函数中调用超类型构造函数实现继承,解决了无法向超类型传递参数的问题。
  • 缺点: 不能继承超类型原型上定义的方法,无法实现方法的复用。

类比: 子类型好像搬到了超类型的房子里,可以带些自己的家具,但不能使用超类型房子原本的设施。

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(this.name + ' is eating.');
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

var myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat();  // 输出: Buddy is eating.

3. 组合继承:

将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。

  • 特点: 结合了原型链继承和构造函数继承,解决了各自单独使用时的问题。
  • 缺点: 调用了两次超类的构造函数,导致子类型的原型中包含不必要的属性。

类比: 子类型既有自己的房子,也可以使用超类型的设施,但房子的结构可能有些多余。

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(this.name + ' is eating.');
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

Dog.prototype = new Animal();

var myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat();  // 输出: Buddy is eating.

4. 原型式继承:

原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象, 然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。

  • 特点: 基于已有对象创建新对象,通过Object.create()实现,适用于简单继承。
  • 缺点: 与原型链方式相同,引用类型属性会被所有实例共享。

类比: 像是通过复制一张已有图纸来创建新的图纸,但所有图纸共享相同的设计。

var animal = {
    name: 'GenericAnimal',
    eat: function() {
        console.log(this.name + ' is eating.');
    }
};

var dog = Object.create(animal, { breed: { value: 'Golden Retriever' } });

dog.eat();  // 输出: GenericAnimal is eating.

5. 寄生式继承:

寄生式继承的思路是创建一个用于 封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本, 然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解 是一种继承

  • 特点: 创建一个用于封装继承过程的函数,通过复制对象并进行扩展实现继承。
  • 缺点: 不能实现函数的复用。

类比: 好比购买了一个现成的家具,然后为了增加一些功能,加了一些自定义的部分。

function createDog(breed) {
    var dog = Object.create({
        eat: function() {
            console.log(this.name + ' is eating.');
        }
    });
    dog.breed = breed;
    return dog;
}

var myDog = createDog('Golden Retriever');
myDog.eat();  // 输出: undefined is eating.

在这个例子中,createDog 函数返回一个新的对象,但这个对象的 bark 方法是在函数内部定义的,而且它没有被抽象成一个可以在其他地方复用的函数。如果我们再次调用 createDog 创建另一个对象,每次都会创建一个新的 bark 方法,而无法实现真正的函数复用。

6. 寄生式组合继承:

  • 特点: 使用超类型原型的副本作为子类型的原型,解决了组合继承中不必要的属性问题。组合继承的缺点就是使用超类 型的实例做为子类型的原型,导致添加了不必要的原型属性。
  • 优点: 保留了原型链继承和构造函数继承的优点,避免了它们各自的缺点。

类比: 子类型既有自己的房子,也可以使用超类型的设施,但是通过复制超类型的设计来避免了多余的结构。

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(this.name + ' is eating.');
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

// 解决了组合继承中调用两次超类构造函数的问题
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

var myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat();  // 输出: Buddy is eating.