什么是原型链?

引子

什么是原型链,这可以说是 JS 中一个老生常谈的问题了,下面我从两个问题,一个理论、一个实际来分析,相信你看完一定能明白什么是原型链。

问题一: 什么是原型链

让我们先谈谈前置,当我们什么代码都不写的时候,JS 最初就有:

  • JS 有个全局对象window,它有很多重要的全局属性

    • 例如window.Object()window.Array()window.Function
    • JS 里函数也是对象
  • Object 也有一个很重要的属性,叫做Object.prototype

  • 同理,Array 也有这样的属性Array.prototype
  • Function.prototype

下面让我们开始写代码吧:

1
2
3
var obj = {}; // 现在我们创建了一个空对象
// 执行
obj.toString(); // "[object Object]"

为什么我们创建了一个空对象,确有toString()方法呢,这里就需要用到我们的原型链了,我们的 JS 引擎事先就会在Object.prototype上写好了toString()方法。

那么,obj是怎么链接到Object.prototype,这是由于 JS 的实现规定,如果obj是一个对象,obj就能访问到Object.prototype的属性了。

那么,浏览器是如何实现这个规则的呢?

当我们在声明对象的时候,浏览器引擎会把Object.prototype对象地址存到obj.__proto__里,然后我们实际执行obj.toString()时,首先会查找对象本身是否有该方法,如果没有,它就会去查找obj.__proto__是否有该属性。

那么,我们来看看array:

1
2
3
4
5
var a = []; //
// 执行
a.length; // 0
a.push(1);
a; // [1]

这里实际上,我们在声明var a= []后,引擎除了会把原型地址赋值给a,还会执行a.length = 0

好了,我们来问个比较刁钻的问题,当我们执行a.toString()会发生什么呢?

大家可以查询一下Array.__proto__,实际上是有toString()方法的,但是我们上面说过一个规则,对象是能访问到Object.prototype的,数组也是对象,那么数组应该也能找到Object.toString()呀,实际上,如果我们在数组的__proto__找不到对应方法,就会往上,找Object.prototype,即a.__proto__.__proto__/Array.prototype.__proto__

下面一张图把这些关系连起来:

原型链

问题二 “伪数组”与”真数组’’?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 伪
var arr = {
0: 0,
1: 1,
2: 2,
length: 3,
};
arr.push(); // undefined

// 真
var arr2 = [0, 1, 2];
arr.push(4); // [0,1,2,4]

// 修改原型
arr.__proto__ = Array.prototype;

上面我们定义一个好像数组的对象,我们使用{}声明,它其实是一个对象,不是数组,但是我们可以通过修改arr.__proto__ = Array.prototype来让它具有数组的方法,这也是原型链的一种体现。

总结

原型会把一些写好的函数或者属性存在Object.prototype中(这里我们以Object为例),而我们声明属性的时候引擎会帮我们定义一个obj.__proto__去存这个事先有的对象的地址,原型链就会把这些对象地址串起来,执行某些方法时,依次往上寻找是否存在该方法,直到最顶层Object都没有时,抛出方法未定义的错误。

作者

bert_cai

发布于

2019-04-22

更新于

2020-11-16

许可协议

评论