原型
每个函数在 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.prototype是Person的原型,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,形成了原型链。
原型链的查找过程
- 首先查找对象本身是否有该属性。
- 如果对象本身没有该属性,它会查看该对象的
__proto__,即该对象的原型对象。 - 如果原型对象没有该属性,它会继续查找原型对象的原型,一直到达
Object.prototype。 - 如果还是没有找到,最终会返回
undefined或null。
原型图示例:
练习题:
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();
问题:
- 在上面的代码中,
myDog.speak()会输出什么?为什么?
Animal sound dog
2. 综合
问题要求:
创建一个动物类 Animal,它具有 name 和 age 属性,并在原型上定义一个 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();
问题要求:
-
原型链:
- 分析
dog1和poodle1对象的原型链。 dog1和poodle1在调用describe方法时,方法是如何从原型链上找到的?
- 分析
-
方法调用:
- 调用
animal1.describe()、dog1.describe()和poodle1.describe()时,输出会是什么?
- 调用
-
继承的影响:
- 为什么
Poodle类需要先继承Dog,再继承Animal?这个顺序对调用方法有何影响?
- 为什么