JavaScript 的原型和原型链

这是深入理解 JavaScript 语法合集的第四篇,其余文章见文末。

前言

前一篇文章 介绍 instanceof 检测变量类型时有提到,它的作用是用于检测构造函数的 prototype 是否在某个实例对象的原型链上。那么什么是原型?什么又是原型链呢?

一、JavaScript 中的原型

(1)构造函数与实例原型

相信大家肯定听过原型,还看过 prototype 这个属性。现在我们就来看看它们的关系。

你可能学过其它的语言,比如 Java。Java 可以通过new一个类来创建一个对象,像这样:

1
User user = new User();

这里的 User() 是已经定义好的类。在 JavaScript 也可以通过“类似”的方式来创建一个对象:

1
let person = new Person();

但是不同的是,这里的 Person() 不是类,而是构造函数。当我们使用这个构造函数来创建对象 person 时,这个对象就和 Person 这个构造函数产生了某种联系。

JavaScript 中的构造函数和其它语言中不同,它其实仅仅是一个普通的函数。

每个构造函数都有一个 prototype 属性,这个属性指向一个对象,这个对象就是调用这个构造函数所生成的实例的原型,也就是 person 的原型。那么什么是原型呢?《JavaScript 深入之从原型到原型链》中这样描述:

你可以这样理解:每个 JavaScript 对象(null 除外),在创建的时候会与之关联另一个对象,这个对象就是我们所说的原型。每个对象都会从这个原型中“继承”属性。

用一张图表示它们的关系:

prototype

图中的指向方向是从构造函数到实例原型,那实例原型有没有对象指向构造函数呢?我们通过打印可以得知,有的:

1
2
3
4
5
function Person() {}

let person = new Person();

console.log(Person.prototype); // {constructor: ƒ}

打印后得出,实例原型中有一个 constructor 对象,且查看内容可知,这个对象指向的正是构造函数:
constructor1

也可以通过打印来验证一下:

1
2
3
4
5
function Person() {}

let person = new Person();

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

其实每个原型对象都有一个 constructor 属性,它指向构造函数:

constructor2

(2)实例对象与实例原型

讲过了构造函数与实例原型之间的关联,现在来讲讲实例与实例原型之间的关联。

我们通过打印可以得到,每个实例都有一个 __proto__ 属性,且指向实例原型(观察到,__proto__中含有属性 constructor,而该属性是实例原型的):
__proto__1

于是,我们得出构造函数、实例和实例原型之间的联系:
__proto__2

当我们通过构造函数创建多个对象实例时,所有对象都有各自的 __proto__ 属性,且指向原型对象。

二、JavaScript 中的原型链

(1)从原型到原型链

其实我们前面在使用 toString 来检测变量类型时就已经提到过:由于大部分对象类型的 toString 方法被重写,所以我们追溯到原型链的顶端,使用 ObjecttoString 方法来检测。

对象类型的 toString 方法就是从 Object 中“继承”过来的。那是如何“继承”的呢?

我们刚刚讲过,每个对象都拥有一个 __proto__ 属性,且这个属性指向原型对象。而我们知道,原型对象也是一个对象,所以,原型对象也是有一个 __proto__ 属性的,它也指向它的原型对象。前面的图中也有显示(原型对象的下面也有一个 __proto__属性):

__proto__1

于是我们可以得知,它们的关系链是这样的:

原型链1

由于 Object.prototype 这个原型对象处于顶端,所以它的原型对象为 null,即 __proto__值为 null

沿着对象的关系箭头就是原型链。

现在我们来解释一下,其它的对象类型是如何“继承” ObjecttoString 方法的。以 Person 为例:

1
2
3
4
5
function Person() {}

let person = new Person();

person.toString(); // "[object Object]"

  • 通过 person 对象的 __proto__属性,查找 person 原型对象 Person.prototype 上是否有该方法。
  • 有则调用,没有则通过原型对象 Person.prototype__proto__ 属性查找上一级原型对象 Object.prototype 上是否有该方法。
  • Object.prototype 上面有该方法,则调用 toString,最后得出结果。

所谓“继承”,本质时就是顺着原型链向上查找。

下一篇我们来介绍一下 JavaScript 中的 “对象”。


这是深入理解 JavaScript 语法合集的其中一篇,其它合集:
1. 深入理解 JavaScript —— 从设计一门语言讲起
2. 你真的掌握了 JavaScript 变量和类型嘛?(上)
3. 你真的掌握了 JavaScript 变量和类型嘛?(下)
5. JavaScript 中的对象

参考文章:
1. JavaScript深入之从原型到原型链-2017-04-23 by mqyqingfeng

有问题?发送 issues 给我~

0%