JavaScript 原型与原型链详解
JavaScript 是一门基于原型的语言,理解原型(Prototype)和原型链(Prototype Chain)对于深入掌握 JavaScript 至关重要。本文将带你深入了解这一核心机制。
核心概念
在 JavaScript 中,几乎所有的对象都是通过函数创建的。我们需要区分两个重要的属性:
prototype: 这是函数特有的属性。它指向一个对象,这个对象包含了由该构造函数创建的实例共享的属性和方法。__proto__(或[[Prototype]]): 这是对象(实例)特有的属性。它指向该对象的构造函数的原型对象。
简而言之:实例.__proto__ === 构造函数.prototype
原型链的工作原理
当你访问一个对象的属性时,JavaScript 引擎会按照以下步骤查找:
- 首先查找对象本身是否有该属性。
- 如果没有,则去对象的
__proto__(即构造函数的prototype)中查找。 - 如果还没有,则去
__proto__的__proto__中查找。 - 这个过程一直持续,直到找到
null(原型链的顶端)。
如果最终还是没找到,则返回 undefined。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`你好,我是 ${this.name}`);
};
const p1 = new Person('张三');
// 访问自身属性
console.log(p1.name); // "张三"
// 访问原型上的方法
p1.sayHello(); // "你好,我是 张三"
// 验证原型关系
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
构造函数、原型与实例的关系
- 构造函数:
function Foo() {} - 原型对象:
Foo.prototype - 实例:
var f = new Foo()
它们之间的关系如下:
f.__proto__ === Foo.prototypeFoo.prototype.constructor === Foo
继承的实现
ES5 原型继承
在 ES6 之前,我们通常使用原型链来实现继承:
function Animal(type) {
this.type = type;
}
Animal.prototype.eat = function() {
console.log('正在吃东西...');
};
function Dog(name) {
Animal.call(this, '犬科'); // 借用构造函数继承实例属性
this.name = name;
}
// 建立原型链继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复 constructor 指向
Dog.prototype.bark = function() {
console.log('汪汪!');
};
const myDog = new Dog('旺财');
myDog.eat(); // 继承自 Animal
myDog.bark(); // Dog 自己的方法
ES6 Class 语法糖
ES6 引入了 class 关键字,但这只是语法糖,底层的机制依然是原型链。
class Animal {
constructor(type) {
this.type = type;
}
eat() {
console.log('正在吃东西...');
}
}
class Dog extends Animal {
constructor(name) {
super('犬科');
this.name = name;
}
bark() {
console.log('汪汪!');
}
}
const myDog = new Dog('来福');
myDog.eat();
总结
- JavaScript 对象通过原型链继承属性和方法。
prototype是构造函数的属性,用于定义共享的方法。__proto__是实例的属性,指向构造函数的原型。Object.prototype是原型链的尽头,其__proto__为null。- 理解原型链有助于编写更高效、复用性更强的代码。