prototype 是原型吗?终结 JS 原型疑问

1,737 阅读4分钟

JS 的原型和原型链可以说是最基础的知识了,一起来总结下

有图有真相

avatar
avatar

这张流传久远的图其实很好的将原型与原型链的关系表达了出来,我们去分析图中的指向就可以了

实例对象

  • 就是图中最左侧的一列,包括由构造函数 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 排版