Skip to content
本页目录

JavaScript 原型

原型

原型是 JavaScript 中每个对象的基础。每个 JavaScript 对象都有一个原型对象,它是一个包含属性和方法的对象。这个原型对象可以被其他对象继承,从而实现属性和方法的共享。

在 JavaScript 中,原型是一个关键的概念,它使得对象可以通过原型链(prototype chain)访问其他对象的属性和方法。原型链是一个对象关联到其他对象的链条,使得属性和方法可以被共享和继承。

原型链

原型链是一个对象和它的原型对象之间的连接。当你访问一个对象的属性或方法时,JavaScript 会首先查找该对象本身是否有这个属性或方法,如果没有,它会沿着原型链向上查找,直到找到为止。

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

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

const person = new Person('John')
person.sayHello() // 输出: "Hello, my name is John"

在上面的例子中,定义了一个构造函数 Person,它有一个属性 name 和一个方法 sayHello。然后,我们通过 Person.prototype 来定义 sayHello 方法。当我们创建一个 Person 对象 person 并调用 sayHello 方法时,JavaScript 会首先查找 person 对象本身是否有 sayHello 方法,如果没有,它会沿着原型链找到 Person.prototype 上的 sayHello 方法。

原型对象

每个 JavaScript 对象都有一个关联的原型对象。你可以通过 __proto__ 属性来访问对象的原型对象。例如:

javascript
const person = { name: 'John' }
console.log(person.__proto__) // 输出: Object {}

在这个例子中,person 对象的原型对象是 Object {},它是所有 JavaScript 对象的基本原型。

原型继承

原型继承是 JavaScript 中对象之间共享属性和方法的一种方式。当一个对象继承了另一个对象的原型时,它可以访问该原型对象上的属性和方法。

让我们再次看一个例子:

javascript
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)

const dog = new Dog('Buddy', 'Golden Retriever')
dog.eat() // 输出: "Buddy is eating"

在这个例子中,我们定义了两个构造函数 AnimalDogAnimal 构造函数有一个 eat 方法,而 Dog 构造函数继承了 Animal 构造函数,并在其原型上创建了一个新对象,该对象的原型是 Animal.prototype。这样,dog 对象就可以访问 Animaleat 方法。

JavaScript 中的原型继承方式

JavaScript 中有多种方式来实现原型继承,让我们一一了解这些方式。

1. 构造函数和原型链

这是最常见的原型继承方式。它使用构造函数来创建对象属性,同时使用原型链来共享方法。在前面的例子中,我们已经看到了这种方式的示例。

javascript
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)

const dog = new Dog('Buddy', 'Golden Retriever')
dog.eat() // 输出: "Buddy is eating"

2. Object.create()

Object.create() 方法是一种更简单的方式来创建一个对象并指定其原型。它接受一个原型对象作为参数,然后返回一个新对象,该对象的原型就是传入的原型对象。

javascript
const animal = {
  eat: function () {
    console.log(`${this.name} is eating`)
  },
}

const dog = Object.create(animal)
dog.name = 'Buddy'
dog.eat() // 输出: "Buddy is eating"

3. 类继承

ES6 引入了类(class)的概念,它提供了一种更简单的方式来实现原型继承。类可以使用 extends 关键字继承另一个类的属性和方法。

javascript
class Animal {
  constructor(name) {
    this.name = name
  }

  eat() {
    console.log(`${this.name} is eating`)
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name)
    this.breed = breed
  }
}

const dog = new Dog('Buddy', 'Golden Retriever')
dog.eat() // 输出: "Buddy is eating"

4. Object.setPrototypeOf()

Object.setPrototypeOf() 方法允许你在运行时更改对象的原型。这个方法接受两个参数:要更改原型的对象和新的原型对象。

javascript
const animal = {
  eat: function () {
    console.log(`${this.name} is eating`)
  },
}

const dog = { name: 'Buddy' }

Object.setPrototypeOf(dog, animal)

dog.eat() // 输出: "Buddy is eating"

原型继承与传统继承的区别

JavaScript 中的原型继承与传统面向对象编程语言中的继承有一些重要区别。以下是一些主要区别:

1. 动态性

JavaScript 的原型继承是动态的,这意味着你可以在运行时更改对象的原型。这种灵活性可以带来很多优势,但也需要小心使用,以避免不必要的复杂性。

2. 原型链

JavaScript 中的继承是通过原型链来实现的,这意味着一个对象可以继承多个原型对象的属性和方法。这种多重继承的能力是传统继承中不常见的。

3. 对象与类

传统面向对象编程语言通常使用类来定义对象的结构和行为,而 JavaScript 使用构造函数和原型对象。这种不同的方式可以导致不同的编程风格和理念。

使用原型的最佳实践

在使用原型时,有一些最佳实践可以帮助你编写更清晰、更可维护的代码:

1. 避免直接修改内建对象的原型

虽然你可以修改内建对象的原型,但最好不要这样做,因为它可能会导致意外的行为和不稳定性。如果需要扩展内建对象的功能,可以创建一个自定义的工具函数或对象,而不是修改原型。

2. 将方法添加到原型上

将方法添加到原型上而不是在构造函数内部定义方法,可以节省内存,因为所有对象实例共享同一个方法。这样可以降低内存使用,特别是当有大量对象实例时。

3. 注意原型链中的属性和方法覆盖

当在子对象上定义与父对象相同名称的属性或方法时,它会覆盖父对象上的属性或方法。因此,在命名属性和方法时要小心,以避免不必要的覆盖。

4. 使用 Object.create()来创建对象

Object.create() 方法提供了一种更灵活的方式来创建对象,并指定其原型。它可以帮助你更清晰地定义对象之间的继承关系。

lemon's personal blog.