原型与原型链
原型(Prototype)是 JavaScript 中实现继承的核心机制。每个 JavaScript 对象都有一个内部属性 [[Prototype]]
,它指向另一个对象。这个“另一个对象”就是该对象的原型。当试图访问一个对象的属性时,如果在该对象上找不到,JavaScript 引擎会自动沿着原型链向上查找。
__proto__
和 Object.getPrototypeOf()
虽然 [[Prototype]]
是一个内部属性,但我们可以通过 __proto__
(不推荐在生产代码中使用)或 Object.getPrototypeOf()
来访问它。
javascript
const warrior = {
attack() {
return 'Attack!';
}
};
const elf = {
name: 'Legolas'
};
// 将 warrior 设置为 elf 的原型
Object.setPrototypeOf(elf, warrior);
console.log(elf.attack()); // 输出: Attack!
console.log(Object.getPrototypeOf(elf) === warrior); // 输出: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在上面的例子中,elf
对象本身没有 attack
方法。当 elf.attack()
被调用时,引擎在 elf
上找不到 attack
,于是就去它的原型 warrior
上查找,并成功找到了该方法。
原型链
对象的原型也可以有自己的原型,这样就形成了一个原型链。查找属性的旅程会沿着这条链一直持续到链的末端,即一个其原型为 null
的对象。
javascript
const character = {
health: 100
};
const mage = Object.create(character);
mage.mana = 150;
const fireMage = Object.create(mage);
fireMage.spell = 'Fireball';
console.log(fireMage.spell); // 'Fireball' (自有属性)
console.log(fireMage.mana); // 150 (继承自 mage)
console.log(fireMage.health); // 100 (继承自 character)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这条原型链是:fireMage
-> mage
-> character
-> Object.prototype
-> null
。
构造函数与 prototype
属性
当使用构造函数创建一个新对象时,新对象的 [[Prototype]]
会被设置为构造函数的 prototype
属性。
javascript
function Player(name) {
this.name = name;
}
// 在 Player 的原型上添加一个方法
Player.prototype.sayHello = function() {
return `Hello, I am ${this.name}`;
};
const player1 = new Player('Bilbo');
const player2 = new Player('Frodo');
console.log(player1.sayHello()); // 输出: Hello, I am Bilbo
console.log(player2.sayHello()); // 输出: Hello, I am Frodo
// 两个实例共享同一个原型方法
console.log(player1.sayHello === player2.sayHello); // 输出: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这种方式非常高效,因为所有 Player
的实例都共享 sayHello
方法,而不是每个实例都创建一个新的函数。
虽然 ES6 的 class
语法在很大程度上简化了继承的写法,但其底层原理仍然是基于原型的。理解原型是深入掌握 JavaScript 的关键。