一文搞懂JavaScript中原型与原型链

作者:闭包旅人

时间:2026-10-26

浏览:258

本文深入解析JavaScript中的原型与原型链机制,详细阐述构造函数、实例对象与原型之间的关系。通过解析prototype、__proto__及constructor属性,结合代码示例与关系图,清晰展示属性查找路径及原型链的终止条件,帮助开发者彻底理解JS对象继承的核心原理。

文章目录

前言

JavaScript中的原型与原型链是前端开发中常被提及的基础概念,也是面试中的高频考点。许多开发者仅知其表面含义,一旦深入追问便难以应对。为了在面试中更从容地展示理解,并加深自身记忆,特此整理此内容,希望能协助同样有此困惑的开发者。

首先展示一张关系图。

前言

初次接触这张图时,读者可能会感到复杂而望而却步。然而,只要耐心读完本文,便会发现其逻辑清晰且易于掌握。这类图示在理解原型机制时极具价值,重要性不言而喻。接下来我们将逐步展开分析。

构造函数创建对象

我们首先通过一个构造函数来创建一个对象实例。

function Person() {

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

上述示例展示了使用Person构造函数通过new关键字生成实例对象person的过程,这是JavaScript中创建对象的基本方式之一。

prototype

每个函数都有一个prototype属性,不信的话,你可以随便找个函数,打开F12都能看到。(注意: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对象在创建时都会自动关联另一个对象,即原型。所有对象都会从原型中“继承”属性。我们可以通过下图来展示构造函数与实例原型之间的关系:

前言

图中使用Person.prototype来表示实例原型。

那么,如何表示实例对象与实例原型(即person与Person.prototype)之间的关系呢?这就需要引入另一个关键属性:

__proto__

为了验证“每个JavaScript对象(null除外)在创建时都会关联另一个对象(即原型)”这一论断,我们可以通过具体示例进行证明。

function Person() {

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

基于上述验证,我们更新关系图如下:

前言

既然实例对象和构造函数都可以指向原型,那么原型中是否存在属性可以指向实例或构造函数呢?

首先可以排除原型指向实例的可能性,因为一个构造函数可以创建多个实例对象。

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

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

实例与原型

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

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。

如果仍未找到呢?原型的原型又是什么?

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

原型链

那么,Object.prototype的原型是什么呢?答案是null。我们可以通过代码验证:

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

因此,Object.prototype.__proto__的值为null,这与“Object.prototype没有原型”表达的是同一个意思。

这意味着,在属性查找过程中,当查找到Object.prototype时即可停止查找。

至此分析完毕,图中蓝色的连线即为原型链。

前言

总结

这里有几个关键点需要注意:

constructor

首先看constructor属性,参考以下示例:

function Person() {

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

当获取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中原型与原型链的解析告一段落。如需了解更多相关内容,可搜索本站过往文章或继续浏览后续文章,感谢大家的支持!

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章