你还说你不懂原型链
又在炒冷饭,讲什么原型链
原型链
prototype 是啥玩意儿
每个函数,都有一个prototype
__proto__
又是啥玩意儿
很多人看到 __proto__
就会想到下面这张图,有的时候,知识抽象成图表反而不太明了,必须得知道对应关系,再结合图表来看,就会倍感清晰。
跟着我看完,如果还不清晰,你来砍我???
神图
不要一上来就给别人一张图
规律
首先你需要打开谷歌浏览器的控制台,依次执行以下代码,看看其返回值,总结出一些规律。
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
trueFunction.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
结束
为什么结束的这么突然,因为鸡生蛋这个问题意义不大,甚至是否理解原型链在大多数情况也影响不到你的开发,但当你了解其根本的时候,才会引发更多 “有趣” 的讨论,当你看源码的时候,才不至于昏昏欲睡。
终极问题:当你是一个面试官的时候,你会问原型链吗?