JavaScript原型与原型链机制详解

作者:拾光

时间:2026-10-23

浏览:490

本文详细解析JavaScript中构造函数、实例与原型之间的关系。通过剖析prototype、__proto__和constructor三个关键属性,阐明实例如何沿原型链查找属性。文章还探讨了Object.prototype的原型为null的终点机制,以及委托式继承与属性复制的区别,帮助开发者深入理解JS对象模型。

文章目录

构造函数与对象创建

使用构造函数来创建对象是JavaScript中的常见方式。

首先,我们通过一个具体的例子来展示构造函数的使用过程:


function Person() {
}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin

在上述示例中,Person充当了构造函数的角色,并通过new关键字实例化生成了一个名为person的对象。

这一过程相对直观,下面我们将深入探讨其核心机制:

理解prototype属性

prototype属性

所有的函数都拥有一个名为prototype的属性,这在各类示例代码中十分常见,例如:


function Person() {
}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

那么,这个函数的prototype属性具体指向何处?它是否直接指向函数的原型?

实际上,函数的prototype属性指向一个特定的对象,该对象正是调用此构造函数所创建的实例的原型。换言之,它是示例中person1和person2这两个实例的共同原型。

究竟什么是原型?可以这样理解:除了null以外,每个JavaScript对象在创建时都会自动关联另一个对象,这个被关联的对象即为原型。每个对象都会从其所关联的原型中“继承”属性。

我们可以借助一张示意图来直观展示构造函数与实例原型之间的关联:

构造函数与对象创建

图中使用Object.prototype来表示实例原型的顶层。

那么,如何表示实例对象与构造函数原型(即person与Person.prototype)之间的关系呢?这涉及到第二个关键属性:

__proto__属性的作用

__proto__属性

这是除null以外所有JavaScript对象都具备的一个属性,称为__proto__。该属性的值指向该对象的原型。

为了验证这一结论,可以在Firefox或Chrome浏览器控制台中执行以下操作:


function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

基于此,我们更新之前的关系图示:

构造函数与对象创建

既然实例对象和构造函数都能指向原型,那么原型是否也拥有指向构造函数或实例的属性呢?

constructor属性的指向

constructor属性

原型并没有指向实例的属性,因为一个构造函数可以生成多个实例。然而,原型确实拥有指向构造函数的属性,这就是第三个关键属性:constructor。每个原型都包含一个constructor属性,该属性指向其关联的构造函数。

为了验证这一点,可以尝试执行以下代码:


function Person() {
}
console.log(Person === Person.prototype.constructor); // true

因此,我们需要再次更新关系图:

构造函数与对象创建

综上所述,我们已经明确了以下关系:


function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

在理解了构造函数、实例原型以及实例三者之间的关系后,接下来我们将探讨实例与原型之间的具体交互机制:

实例与原型的属性查找机制

实例与原型的属性查找

当尝试读取实例对象的属性时,如果该属性在实例本身中不存在,引擎会查找与该对象关联的原型中的属性。若仍未找到,则继续向上查找原型的原型,直至到达最顶层为止。

以下是一个具体的示例:


function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin

在此示例中,我们向实例对象person添加了name属性。当打印person.name时,结果自然为Daisy。

然而,当我们删除person的name属性后,再次读取person.name,由于在person对象中找不到该属性,引擎会从person的原型(即person.__proto__,也就是Person.prototype)中查找。幸运的是,我们在原型中找到了name属性,结果为Kevin。

如果依然没有找到呢?原型的原型又是什么呢?

原型的原型与原型链

原型的原型机制

前文已提及,原型本身也是一个对象。既然是对象,我们就可以使用最原始的方式创建它,代码如下:

var obj = new Object(); obj.name = 'Kevin' console.log(obj.name) // Kevin

由此可见,原型对象是通过Object构造函数生成的。结合之前的论述,实例的__proto__指向构造函数的prototype,因此我们再次更新关系图:

构造函数与对象创建

原型链机制

那么,Object.prototype的原型是什么呢?

答案是null。不信的话,我们可以打印验证:

console.log(Object.prototype.__proto__ === null) // true

因此,当属性查找进行到Object.prototype时,即可停止查找。

最后一张关系图如下所示:

构造函数与对象创建

顺便说明一下,图中由相互关联的原型所构成的链状结构即为原型链,也就是图中蓝色的连线部分。

关键细节补充与委托概念

补充说明

最后,补充三个可能容易被忽视的细节:

关于constructor属性

首先来看constructor属性,观察以下示例:


function Person() {
}
var person = new Person();
console.log(person.constructor === Person); // true

当获取person.constructor时,实际上person对象本身并不包含constructor属性。当无法直接从person中读取到constructor属性时,引擎会从person的原型(即Person.prototype)中进行查找。恰好原型中拥有该属性,因此:


person.constructor === Person.prototype.constructor

关于__proto__属性

其次关注__proto__属性。绝大多数浏览器都支持通过这一非标准方法访问原型,但它并不存在于Person.prototype中。实际上,它源自Object.prototype,与其说它是一个普通属性,不如说它是一个getter/setter。当使用obj.__proto__时,可以理解为返回了Object.getPrototypeOf(obj)的结果。

这真的是继承吗?

最后讨论关于继承的概念。前文提到“每个对象都会从原型‘继承'属性”,实际上,“继承”这一术语具有极大的迷惑性。引用《你不知道的JavaScript》中的观点:

继承意味着复制操作,然而JavaScript默认并不会复制对象的属性。相反,JavaScript只是在两个对象之间创建一个关联链接,使得一个对象可以通过委托访问另一个对象的属性和函数。因此,与其称之为继承,使用“委托”这一说法反而更为准确。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章