引言

嘿,你还记得小时候玩过的积木吗?你可以用同样的几块积木搭建出无数种造型。JavaScript 中的“原型”和“继承”有点类似,它们让你能够用少量的代码创建许多不同的对象。这篇文章就像是一场积木大赛,带你一起看看如何用原型和继承搭建出各种有趣的代码结构。


1. 原型是什么?

你可以把原型想象成一块模板积木。每个 JavaScript 对象都有一个“原型”,它是用来提供那些我们不想重复造的轮子。

1.1 给你打个比方

想象你是个厨师,每次做菜都要从头准备调料、火候、时间等基本步骤。累吧?这时候你有了一个菜谱,这个菜谱就是“原型”。你按照菜谱(原型)去做,省了不少事。

在 JavaScript 中,原型就是对象的菜谱,里面有对象的基本属性和方法。当你创建一个新的对象时,这个新对象会“继承”它的原型中的方法和属性,就像你按照菜谱做菜一样。

示例:

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

Person.prototype.greet = function () {
  console.log(`Hello, my name is ${this.name}.`);
};

const alice = new Person("Alice");
alice.greet(); // 输出:Hello, my name is Alice.

在这个例子里,Person 就是你的“菜谱”,而 greet 方法是“做菜的步骤”。每次你创建一个新的人物对象,它都会继承这个步骤。

1.2 那原型链是什么呢?

如果原型是菜谱,那原型链就是一系列菜谱的连接。假设你有一个总的菜谱,里面有各种基础菜肴的做法,然后你可以有其他更详细的菜谱,继承这个基础菜谱。原型链就是这么运作的。

比喻:

  • 基础菜谱:Object 原型
  • 详细菜谱:你定义的原型

每当你调用一个对象的方法时,JavaScript 会顺着原型链往上找,就像你找不到做某道菜的方法时,会去翻基础菜谱一样。


2. 继承是怎么回事?

继承让你可以用现有的积木块(原型)搭建出新玩意儿,而不必从头开始。

2.1 假设你是玩具设计师

你设计了一款基础玩具车,具有轮子和车身。接着,你想设计一款更酷的赛车,你当然不想重新造轮子吧?所以你决定“继承”基础玩具车的设计,加上一些新元素,比如涡轮增压。

示例:

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

Car.prototype.startEngine = function () {
  console.log(`${this.make} ${this.model} engine started.`);
};

function SportsCar(make, model, turbo) {
  Car.call(this, make, model); // 继承 Car 的属性
  this.turbo = turbo;
}

// 继承 Car 的方法
SportsCar.prototype = Object.create(Car.prototype);
SportsCar.prototype.constructor = SportsCar;

SportsCar.prototype.boost = function () {
  console.log(`${this.make} ${this.model} boosting with ${this.turbo}!`);
};

const myCar = new SportsCar("Porsche", "911", "Turbo S");
myCar.startEngine(); // 输出:Porsche 911 engine started.
myCar.boost(); // 输出:Porsche 911 boosting with Turbo S!

在这个例子里,SportsCar 就像是在基础玩具车的设计上加了涡轮增压,通过继承基础设计实现了一个更复杂的玩具。

2.2 原型继承的好处

继承不仅让你节省代码,还让你的代码更加模块化和易于维护。如果你需要改进玩具车的设计,只需修改基础设计,所有继承它的赛车也会自动获得这些改进。

比喻:
就像如果你改进了菜谱中的某个基础步骤,所有依赖这个步骤的菜都会变得更好吃。


3. 常见的继承模式

在 JavaScript 中,有几种常见的继承模式,接下来我们一起看看。

3.1 原型链继承

这是最简单的继承方式,你只需要让新对象的原型指向父对象的实例。

示例:

function Animal() {
  this.species = "animal";
}

Animal.prototype.speak = function () {
  console.log(`A ${this.species} makes a sound.`);
};

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

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

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

const rex = new Dog("Rex");
rex.speak(); // 输出:A animal makes a sound.
rex.bark(); // 输出:Rex barks!

注意: 原型链继承虽然简单,但它也有一些问题,比如当父对象的属性是引用类型时,子对象会共享这个属性,这可能导致一些意外的错误。

3.2 组合继承

组合继承结合了原型链继承和构造函数继承的优点,解决了原型链继承的共享属性问题。

示例:

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

Animal.prototype.speak = function () {
  console.log(`A ${this.species} makes a sound.`);
};

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

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

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

const rex = new Dog("Rex");
rex.speak(); // 输出:A dog makes a sound.
rex.bark(); // 输出:Rex barks!

这种继承方式确保了每个子对象有自己的属性,同时还能继承父对象的所有方法。

3.3 ES6 class 语法

如果你觉得上面的继承方式有点麻烦,别担心,ES6 提供了更简洁的 class 语法,让继承变得更加直观。

示例:

class Animal {
  constructor(species) {
    this.species = species;
  }

  speak() {
    console.log(`A ${this.species} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super("dog"); // 调用父类的构造函数
    this.name = name;
  }

  bark() {
    console.log(`${this.name} barks!`);
  }
}

const rex = new Dog("Rex");
rex.speak(); // 输出:A dog makes a sound.
rex.bark(); // 输出:Rex barks!

使用 class 语法,就像是在搭建乐高积木,结构清晰,操作简便。


结论

你已经学会了如何在 JavaScript 中用原型和继承来搭建你的代码结构。这就像是用积木搭建模型,通过基础的积木块(原型),你可以构建出丰富多样的复杂结构(对象)。

社区与资源

想进一步深入了解 JavaScript 的原型和继承吗?以下是一些推荐的资源:

最后修改:2024 年 08 月 16 日
如果觉得我的文章对你有用,请随意赞赏