实现ES6的extends继承

手写实现ES6 class继承机制,理解JavaScript原型链和构造函数的继承原理

问题

ES6引入了classextends关键字,使得JavaScript的面向对象编程更加直观。但在底层,它仍然是基于原型链实现的。这道题要求我们手动实现extends的继承机制,理解子类如何继承父类的属性和方法。

解答

/**
 * 实现ES6的extends继承
 * @param {Function} Child - 子类构造函数
 * @param {Function} Parent - 父类构造函数
 */
function myExtends(Child, Parent) {
  // 1. 创建一个空函数作为中介,避免直接实例化父类
  function F() {}
  
  // 2. 将空函数的原型指向父类原型
  F.prototype = Parent.prototype;
  
  // 3. 将子类的原型指向空函数的实例
  // 这样子类原型就能访问父类原型上的方法,同时不会影响父类
  Child.prototype = new F();
  
  // 4. 修正子类原型的constructor指向
  Child.prototype.constructor = Child;
  
  // 5. 保存父类引用,方便子类调用父类方法(类似super)
  Child.__proto__ = Parent;
  
  // 6. 为子类添加一个静态属性,指向父类原型
  Child.super = Parent.prototype;
}

// 更简洁的实现方式(使用Object.create)
function myExtends2(Child, Parent) {
  // 使用Object.create创建一个以Parent.prototype为原型的对象
  Child.prototype = Object.create(Parent.prototype);
  
  // 修正constructor指向
  Child.prototype.constructor = Child;
  
  // 继承父类的静态属性和方法
  Object.setPrototypeOf(Child, Parent);
}

使用示例

// 定义父类
function Animal(name) {
  this.name = name;
  this.energy = 100;
}

Animal.prototype.eat = function() {
  console.log(`${this.name} is eating`);
  this.energy += 10;
};

Animal.prototype.sleep = function() {
  console.log(`${this.name} is sleeping`);
};

// 父类静态方法
Animal.getType = function() {
  return 'Animal';
};

// 定义子类
function Dog(name, breed) {
  // 调用父类构造函数,继承实例属性
  Animal.call(this, name);
  this.breed = breed;
}

// 使用myExtends实现继承
myExtends2(Dog, Animal);

// 子类添加自己的方法
Dog.prototype.bark = function() {
  console.log(`${this.name} is barking: Woof!`);
};

// 子类重写父类方法
Dog.prototype.eat = function() {
  console.log(`${this.name} the dog is eating dog food`);
  this.energy += 15;
};

// 测试
const myDog = new Dog('Buddy', 'Golden Retriever');

console.log(myDog.name); // Buddy
console.log(myDog.breed); // Golden Retriever
console.log(myDog.energy); // 100

myDog.eat(); // Buddy the dog is eating dog food
console.log(myDog.energy); // 115

myDog.sleep(); // Buddy is sleeping
myDog.bark(); // Buddy is barking: Woof!

// 验证原型链
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
console.log(myDog.constructor === Dog); // true

// 验证静态方法继承
console.log(Dog.getType()); // Animal

关键点

  • 原型链继承:通过设置Child.prototype为一个继承自Parent.prototype的对象,实现方法的继承

  • 构造函数继承:在子类构造函数中使用Parent.call(this, ...args)调用父类构造函数,继承实例属性

  • 使用中间对象:通过空函数FObject.create()创建中间对象,避免直接实例化父类,防止父类构造函数的副作用

  • 修正constructor:继承后需要手动修正Child.prototype.constructor,使其指向子类本身

  • 静态属性继承:通过Object.setPrototypeOf(Child, Parent)Child.__proto__ = Parent实现静态方法的继承

  • 组合继承:结合原型链继承和构造函数继承,这是最常用的继承模式,也是ES6 extends的底层实现原理

  • instanceof验证:正确实现后,子类实例应该同时是子类和父类的实例