JS 三座大山之原型和原型链

209 阅读4分钟

我正在参加「掘金·启航计划」。

一、__proto__之问:你的原型是谁?

你的原型是谁.png

自制了“图解原型链”小图一张,没学过的同学可以保存一下,到时候不懂了来查阅。

这个图怎么看?

线起点表示对象,线指向的终点表示这个「对象的原型」。比如最上面,arr=[1,2,3] 指向 Array.prototype,那么 arr.__proto__就指向 Array.prototype。(arr.__proto__ 表示数组 arr 的原型)

有几个重点:

  1. Object.prototype 是「原型链」的最顶端,它的原型是 null(没有画出来)

  2. 「原型」。一般来说,JS 中所有对象类型的数据都有一个隐藏属性,记作__proto__[[Prototype]]。这个隐藏属性所指向的对象就是原型,原型上有对象的“共有属性”(相对于对象的“自有属性”而言)。
    顺带说一下,之所以称之为“隐藏属性”,是因为它是不可枚举属性(enumerable: false

  3. 「原型链」。图中的这些箭头指向其实就是「原型链」的视觉呈现。当读取对象的属性时,如果找不到,就会去原型(隐藏属性对应的对象)上面找。如果还找不到,就会到原型的原型上去找,以此类推,直到找到Object.prototype为止。对象会沿着这些箭头方向追溯自己的「原型」,以及原型的原型,即沿着原型链在每个原型上寻找是否有这个属性。这样的追溯过程的轨迹就如图中箭头所示,形成一个像链子一样的东西,叫「原型链」它应该算是一种继承机制,其实就是去寻找存在于上游的共有属性。

二、constructor 之问:你是谁构造的?

你是谁构造的.png

1) 构造对象

面向对象编程中什么叫「构造对象」:具体到语法层面就是,let obj = new fn()。如果对象是fn通过 new 操作符创建出来的,那么我们就说对象 obj 是 fn 构造的,obj 的构造函数是 fn,fn 构造出对象 obj。

2) 不要被construcotr属性骗了

首先警告一点:不要被fooObj.constructor骗了,一个对象的constructor属性不一定代表它的构造函数。 虽然一般来说一个对象的constructor属性指向它的构造函数,但「prototype 对象」就是个反例。

3) prototype对象——函数.prototype

一般来说只有函数 (而且是「可以做构造器的函数」) ,才有 prototype 属性,普通的对象默认情况下没有 prototype 属性。我们把函数的 prototype 属性所指向的对象叫做「prototype 对象」。除「prototype 对象」以外,任何对象的 constructor 属性默认指向其构造函数。

4) prototype 对象的 constructor ——函数.prototype.constructor

prototype 对象的 constructor 属性并没有指向它的构造函数,举个例子:

function fn(){}
console.log((fn.prototype).constructor === fn) // true

声明一个函数fnfn.prototype 是个 prototype 对象,它的constructor 属性没有指向它的构造函数 Object,而是指向了fn

你看看,constructor 属性此时的指向是不是让人感到迷惑?

实际上 JS 引擎在创建函数 fn 时,自动通过 Object 构建 fn.prototype,自动将 fn.prototype.constructor 指向 fn。JS 引擎之所以这样做是为了 fn 创建实例化对象 obj 时,使得 obj.constructor 指向 fn,这样就能方便得知道实例对象 obj 的构造函数是谁。

需要几点说明:

  • 首先,有些函数是没有 prototype 属性的。比如箭头函数等,具体见:《无法做构造器的函数》。
  • 将这些函数排除在外后,其余所有函数的 prototype 属性指向的对象,都是由 Object 构造的。然后 JS 引擎自动将这些对象的 constructor 属性指向函数自身,所以才有 fn.prototype.constructor 指向 fn,也就是“fn 实例”的构造函数。
  • fn.prototype.constructor是可以被修改的,可以对其赋值,改变它的指向。所以通过constructor 属性判断对象的构造函数是不可靠的。
function f1(){}
Object.getOwnPropertyDescriptor(f1.prototype,"constructor")
// {writable: true, enumerable: false, configurable: true, value: ƒ}

5) JS 的内置对象是怎么被构造出来的?

  • Function 是个构造函数,是 JS 引擎用 C++ 创建,然后放到浏览器内存中,并且 JS 引擎指定 Function 的构造者是自己。
Function.constructor === Function // true
  • 所有的函数都是 Function 构造的。Object 也是函数,所以 Object 也是 Function 构造的。
  • Object 也是个构造函数,所有的对象都是 Object 函数构造的。( Function.prototype 也假定为 Object 构造,然后内部实现了[[Call]]方法,变成了callable object,具体需要看 V8 JS 引擎的内部实现。)