❝JS 的原型和原型链可以说是最基础的知识了,一起来总结下
❞
有图有真相
❝这张流传久远的图其实很好的将原型与原型链的关系表达了出来,我们去分析图中的指向就可以了
❞
实例对象
- 就是图中最左侧的一列,包括由构造函数 Foo 生成的 f1、 f2 和 Object 生成的 o1、o2
构造函数
- 就是图中中间的一列,Functions
原型对象
- 就是图中最右边的一列,Prototypes
实例与构造函数
- 实例对象由
new关键字调用构造函数生成,则称该对象是这个构造函数的一个实例。构造函数有一个prototype属性,而这个就是该实例的原型,那么实例有没有什么属性指向它的原型呢,肯定是有的,这就是所谓的隐式原型__proto__,同时 ES5 也新增了Object.getPrototypeOf()这一静态方法获取一个对象的原型,代码如下:
// Foo.prototype 是实例对象 f 的原型对象
function Foo(){}
const f = new Foo()
f.__proto__ === Foo.prototype // true
Object.getPrototypeOf(f) === Foo.prototype // true
原型的原型对象
- 上面我们从实例出发,找到了它的原型指向的是构造函数的
prototype属性;但实例的原型也是一个对象,它应该也存在自己的原型,那么这个原型对象的原型该怎么找呢,其实和上面是一样的,也是指向它构造函数的prototype属性。这个原型对象的构造函数是 Object,因此:
f.__proto__.__proto__ === Object.prototype // true
const fOfPrototype = Object.getPrototypeOf(f); // 得到实例的原型对象
const pOfPrototype = Object.getPrototypeOf(fOfPrototype); // 得到原型对象的原型
pOfPrototype === Object.prototype // true
原型链的终点
- 我们通过一个普通实例对象,找到了它的原型对象,又找到了原型对象的原型,指向
Object.prototype,这同时说明Object.prototype也是一个对象,那么按照上面的结论,它应该也有自己的原型,即Object.prototype.__proto__,那么这个对象是什么呢,到了这里,就走到了原型链的终点 null,即:
Object.prototype.__proto__ === null // true
Object.getPrototypeOf(Object.prototype) === null // true
函数对象的原型
- 从一个普通实例沿着原型链一直找到终点 null,原型的知识就结束了吗,当然没有,对照开篇的图可以看到,函数被我们漏掉啦,那么接下来就分析一下函数的原型。其实很简单,跟上面是一样的。我们知道在 JS 里,函数也是一个对象,那么它的原型当然也指向它构造函数的
prototype属性,而函数的构造函数是Function,即:
const bar = new Function();
bar.__proto__ === Function.prototype //true
Object.getPrototypeOf(bar) === Function.prototype // true
// 开篇使用的构造函数
Foo.__proto__ === Function.prototype //true
Object.getPrototypeOf(Foo) === Function.prototype; //true
- 函数的原型对象是
Function.prototype,同样的问题,那这个原型对象的原型指向哪里呢,其实上面已经分析过了,因为Function.prototype是一个对象,那么它的原型也是指向其构造函数的prototype属性,即:
Function.prototype.__proto__ === Object.prototype //true
- 到了
Object.prototype这一层,再往下走就到达了原型链的终点
特殊对象的原型
- Object 是生成对象的构造函数,是一个函数对象,因此它的原型是:
// 指向其构造函数的 prototype 属性
Object.__proto__ === Function.prototype //true
- Function 是生成函数的构造函数,是一个函数对象,因此它的原型是:
// 还是指向其构造函数的 prototype 属性
Function.__proto__ === Function.prototype //true
总结
- 我们已经把开篇图中所有对象和函数间的指向分析完毕,其实 JS 原型相关的知识很简单,总结起来就一句话:「对象的原型指向其构造函数的 prototype 属性,原型又有它自己的原型,沿着原型链一直向上回溯,一直找到 Object.prototype,而它的原型是 null,到达原型链的终点」。
- 只不过普通对象和函数对象稍有不同。我们知道在 JS 中,构造函数其实就是一个普通函数,只是因为使用了 new 关键字去调用,才具有了构造函数的特性。因此几乎所有的函数对象不管有没有作为构造函数去调用,除了具有指向其自身原型的
__proto__属性外,还有一个prototype属性,即:
bar.hasOwnProperty('prototype') //true
- 为什么说是几乎所有的函数对象呢,因为确实有一种函数没有这个属性,即:
const bound = bar.bind(null);
bound.hasOwnProperty('prototype'); //false
- 同样的,几乎所有对象都有继承自原型的方法,以及指向其原型的
__proto__属性,除了:
// obj 是一个真正的空对象,没有任何原型上的方法,比如 toString() 等
const obj = Object.create(null);
测试
- 我们通常可以使用
instanceof运算符检测一个对象是否是另一个构造函数的实例,其原理就是「检测运算符右侧构造函数的 prototype 属性值是否出现在运算符左侧实例对象的原型链上」。那么运用现在学到的知识,结合开篇图,看看下列代码的输出结果:
Function instanceof Object
Object instanceof Function
Function.prototype instanceof Object
Function.__proto__ instanceof Object
- 输出结果:
全是 true
本文使用 mdnice 排版