你还说你不懂原型链

又在炒冷饭,讲什么原型链

原型链

prototype 是啥玩意儿

每个函数,都有一个prototype

__proto__ 又是啥玩意儿

很多人看到 __proto__ 就会想到下面这张图,有的时候,知识抽象成图表反而不太明了,必须得知道对应关系,再结合图表来看,就会倍感清晰。
跟着我看完,如果还不清晰,你来砍我???

神图

不要一上来就给别人一张图
img

规律

首先你需要打开谷歌浏览器的控制台,依次执行以下代码,看看其返回值,总结出一些规律。

function fn() {} 
var f = new fn() 
f.__proto__  // 看看这个对象里面有啥  
/*
上面那行代码返回一个对象,constructor 指向其构造函数, 
又有一个 __proto__ 指向 'Object',记住这个Object的结构,待会要考
{constructor: ƒ}
constructor: ƒ fn()
__proto__: Object
*/
fn.prototype
/* 
再看看这行代码,好神奇耶,看起来跟 f.__proto__ 相等,
所以,我们得出第一个“规律”
f.__proto__ === fn.prototype  
你试着验证这条规律,发现它是对的,我们用文字总结:
规律1,对象的__proto__总是指向它的构造函数的prototype,也就是原型链
*/

那么问题来了,在js的世界里,一切皆对象,上面的 fn ,应该也是对象。再打开你的控制台,输入以下代码

fn.__proto__  
// 奇怪,跟上次长得不一样,控制台打印出了看不懂的代码 'ƒ () { [native code] }'  
// 再想想上个规律,对象的—__proto__总指向其构造函数,那 fn 的构造函数是?
Function.prototype
// 机智的你,迫不及待的想 用 === 把两个值进行比较,你没猜错,是 true
// 于是我们有了规律2,虽然它是规律1 的子集,但还是先总结起来,后面有用  
// 规律2,函数的__proto__ 总是指向 Function 的 原型链,并且还是一个 
// ƒ () { [native code] }

emmm,问题又来了,上面的代码片段打印出 ƒ () { [native code] }, 虽然它是原生代码,但它还是隐喻我们,它是一个 funtion,好奇的我们又想知道,它到底是什么玩意儿?

typeof Function.prototype
// 'function' 果然,它还是函数!!!,我们的规律2可以用起来了,
// 函数的 __proto__ 肯定是指向 Function 的原型链  
Function.prototype.__proto__ === Function.prototype  
// false  
// ???
// ???
// 你在逗我?
// 别着急,我们 打印出来看看
Function.prototype.__proto__  
// 你会发现,它是一个对象,并且其 constructor 指向了 Object 构造函数。  
// 它?会不会是 Object.prototype 呢?  
Function.prototype.__proto__ === Object.prototype
// 诚不欺我,果然是。可是为啥要推翻我们的规律,
// 硬生生的将它指给 Object.prototype呢  
// 再回头看看代码,其实它循环引用的。啥玩意儿是循环引用呢?  
let a = {}
let b = {}
a.b = a  
// 这就是循环引用,子子孙孙无穷尽也,为了规避这种情况,浏览器特意做了如上操作,于是我们又有了规律 3
// 规律3: Function 原型链的 __proto__ 指向了 Object 的 原型链,是为了规避循环引用。

我们总结了三种规律,到头了吗,Function 到头了,Object 还没有
让我们继续探究
规律3 是针对 Function 的原型链的,那 Object 的原型链是啥

typeof Object.prototype
// 快看,是对象,我们的规律1派上用场了,再回头看看规律1  
// 既然是对象,那其 __proto__ 肯定指向 Object.prototype 了,机智如我  
Object.prototype.__proto__ === Object.prototype
// false
// ?
// ???
// 经过上次 Function.prototype 的洗礼,我们一眼就发现了循环引用的问题
// 是的,为了避免这种问题,浏览器让 Object.prototype.__proto__ 指向了 null
// 让我们的原型链,有了“终点”,有了“根”。
// 我们的规律4也出炉了 Object.prototype 的 __proto__ 指向了 null

__proto__ 四条规律

  • 对象的 __proto__ 总是指向它的构造函数的prototype,也就是原型链
  • 函数的 __proto__ 总是指向 Function 的 原型链,并且还是一个 native code function
  • Function 原型链的 __proto__ 指向了 Object 的 原型链,是为了规避循环引用。
  • Object.prototype 的 __proto__ 指向了 null,也是为了规避循环引用
  • 当你彻底了解这四条规律的时候,尝试去看最开始那张神图,你会发现,so easy~~ *

好玩的代码

Object instanceof Function         // true
Function instanceof Object         // true

Object instanceof Object             // true
Function instanceof Function     // true

instanceof

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
原型链的魅力来了。
首先一切皆对象,那 instanceof 大家都可以用。

1, Object instanceof Function
代表着在 Object 的原型链中,存在一个 Function.prototype
Object.__proto__ === Function.prototype
true 通过

2, Function instanceof Object
代表着在 Function的原型链中,存在一个 Object.prototype
那么:
Function.__proto__ === Object.prototype
是false,未通过,怎么办呢,继续往上一个链条找。
Function.prototype.__proto__ === Object.prototype
true 通过

3,Object instanceof Object
代表着在 Object 的原型链中,存在一个 Object.prototype
Object.__proto__ === Object.prototype
false,怎么办呢,继续往上一个链条找。
Object.__proto__ === Function.prototype true
Function.prototype.__proto__ === Object.prototype
true 通过

4,Function instanceof Function
代表着在 Function的原型链中,存在一个 Function.prototype
那么:
Function.__proto__ === Function.prototype
true 通过

完结撒花

别忘了再看一眼开局的图

该撒花了?

不!还不是时候。你以为你懂了,其实。

表情包

“鸡生蛋,蛋生鸡”的哲学问题

🥚-> 🐔?
🐔-> 🥚?
我们注意到
Function.prototype === Function.__proto__

这是否说明了 Function 的构造函数是 Function,是谁生了我?我又生了谁?是 我生了我。

这不仅是个哲学问题,还是个TMD伦理问题!

按照 __proto__ 的定义,它就指向了其构造函数的原型,那 Function 的 构造函数是 Function 肯定是没问题的。别问为什么,标准就这么规定的,那么,第一个 Function 是谁创造的???

毫无疑问,这是一个无聊的问题,但很多人深陷其中不可自拔,就像哲学系里那一个个掉光头发的老头一样,苦思冥想。不过还好,这个问题没有 鸡生蛋 那么复杂。我们应该可以在头发掉光前理解它。(是吗?)

hax 的观点

贺老对于这个问题给出了两个答案,可以说是,也可以说不是。
是:

hax: 按照JS中“实例”的定义,a 是 b 的实例即 a instanceof b 为 true,默认判断条件就是 b.prototype 在 a 的原型链上。而 Function instanceof Function 为 true,本质上即 Object.getPrototypeOf(Function) === Function.prototype,正符合此定义。

hax:
Function 是 built-in 的对象,也就是并不存在“Function对象由Function构造函数创建”这样显然会造成鸡生蛋蛋生鸡的问题。实际上,当你直接写一个函数时(如 function f() {} 或 x => x),也不存在调用 Function 构造器,只有在你显式调用 Function 构造器时(如 new Function(‘x’, ‘return x’) )才有。

https://www.zhihu.com/question/31333084/answer/152086175

结束

为什么结束的这么突然,因为鸡生蛋这个问题意义不大,甚至是否理解原型链在大多数情况也影响不到你的开发,但当你了解其根本的时候,才会引发更多 “有趣” 的讨论,当你看源码的时候,才不至于昏昏欲睡。

终极问题:当你是一个面试官的时候,你会问原型链吗?