这是深入理解 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 除外),在创建的时候会与之关联另一个对象,这个对象就是我们所说的原型。每个对象都会从这个原型中“继承”属性。
用一张图表示它们的关系:
图中的指向方向是从构造函数到实例原型,那实例原型有没有对象指向构造函数呢?我们通过打印可以得知,有的:1
2
3
4
5function Person() {}
let person = new Person();
console.log(Person.prototype); // {constructor: ƒ}
打印后得出,实例原型中有一个 constructor
对象,且查看内容可知,这个对象指向的正是构造函数:
也可以通过打印来验证一下:1
2
3
4
5function Person() {}
let person = new Person();
console.log(Person.prototype.constructor === Person); // true
其实每个原型对象都有一个 constructor
属性,它指向构造函数:
(2)实例对象与实例原型
讲过了构造函数与实例原型之间的关联,现在来讲讲实例与实例原型之间的关联。
我们通过打印可以得到,每个实例都有一个 __proto__
属性,且指向实例原型(观察到,__proto__
中含有属性 constructor
,而该属性是实例原型的):
于是,我们得出构造函数、实例和实例原型之间的联系:
当我们通过构造函数创建多个对象实例时,所有对象都有各自的 __proto__
属性,且指向原型对象。
二、JavaScript 中的原型链
(1)从原型到原型链
其实我们前面在使用 toString
来检测变量类型时就已经提到过:由于大部分对象类型的 toString
方法被重写,所以我们追溯到原型链的顶端,使用 Object
的 toString
方法来检测。
对象类型的 toString
方法就是从 Object
中“继承”过来的。那是如何“继承”的呢?
我们刚刚讲过,每个对象都拥有一个 __proto__
属性,且这个属性指向原型对象。而我们知道,原型对象也是一个对象,所以,原型对象也是有一个 __proto__
属性的,它也指向它的原型对象。前面的图中也有显示(原型对象的下面也有一个 __proto__
属性):
于是我们可以得知,它们的关系链是这样的:
由于 Object.prototype
这个原型对象处于顶端,所以它的原型对象为 null
,即 __proto__
值为 null
。
沿着对象的关系箭头就是原型链。
现在我们来解释一下,其它的对象类型是如何“继承” Object
的 toString
方法的。以 Person
为例:1
2
3
4
5function 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 给我~