简述ES6 之前使用 prototype 实现继承 ?

参考回答

在 ES6 之前,JavaScript 使用 prototype 来实现继承。每个 JavaScript 函数都有一个 prototype 属性,所有实例对象都会继承这个 prototype 上的方法和属性。

步骤:
1. 定义一个构造函数。
2. 在构造函数的 prototype 上添加方法和属性。
3. 通过改变子类构造函数的 prototype 属性,使其指向父类实例,来实现继承。

例如,下面是一个通过 prototype 实现继承的例子:

// 父类构造函数
function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log(`Hello, I am {this.name}`);
};

// 子类构造函数
function Dog(name, breed) {
  Animal.call(this, name); // 调用父类构造函数
  this.breed = breed;
}

// 继承父类的原型方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 子类添加特有的方法
Dog.prototype.bark = function() {
  console.log(`{this.name} is barking!`);
};

const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayHello(); // 输出:Hello, I am Buddy
dog.bark(); // 输出:Buddy is barking!

详细讲解与拓展

1. 父类构造函数

首先,我们定义了一个父类 Animal,它有一个构造函数,用于初始化 name 属性,并在其原型上定义了一个方法 sayHello。这样,所有通过 Animal 构造函数创建的实例都能共享 sayHello 方法。

2. 子类构造函数

然后,我们定义了一个子类 Dog。子类构造函数调用了父类构造函数 Animal.call(this, name),目的是确保父类的构造函数在子类实例化时也能执行,从而初始化父类的属性(如 name)。这一步是模拟传统面向对象语言中子类继承父类属性的一部分。

3. 原型链继承

为了让子类能够继承父类的方法,我们将子类的 prototype 设置为一个新的对象,且这个对象的原型是父类的 prototype。这样,子类的实例就能够访问到父类的方法。通过 Object.create() 方法,我们创建了一个新的对象,它的原型是 Animal.prototype,然后将其赋值给 Dog.prototype。这实现了原型链的继承。

需要注意的是,我们还需要将 Dog.prototype.constructor 设置回 Dog,因为在将 Dog.prototype 指向父类原型时,constructor 会丢失,它会指向 Animal

4. 子类特有的方法

最后,我们为 Dog 类添加了一个特有的方法 bark。这个方法将只在 Dog 的实例上有效,而不会影响 Animal 类的实例。

继承的优缺点

  • 优点
    • 可以通过修改父类的 prototype 来共享方法,节省内存。
    • 模拟了类继承的机制,允许子类继承父类的属性和方法。
  • 缺点
    • 语法较为冗长和复杂,尤其是 prototype 和构造函数的管理需要小心。
    • prototype 继承并没有完全解决构造函数的继承,必须手动设置 constructor
    • 这种方式可能会导致原型链的复杂性,在调试时不容易理解和维护。

通过 prototype 实现继承的替代方案:组合继承

由于 prototype 继承存在一些问题,如多次调用父类构造函数和原型链复杂度高等,通常在 ES6 之前,开发者还使用了“组合继承”来弥补 prototype 继承的不足。

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

Animal.prototype.sayHello = function() {
  console.log(`Hello, I am {this.name}`);
};

function Dog(name, breed) {
  Animal.call(this, name); // 继承父类属性
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype); // 继承父类方法
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
  console.log(`{this.name} is barking!`);
};

const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayHello(); // Hello, I am Buddy
dog.bark(); // Buddy is barking!

组合继承结合了构造函数继承和原型继承的优点,能够避免 prototype 继承中出现的问题,但它会有多次调用父类构造函数的缺点,导致父类属性被初始化多次。

总结

在 ES6 之前,使用 prototype 实现继承是 JavaScript 中最常见的方式。通过修改构造函数的 prototype 属性,可以使得子类继承父类的方法。虽然 prototype 继承方式有效,但它的语法较为复杂,可能会导致一些维护上的困难。

发表评论

后才能评论