从数组探寻JavaScript原型链
以一个最普通的数组为例
let arr = [1, 3, 2, 4]
调用arr.reverse()
可以让数组进行逆序排列
arr.reverse() // 此时arr变为[4, 2, 3, 1]
调用 arr.toString()
会将数组内容展示为字符串形式
arr.toString() // 此时控制台输出"4,2,3,1", arr不变
此时查看 arr 的属性,得到如下结果
> console.dir(arr)
▼Array(4)
0: 4
1: 2
2: 3
3: 1
length: 4
▶︎__proto__: Array(0)
并没有发现用过的 reverse
和 toString
方法,而当我们展开 arr 的 __proto__
属性时,我们找到了上述两个方法,并且还发现了一大堆见过的没见过的方法也在里面
> console.dir(arr)
▼Array(4)
0: 4
1: 2
2: 3
3: 1
length: 4
▼__proto__: Array(0)
▶︎concat: ƒ concat()
▶︎constructor: ƒ Array()
…
▶︎reverse: ƒ reverse() // 我们用过的reverse
…
▶︎toString: ƒ toString() // 我们用过的toString
…
▶︎__proto__: Object // 又一个__proto__
arr 的 __proto__
末尾还包含一个 __proto__
,如果继续展开,里面已经不再包含另一个 __proto__
了,似乎到这里就结束了。
回到之前的问题,当数组 arr 使用 reverse
和 toString
方法时,除了在自身层面和 prototype
中寻找以外,仿佛能够通过 __proto__
链接到另一个地方并使用它的方法。大胆猜测一下:这些方法是不是 arr 的构造函数所有的呢?
我们知道,数组的构造函数是 Array ,所以有了以下验证
arr.reverse === Array.prototype.reverse // 返回true
果然 arr 中的 reverse 同时也在 Array 的原型 prototype 中,进一步细化,我们可以验证
arr.__proto__.reverse === Array.prototype.reverse // 返回true
为了更直观地表示,我们可以画一张图
由此可见, arr.reverse
Array.prototype.reverse
arr.__proto__.reverse
三者是等价的,对象的 __proto__
属性链接到了构造函数的原型 prototype
上,此构造函数的 __proto__
属性链接到了自己的构造函数的原型 prototype
上……
这个就是原型链
所有函数都有一个 .prototype 属性,对应一个对象
使用 new 函数创建的新对象都有一个.__proto__
属性,指向构造函数的.prototype
当使用对象的属性或方法时,先从自身寻找,找不到再从.__proto__
找,以此递进,直到.__proto__
为 null
所以现在可以回答开头的问题了:
reverse
和 toString
等非自定义的方法或属性是在对象的原型链上定义的。
以此为依据可以进一步拓展,找一找对象的根源在哪里,也就是对象归根结底是由谁创建的。
这里可以使用运算符 instanceof
检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
arr instanceof Object // 返回true
Object instanceof Function //返回true
Function instanceof Function //返回true
验证可知, arr 对象是由 Object
函数创建的,而 Object
是由 Function
创建的,而 Function
也是由 Function
创建的。普通对象都是由 Object
函数创建的,函数都是由 Function
函数创建的。