start
其实上一篇文章就基本说明了原型之间的关系。
这里再总结一下:
- 函数的显示原型属性
prototype
是一个对象,也就是原型。 - 对象的隐式原型属性
__proto__
指向原型,__proto__
将对象和原型对象连接起来组成了原型链。 - 原型有一个属性
constructor
指向它对应的构造函数。
因为在 JS 中是没有类的概念的,为了实现类似继承的方式,通过 _proto_
将对象和原型联系起来组成原型链,得以让对象可以访问到不属于自己的属性(即可以访问到原型链上的属性)。
特殊情况
1. 函数Tomato 的隐式原型和显示原型
其实上面的基本只是理解透彻了,基本原型这个东西就很简单了。但是有一些特殊情况需要研究说明一下。
我们知道,函数上有一个 prototype 属性,指向了它的原型对象。实例对象上有一个__proto__
指向它构造函数的原型对象。
但是不要忘记了,函数也是一种特殊的对象!所以函数也会有__proto__
属性。我们去看看他指向的什么
首先定义一个函数,我们打印一下它。
function Tomato() {
}
// console.dir 用于打印对象的详细信息
console.dir(Tomato)
根据上面的打印,我们可以得到这么一个关系图:
图一
函数 Tomato 的隐式原型和显示原型都在上面的图了。它的隐式原型,也是一个对象。这个对象的constructor指向的是Funciton。
2. 函数 Function 的隐式原型和显示原型
我们继续去打印一下构造函数 Function 的两个原型属性。
console.dir(Function.__proto__)
console.dir(Function.prototype)
console.dir(Function.prototype === Function.__proto__)
什么情况?Function的隐式原型和显示原型居然相等!!!
之前是 实例对象的隐式原型 指向 构造函数的显示原型 t1.__proto__ === Tomato.prototype
。
这里我看了很多的博客,原文结论如下:
Function作为一个内置对象,是运行前就已经存在的东西,所以根本就不会根据自己生成自己,所以就没有什么鸡生蛋蛋生鸡,就是鸡生蛋。
至于为什么
Function.__proto__
=== Function.prototype,我认为有两种可能:一是为了保持与其他函数一致,二是就是表明一种关系而已。简单的说,我认为:就是先有的Function,然后实现上把原型指向了Function.prototype,但是我们不能倒过来推测因为Function.proto === Function.prototype,所以Function调用了自己生成了自己。
我的想法:
- 除了
Function
,所有的函数都是new Function形成的。- 其次,不用太过于纠结于这个地方,可以参考上面博客的理解思路。
得到最新的关系图
3.沿着原型链继续向上寻找
function Tomato() {
}
// 函数Tomato的隐式原型的隐式原型,
console.log(Tomato.__proto__.__proto__)
// 函数Tomato的显式原型的隐式原型
console.log(Tomato.__proto__.__proto__)
console.log(Tomato.__proto__.__proto__.__proto__) // null
console.log(Tomato.__proto__.__proto__===Tomato.prototype.__proto__) // true
由上面的代码可以得到这么一个关系图。
这样一直找下去,发现:
-
它们的原型都指向了一个constructor为Obejct的对象。
-
原型的尽头,是null。
4. 看下Object
console.dir(Object.prototype)
console.dir(Object.__proto__)
console.log(Object.__proto__ === Object.prototype) // false
由上图可以得知这么一个关系。
5.总结原型链
- 我画了一个我理解的原型图解
-
原型链的本质就是为了实现属性的共享,理解原型了之后,你会发现,怪不得数组可以
.push
,字符串却不行。 -
还有一个我第一次接触原型的时候的痛点,prototype和constructor互相指来指去,一个圆环有什么用???其实,到我现在的认知里面,看图解,先只看 隐式原型
__proto__
即可。可以理解为 原型链通过__proto__链接起来的,就很好理解啦。
说个丢人的场景:不懂原型的时候,查看 prototype和constructor 查看了很久,哈哈哈…
__proto__
,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Tomato.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__
时,可以理解成返回了 Object.getPrototypeOf(obj)。 (我记得ES6之后开始官方支持了这个属性,但是我没找到官方的原本出处,就不说百分百确认的话了。)
其他优质图解
我虽然画了一个原型之间的关系图,但是还是有点草率,这里记录一下其他图解。其实懂了原型链,这个图看起来就很简单了。
本文参考的原型相关的优质博客。
END
原型学到这里基本ok啦。