JS 原型和原型链

JavaScript 基于原型继承,class 实际上是函数,是一种语法糖;

隐式原型和显式原型

.__proto__是隐式原型;
.prototype是显式原型;

每个实例都有隐式原型;
每个 class 都有显式原型;
每个实例的.__proto__都指向 class 的.prototype
实例会先在自身的属性和方法上寻找,如果找不到则自动顺着.__proto__链查找;

instanceof 判断数据类型

instanceof只能用于判断引用类型;

instanceof通过左操作数的隐式原型链(__proto__)向上查找,如果在某一刻与右操作数的显式原型(prototype)重合,则返回 true,否则继续向上查找,直到 null,返回 false;(注意 ⚠️:左边是原型链,右边是原型);

也就是说,instanceof根据对象(左操作数)是否是某个构造函数(右操作数)的实例来返回布尔值(判断对象的类型);

引用《你不知道的 JavaScript 上卷》第二部分第五章的一句话,可以很形象的描述instanceof工作的原理:a instanceof Foo

instanceof 回答的问题是:在 a 的整条[[Prototype]]链中是否有 Foo.prototype 指向的对象?

[[Prototype]]属性是对象的一个特殊的内置属性,其实就是对于其他对象的引用,几乎所有对象在创建时[[Prototype]]属性都会被赋予一个非空的值;

当试图引用对象的属性时,会触发[[Get]]操作,如果对象自身有这个属性,则使用它,否则沿着[[Prototype]]链向上查找,这个过程中如果找到匹配项则返回值,如果找完整条[[Prototype]]链都没有匹配项,则返回 undefined;

规则很眼熟吧,没错,[[Prototype]]链就是原型链;

简单实现 instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
*
* @param {object} target 对象
* @param {constructor} origin 构造函数
* @returns true 或 false
*/
function myInstanceOf(target, origin) {
// 1. 非引用类型或 null 返回 false
if (
(typeof target !== "function" && typeof target !== "object") ||
target === null
) {
return false;
}
// 2. 沿着 target 的原型链向上查找
while (target) {
if (target.__proto__ === origin.prototype) {
return true;
}
target = target.__proto__;
}

// 3. target 为 null 还没有找到,返回 false
return false;
}

Object.prototype.toString.call()

Object.prototype.toString()是 Object 原型对象上的方法,返回表示该对象类型的字符串;

为什么不直接调用对象自身的 toString() 方法

不直接调用是因为大部分类型都重写了 toString() 方法,代表各自不同的逻辑,所以需要调用 Object 原型对象上的 toString() 方法,才能返回表示对象类型的字符串;

例如:

  1. Array 的 toString() 方法返回数组元素的字符串表示;
  2. String 的 toString() 方法返回字符串的字面量表示;
  3. Number 的 toString() 方法返回数值对应的字符串表示;
  4. Boolean 的 toString() 方法返回布尔值对应的字符串表示

Object.getPrototypeOf()

Object.getPrototypeOf()返回指定对象的原型(内部[[Prototype]]属性的值);

在 ES5 中,如果传递给方法的参数不是对象,则会抛出 TypeError 异常;

在 ES6 中,如果传递给方法的参数不是对象,则会强制类型转换为对象;

isPrototypeOf()

还是引用《你不知道的 JavaScript 上卷》第二部分第五章的一句话,可以很形象的描述isPrototypeOf()工作的原理:Foo.prototype.isPrototypeOf(a)

isPrototypeOf() 回答的问题是:在 a 的整条[[Prototype]]链中是否出现过 Foo.prototype?

isPrototypeOf()并不一定需要间接引用构造函数,只需要两个对象也可以判断它们之间的关系,它们的.prototype__proto__属性会被自动访问;

1
2
//问:b 是否出现在 c 的原型链中?
b.isPrototypeOf(c);

特殊情况

Object.getPrototypeOf(`包装器(构造函数)`)

Object.getPrototypeOf(包装器(构造函数))会把创建对象的包装器(构造函数)当作对象,返回的是函数对象的原型Function.prototype;

class 的原型本质

class 的原型本质是构造函数 + 原型链,继承通过原型链实现,类中定义的方法放在构造函数的原型上;

画图理解原型链

下面代码的原型链是怎样的?

1
2
3
function Foo() {}

let fn = new Foo();
  1. fn.__proto__ === Foo.prototype,实例的隐式原型指向(构造)函数的显式原型,这是毋庸置疑的;
  2. Foo.prototype.constructor === Foo,函数原型的构造函数指向函数自身(废话);
  3. Foo.__proto__ === Function.prototype,Foo 是函数,所以 Foo 的隐式原型指向 Function 的显式原型;
  4. Foo.prototype.__proto__ === Object.prototype,一切皆对象,所以函数原型的隐式原型最终指向 Object.prototype;
  5. Object.prototype.__proto__ === null,原型链的终点,1、4、5 组成了一条原型链;
  6. Function.__proto__ === Function.prototype,后面解释;
  7. Function.prototype.constructor === Function,同 2;
  8. Function.prototype.__proto__ === Object.prototype,同 4;
  9. Object.__proto__ === Function.prototype,同 6;
  10. Object.prototype.constructor === Object,同 2;

解释一下 6 和 9,Function 和 Object 都是(构造)函数,因为可以使用 new 操作符,既然是函数,所以原型是 Function.prototype 就合情合理了;

如图:

画图理解原型链