先从一个例子说起,不打开控制台能直接口喷结果的可以 cmd/ctrl + w 了。
function Parent() {}
var child = new Parent()
console.log(child.__proto__)
console.log(child.__proto__.__proto__)
console.log(child.__proto__.__proto__.__proto__)
console.log(Parent.__proto__)
console.log(Parent.__proto__.__proto__)
console.log(Parent.__proto__.__proto__.__proto__)
console.log(Function.__proto__)
console.log(Function.__proto__.__proto__)
console.log(Function.__proto__.__proto__.__proto__)
console.log(Object.__proto__)
console.log(Object.__proto__.__proto__)
console.log(Object.__proto__.__proto__.__proto__)
首先要弄清楚第一个概念 __proto__ 究竟是什么含义。
这里引用《JavaScript权威指南》的一段描述:
Every JavaScript object has a second JavaScript object (or null , but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.
翻译出来就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。
好吧说实话我是没懂这句话的含义,那么用我最通俗易懂的解释就是:__proto__就是用来找它爸爸的原型对象的!
那么再来记住第二个概念就是:所有的函数的祖宗都是 Function ,所有对象的祖宗都是 Object,并且Object也是个函数!
第三个概念:函数是函数,原型对象是对象,不要搞混。
有这三个概念在心里后上面的问题就迎刃而解了。
// child 继承于 Parent 函数
// 所以 child 的爸爸的原型对象就是 Parent 的原型对象
console.log(child.__proto__ === Parent.prototype);
// 由于所有对象的爸爸都是Object
// 所以 child 爸爸的原型对象的爸爸的原型对象就是 Object 的原型对象
console.log(child.__proto__.__proto__ === Object.prototype)
// 由于 Object 已经是所有对象的祖宗,所以他已经不知道他的原型对象的爸爸是谁了,这个时候 null 就出来了
console.log(child.__proto__.__proto__.__proto__ === null)
然后再来看 Parent 函数:
// 由于 Parent 函数找不到自己的亲生爸爸,所以他找到祖宗 Function 的原型对象
console.log(Parent.__proto__ === Function.prototype);
// 由于所有对象的爸爸都是 Object
// Function 的原型对象也不例外
// 所以一样都是 Object 的原型对象
console.log(Parent.__proto__.__proto__ === Object.prototype);
// 一样的,Object 的原型对象指向了 null
console.log(Parent.__proto__.__proto__.__proto__ === null)
然后再来看传说中的 Function 函数:
// 首先要知道 Function 很特别
// 它据说是 js 引擎的亲儿子,所以在js引擎中认为他爸爸就是他自己
console.log(Function.__proto__ === Function.prototype)
// 然后 Function.prototype 又是对象
// 所以你懂的
console.log(Function.__proto__.__proto__ === Object.prototype)
// 然后 Object.prototype 既是对象又是所有对象的祖宗的原型对象
// 所以你懂的
console.log(Function.__proto__.__proto__.__proto__ === null)
最后来看下这个神秘的 Object 函数:
// 首先你必须承认
// 这玩意他也是个函数! 不信你试试 typeof Object
// 所以他找不到他爸爸的时候找到了函数的祖宗 Function
console.log(Object.__proto__ === Function.prototype)
// 然后接下来的故事就有意思了
// Object 先生找他爸爸的原型对象后继续找原型对象的爸爸的原型对象结果找到了自己的原型对象
// 所以你可以看到
console.log(Object.__proto__.__proto__ === Object.prototype)
// Object: 兜兜转转又回到了自己好累~原来我才是最牛逼的
// 最后他选择了自我毁灭...
console.log(Parent.__proto__.__proto__.__proto__ === null)
最后在记住一点: 原型对象的 constructor 都指向他自己。
还有个坑也要记住:所有函数在被创建的时候都有原型对象,唯独 Function.prototype.bind 生成的函数没有原型对象!
好了,看完上面后我们再来看看这张经典图看看有没有豁然开朗的感觉。
总结
以上是结合网上各路大神介绍的原型链之间的关系后总结出来的经验之谈,不得不说js原型链设计的有点奇怪,最大的坑就在于Function 和 Object 这两个之间的联系。