js原型世界

先从instanceof说起

在日常开发中,我们通常使用typeof去判断一个数据类型或者是否为undefined。

但是其结果只能判断出基本的数据类型,即为:number, string, object, boolean, function, undefined等。所以在进行例如数组和对象判断时,我们需要使用instanceof来检测某个数据是否为Array或Object的实例。
例如:

1
2
3
4
5
const a = new Array();
const b = new Object();

console.log(a instanceof Array);
console.log(a instanceof Object);

我们会发现 输出的结果都为true。这个时候可能会疑惑,这样一来无法判断出a到底是Array还是Object的实例了。
我们接下来继续:

1
console.log(Array instanceof Object);

从这里我们看出来Array本身也是继承于Object类。

那么我们怎么判断ab的数据类型呢?只需要多做一层判断

1
2
console.log(b instanceof Array);
console.log(b instanceof Object);

前者返回false,后者返回true。这说明了b是一个对象而非数组。而a在第一次判断就可以确定是一个数组对象。a instanceof Object的判断是多余的。

instanceof 的实现

那么我们现在可以说instanceof 是用来判断生成实例对象所对应的构造函数么?那Array instanceof Object和extends继承有什么关系?

下面我们来实现一个instanceof:

1
2
3
4
5
6
7
8
9
10
11
12
13
function instanceOf (A, B) {
let proto = A.__proto__;
let prototype = B.prototype;
while (true) {
if (proto === null) {
return false;
}
if (proto === prototype) {
return true;
}
proto = proto.__proto__;
}
}

代码很简单,参数A就是instance左边的数据,首先取A的隐式原型(可以理解为对象中的原型,它本质上等于构造函数的原型,这也是我们用来做instanceof判断的基准)。
接下来,就是取B的原型。这里做了一个while循环,终止条件就是一直向上查找到Object.prototype的隐式原型,这里要注意,不管是实例对象还是函数原型,都包含了一个隐式原型对象。
在查询匹配到Object.prototype.proto的时候已经到达了终点就是null。这里可以理解为并没有匹配到对应的B的原型。
方式就是通过获取隐式原型向上查找,如匹配到A的隐式原型和B的原型相同则返回true。

这里我们再通过代码理解一下Array instanceof Object 为 true:

孔子说过:在js当中一切皆为对象。Array 可以理解为一个函数,也可以理解为是一个对象。

那么有如下代码:

1
2
Array.__proto__ === Function.prototype; (true)
Function.prototype.__proto__ === Object.prototype; (true)

九拐十八弯,我们终于发现其实 Array.proto.proto 往上2层寻找到了对应的Object原型。

这里值得注意的是,Function的隐式原型等于Function的原型。这个可能有点怪异,后面会另外做讲解。

准备上我呕心沥血制作的美图了

到这里,可能不太熟悉原型,原型链,构造函数以及相互之间的关系的同学已经要懵逼了。下面我通过原创的关系图来做一个形象的说明。

alt text

如果这张图还不能满足你对于原型的理解,我可以尽量说的再详细一点:

js饼干工厂

首先让我们脑洞一下,js世界里有一个饼干工厂。我就是厂长啦,既然是饼干工厂那肯定是要生产饼干的。
下面我要给饼干做一个市场定位,是专门生产8-14岁的青少年儿童饼干,所有的饼干都富含多种促发育的营养物质,这个就是Object.prototype。
他是一切饼干产品及制作的根源(一切皆为对象)

接下来建立产品饼干的研发中心Function.prototype, 它设立了好几条产线例如:
String, Number, Function, Object, Array, Boolean等函数,当然他们也有自己的prototype 独立的生产配方。但是他们都是基于这个研发中心来生产的。
所以上述函数的隐式原型就是来源于Function.prototype。
另外Function可以理解为处理产品的实验室而非产线,它既可以实例出新的函数,其本身又可以作为对象可以查询到自身的隐式原型指向。

最后通过这些函数可以new 出各种不同种类的饼干例如{}, [], 1, ‘23’, true等等。而这些实例的对象的隐式原型又指向生产他们的配方。

换句话说Object.prototype是一切对象的原型定义, Function.prototype是一切函数的原型定义。但是函数本身也是对象,所以就有了Function.prototype.__prototype =
Object.prototype. 这样一来Object.prototype站在了js原型的最顶端。

通过原型链的层次可以这样来排列:

  1. Object.prototype

  2. Function.prototype

  3. Object, Function, Symbol, Boolean, String, Number

  4. {}, function, symbol, true, ‘123’, 123

  5. 自定义的function本身还可以自定义原型并实例化新的对象{…}