原型与原型链
简述原型与原型链
在 JavaScript 中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)。
通常,我们会通过构造函数对原型进行操作:
1 | function Foo() {} |
打印出来的对象就是Foo函数的原型,将其展开,发现里面有两个属性constructor和__proto__。constructor指向Foo函数本身,__proto__是一个对象。
可以在函数的原型上添加一些其他属性,比如:
1 | Foo.prototype.name = 'nick' |
再次查看Foo的原型:
1 | console.log(Foo.prototype) |
发现刚刚添加的name与age已经在原型中了。
现在创建一个Foo的实例:
1 | var foo = new Foo() |
将其打印,发现里面有一个__proto__属性,而这个属性里面包含了之前在构造函数Foo的原型中创建的name和age属性。也就是说,构造函数的实例都可以通过__proto__属性找到创建它的构造函数的原型对象。
由此可以得到下面这张关系图:
在之前打印Foo的原型时,发现里面的__proto__是一个对象,将其展开,发现有一个constructor属性是指向Object构造函数的。那么Foo原型上的__proto__会不会就是构造函数Object的原型呢?
带着疑问,试着打印对比一下:
1 | console.log(Foo.prototype.__proto__ === Object.prototype) // true |
结果为true。并且我们知道在JavaScript中,函数都有一个特殊的属性prototype,那么构造函数Object的内部结构与之前创建的构造函数Foo的内部结构是差不多的。
现在再来更新一下刚刚的关系图:
可以看出,函数的__proto__属性就像一根线条一样一直往上衔接,那么问题又来了?Object原型上的__proto__又是个啥?会不会是其他构造函数的原型?
带着疑问,再次打印看看结果:
1 | console.log(Object.prototype.__proto__) // null |
结果是一个特殊值null,这就表明,原型链的最顶层就是到构造函数Object原型的__proto__,也就是null。
更新一下刚才的关系图:

到这里,基本上把原型与原型链的关系理清楚了。
但是,还可以延伸一点点内容。
我们知道,在JavaScript中,函数实际上是一个Function对象。这是不是就表明构造函数Foo与Object是Function的实例?
可以通过instanceof来测试一下:
1 | Foo instanceof Function // true |
发现都为true,按照之前的结论,实例的__proto__属性都指向创建它的构造函数的prototype对象,
这就说明,构造函数的__proto__都是指向Function的prototype对象。
1 | console.log(Foo.__proto__ === Function.prototype) // true |
而Function的__proto__应该也是指向自身的prototype对象的:
1 | console.log(Function.__proto__ === Function.prototype) // true |
那么Function的prototype对象中的__proto__又指向哪里呢?
当然是Object的原型呐:
1 | console.log(Function.prototype.__proto__ === Object.prototype) // true |
这样一来,再更新一下刚刚的关系图:

总结
- 任何函数都有一个
prototype属性,它是一个对象,直观的说,这个对象就是当前函数的原型。 prototype中的constructor通常指向函数自身。- 实例对象的
__proto__指向创建它的构造函数的原型。 - 构造函数的
__proto__指向Function的原型。 Function的__proto__指向自身的原型(和上一条类似)。
