返回

揭秘原型和原型链的神秘面纱——手把手教你成为JavaScript大师

前端

原型、原型链与继承:深入浅出

在 JavaScript 的浩瀚世界中,原型、原型链和继承机制扮演着至关重要的角色。它们携手合作,赋予了 JavaScript 强大的灵活性,让我们能够创建可扩展、可重用的代码。

原型:共享属性和方法的蓝图

想象一下,你正在建造一个名为“Person”的房子。为了简化工作流程,你创建了一个蓝图,它包含了房子的所有必要信息:墙壁、门窗、屋顶等。这个蓝图就相当于 JavaScript 中的原型。

原型是一个特殊对象,它充当着其他对象的蓝图。每个函数都有一个原型对象,该对象存储着所有由该函数创建的对象所共享的属性和方法。

例如,我们有一个名为 Person 的函数,用于创建 Person 对象:

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

Person 函数的原型对象包含所有 Person 对象共享的属性和方法,例如 name 属性和 sayHello() 方法:

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

现在,我们可以使用 new 运算符来创建 Person 对象:

const person1 = new Person('Alice');
const person2 = new Person('Bob');

person1person2 都继承了 Person.prototype 中的 name 属性和 sayHello() 方法,就如同它们是从同一个蓝图建造出来的房子一样:

person1.sayHello(); // Hello, my name is Alice.
person2.sayHello(); // Hello, my name is Bob.

原型链:向上追溯的属性查找

原型链是连接一个对象与其原型对象的链条。每个对象都有一个原型对象,这个原型对象可能又拥有自己的原型对象,依此类推,直到遇到 null 为止。

当我们访问一个对象的属性或方法时,JavaScript 会沿着原型链逐级向上查找,直到找到该属性或方法。如果在当前对象中找不到,它就会在该对象的原型对象中查找,依此类推,直到找到该属性或方法,或到达原型链的末端。

举个例子,当我们访问 person1.name 时,JavaScript 会首先在 person1 对象中查找 name 属性。如果找不到,它就会在 Person.prototype 中查找,直到找到该属性:

person1.name; // "Alice"

继承:子类与父类的血缘关系

继承是 JavaScript 中一项强大的特性,它允许子类继承父类的属性和方法。就好像子类是从父类那里继承了一笔财富,获得了父类拥有的所有知识和技能。

通过使用 extends ,我们可以创建子类:

class Employee extends Person {
  constructor(name, jobTitle) {
    super(name);
    this.jobTitle = jobTitle;
  }
}

Employee 类继承了 Person 类的所有属性和方法,并且还添加了自己的 jobTitle 属性。这意味着 Employee 对象既具有 name 属性,也具有 jobTitle 属性:

const employee1 = new Employee('John', 'Software Engineer');
employee1.sayHello(); // Hello, my name is John.
console.log(employee1.jobTitle); // Software Engineer

重写:子类对父类属性的改造

虽然子类继承了父类的属性和方法,但它也可以重写这些属性和方法,以实现自己的特定行为。就好像子类可以对父类的遗产进行一些微调,以满足自己的需求。

要重写父类的方法,我们只需在子类中定义一个同名的方法即可:

class Manager extends Employee {
  constructor(name, jobTitle) {
    super(name, jobTitle);
  }

  sayHello() {
    super.sayHello();
    console.log('As your manager, I\'m here to help you succeed.');
  }
}

Manager 类重写了 sayHello() 方法,添加了一个额外的消息,体现了经理的职责:

const manager1 = new Manager('Mary', 'Manager');
manager1.sayHello(); // Hello, my name is Mary. As your manager, I'm here to help you succeed.

多态:子类的千变万化

多态是指子类可以根据自己的需求重写父类的方法,从而实现不同的行为。就像同一首歌可以由不同的歌手唱出不同的味道,子类也可以用自己的方式演绎父类的行为。

例如,我们有一个 Animal 类,它定义了 makeSound() 方法:

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

  makeSound() {
    console.log('Generic animal sound.');
  }
}

我们可以创建子类 CatDog,它们重写 makeSound() 方法以产生特定动物的声音:

class Cat extends Animal {
  constructor(name) {
    super(name);
  }

  makeSound() {
    console.log('Meow!');
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  makeSound() {
    console.log('Woof!');
  }
}

现在,我们可以在同一个程序中使用这些子类,而无需关心它们的具体类型:

const animal1 = new Animal('Generic');
const cat1 = new Cat('Kitty');
const dog1 = new Dog('Buddy');

animal1.makeSound(); // Generic animal sound.
cat1.makeSound(); // Meow!
dog1.makeSound(); // Woof!

Mixin:共享属性和方法的秘诀

Mixin 是一种特殊类,它可以被多个类继承,从而为这些类添加额外的属性和方法。就好比一个共享厨房,不同的房间可以根据需要使用它。

我们可以创建一个 Logger mixin,为类添加日志记录功能:

const Logger = {
  log(message) {
    console.log(`[${this.name}] ${message}`);
  }
};

然后,我们可以在任何类中使用 Logger mixin:

class MyClass extends SuperClass {
  constructor(...args) {
    super(...args);
    Object.assign(this, Logger);
  }
}

现在,MyClass 的实例可以调用 log() 方法:

const myClass1 = new MyClass();
myClass1.log('Hello world!'); // [MyClass1] Hello world!

总结

原型、原型链和继承是 JavaScript 中的关键概念,它们共同为我们提供了构建可扩展、可重用代码的强大工具。通过理解这些概念,我们能够编写出更优雅、更强大的程序。

常见问题解答

1. 原型与原型链有什么区别?

原型是一个特殊的对象,它包含了其他对象共享的属性和方法。原型链是从一个对象到其原型对象的链条。

2. 继承如何应用于 JavaScript?

子类可以通过 extends 关键字继承父类的属性和方法。子类也可以重写父类的方法以实现自己的特定行为。

3. 多态在 JavaScript 中如何实现?

子类可以根据自己的需求重写父类的方法,从而实现不同的行为。

4. Mixin 在 JavaScript 中的作用是什么?

Mixin 是可以被多个类继承的特殊类,它为这些类添加额外的属性和方法。

5. 为什么理解原型、原型链和继承对于 JavaScript 程序员来说很重要?

理解这些概念对于编写出可扩展、可重用代码至关重要。它们有助于我们创建更灵活、更优雅的程序。