Skip to content

原型链和继承深入学习

原型(Prototype)

在 JavaScript 中,每个对象都有一个指向其原型对象的内部链接,这个链接就是原型链的基础。

原型对象

javascript
function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  return `Hello, I'm ${this.name}`;
};

const person = new Person("JavaScript");
console.log(person.greet()); // "Hello, I'm JavaScript"

原型链查找机制

javascript
const obj = {};

// obj 的原型是 Object.prototype
console.log(obj.toString); // [Function: toString]

// 查找顺序:
// 1. obj 自身属性
// 2. Object.prototype
// 3. null

proto vs prototype

javascript
function Person() {}

const person = new Person();

// __proto__ 是实例对象的属性,指向构造函数的 prototype
console.log(person.__proto__ === Person.prototype); // true

// prototype 是构造函数的属性
console.log(Person.prototype); // Person {}

// 原型链的终点
console.log(Object.prototype.__proto__); // null

WARNING

__proto__ 是已废弃的属性,建议使用 Object.getPrototypeOf()Object.setPrototypeOf()

构造函数

构造函数模式

javascript
function Car(brand, model) {
  this.brand = brand;
  this.model = model;
}

Car.prototype.start = function() {
  return `${this.brand} ${this.model} started`;
};

const car = new Car("Tesla", "Model 3");
console.log(car.start()); // "Tesla Model 3 started"

new 操作符的执行过程

javascript
// new 操作符做了以下事情:
// 1. 创建一个新对象
// 2. 将构造函数的 prototype 赋值给新对象的 __proto__
// 3. 执行构造函数,this 指向新对象
// 4. 返回新对象(如果构造函数没有返回对象)

function MyNew(constructor, ...args) {
  const obj = {};
  obj.__proto__ = constructor.prototype;
  const result = constructor.apply(obj, args);
  return result instanceof Object ? result : obj;
}

继承的实现方式

1. 原型链继承

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

Animal.prototype.eat = function() {
  return `${this.name} is eating`;
};

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

// 原型链继承
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
  return `${this.name} is barking`;
};

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"

DANGER

原型链继承的问题:

  • 子类实例共享父类引用类型属性
  • 无法向父类构造函数传参

2. 构造函数继承

javascript
function Animal(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

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

const dog1 = new Dog("Buddy", "Golden");
const dog2 = new Dog("Max", "Labrador");

dog1.colors.push("green");
console.log(dog1.colors); // ["red", "blue", "green"]
console.log(dog2.colors); // ["red", "blue"] (独立)

TIP

构造函数继承解决了引用类型共享的问题,但无法继承父类原型上的方法。

3. 组合继承(最常用)

javascript
function Animal(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

Animal.prototype.eat = function() {
  return `${this.name} is eating`;
};

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

// 继承方法
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
  return `${this.name} is barking`;
};

const dog = new Dog("Buddy", "Golden");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"

4. 原型式继承

javascript
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

const animal = {
  name: "Animal",
  colors: ["red", "blue"]
};

const dog = object(animal);
dog.name = "Dog";
dog.colors.push("green");

console.log(dog.name); // "Dog"
console.log(animal.colors); // ["red", "blue", "green"] (共享)

5. 寄生式继承

javascript
function createDog(original) {
  const clone = object(original);
  clone.bark = function() {
    return "Woof!";
  };
  return clone;
}

6. 寄生组合式继承(最佳实践)

javascript
function inheritPrototype(subType, superType) {
  const prototype = Object.create(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}

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

Animal.prototype.eat = function() {
  return `${this.name} is eating`;
};

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

inheritPrototype(Dog, Animal);

Dog.prototype.bark = function() {
  return `${this.name} is barking`;
};

const dog = new Dog("Buddy", "Golden");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"

TIP

寄生组合式继承是最理想的继承方式,只调用一次父构造函数,避免了不必要的属性创建。

ES6 类继承

javascript
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  eat() {
    return `${this.name} is eating`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类构造函数
    this.breed = breed;
  }
  
  bark() {
    return `${this.name} is barking`;
  }
}

const dog = new Dog("Buddy", "Golden");
console.log(dog.eat()); // "Buddy is eating"
console.log(dog.bark()); // "Buddy is barking"

原型链检测

instanceof

javascript
function Animal() {}
function Dog() {}
Dog.prototype = new Animal();

const dog = new Dog();

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

isPrototypeOf

javascript
console.log(Dog.prototype.isPrototypeOf(dog)); // true
console.log(Animal.prototype.isPrototypeOf(dog)); // true

Object.getPrototypeOf

javascript
console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true

总结

  • 原型:每个对象都有原型,通过原型链查找属性和方法
  • 构造函数:用于创建对象的函数,通过 new 操作符调用
  • 继承:多种实现方式,寄生组合式继承是最佳实践
  • ES6 类:语法糖,底层仍基于原型链实现