JS原型

41 阅读1分钟

原型

每个函数在 JavaScript 中都有一个 prototype 属性,该属性指向一个对象,通常用于存放该函数构造的实例共享的方法或属性,一般称为显示原型。 每个对象都有一个隐式的 __proto__ 属性,指向它的原型(即通过构造函数创建实例时,原型对象会被赋值给 __proto__),一般称为隐式原型。 例如:

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

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

const person1 = new Person('Alice');
person1.sayHello();  // "Hello, my name is Alice"

在这个例子中:

  • Person 是构造函数。
  • Person.prototypePerson 的原型,sayHello 方法定义在 Person.prototype 上。
  • person1.__proto__ 指向 Person.prototype

原型链

原型链是 JavaScript 中继承的机制。每个对象都有一个 __proto__ 属性指向其构造函数的 prototype。当你访问对象的属性或方法时,JavaScript 会沿着原型链查找,直到找到该属性或方法,或者找到 null 为止。 假设我们有以下对象:

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

Animal.prototype.speak = function() {
  console.log(this.name + ' makes a sound');
};

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

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

Dog.prototype.bark = function() {
  console.log(this.name + ' barks');
};

const dog = new Dog('Buddy');
dog.speak();  // Buddy makes a sound
dog.bark();   // Buddy barks

在这个例子中:

  • Dog 继承了 Animal 的属性和方法。
  • dog.__proto__ 指向 Dog.prototype,而 Dog.prototype.__proto__ 指向 Animal.prototype,形成了原型链。

原型链的查找过程

  1. 首先查找对象本身是否有该属性。
  2. 如果对象本身没有该属性,它会查看该对象的 __proto__,即该对象的原型对象。
  3. 如果原型对象没有该属性,它会继续查找原型对象的原型,一直到达 Object.prototype
  4. 如果还是没有找到,最终会返回 undefinednull

原型图示例:

image.png

练习题:

1. 原型链查找

问题要求:

创建一个 Animal 构造函数,添加 speak 方法,然后创建一个 Dog 构造函数,继承 Animal。使用原型链调用 speak 方法,输出 Dog 实例的声音。

function Animal() {
  this.type = 'animal';
}

Animal.prototype.speak = function() {
  console.log('Animal sound', this.type);
};

function Dog() {
  this.type = 'dog';
}

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

// 创建 Dog 实例并调用 speak 方法
const myDog = new Dog();

问题:

  1. 在上面的代码中,myDog.speak() 会输出什么?为什么?

Animal sound dog

2. 综合

问题要求:

创建一个动物类 Animal,它具有 nameage 属性,并在原型上定义一个 describe 方法,输出动物的基本信息。然后创建一个 Dog 类,继承 Animal 类,并重写 describe 方法,使其能输出狗的特定信息。接下来,创建一个 Poodle 类,继承 Dog 类,并重写 describe 方法,输出贵宾犬的特定信息。

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

Animal.prototype.describe = function() {
  console.log(`I am a ${this.name}, I am ${this.age} years old.`);
};

function Dog(name, age, breed) {
  Animal.call(this, name, age); // 继承 Animal 构造函数
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype); // 继承 Animal 的原型
Dog.prototype.constructor = Dog; // 恢复 constructor

Dog.prototype.describe = function() {
  console.log(`I am a ${this.breed}, my name is ${this.name}, and I am ${this.age} years old.`);
};

function Poodle(name, age) {
  Dog.call(this, name, age, 'Poodle'); // 继承 Dog 构造函数
}

Poodle.prototype = Object.create(Dog.prototype); // 继承 Dog 的原型
Poodle.prototype.constructor = Poodle; // 恢复 constructor

Poodle.prototype.describe = function() {
  console.log(`I am a Poodle named ${this.name}, I am ${this.age} years old.`);
};

// 创建实例
const animal1 = new Animal('Lion', 5);
const dog1 = new Dog('Max', 3, 'Golden Retriever');
const poodle1 = new Poodle('Bella', 2);

// 调用 describe 方法
animal1.describe();
dog1.describe();
poodle1.describe();

问题要求:

  1. 原型链

    • 分析 dog1poodle1 对象的原型链。
    • dog1poodle1 在调用 describe 方法时,方法是如何从原型链上找到的?
  2. 方法调用

    • 调用 animal1.describe()dog1.describe()poodle1.describe() 时,输出会是什么?
  3. 继承的影响

    • 为什么 Poodle 类需要先继承 Dog,再继承 Animal?这个顺序对调用方法有何影响?