主要内容:
- 对象的继承树。
- 函数的继承树。
- 函数 VS 对象
- prototype VS _ _ proto__
- 继承 VS 组合
- 自己定义函数(class),以及实现继承
寻找原型链的“源头”
网上有一个梗:万物基于MIUI。虽然是一句调侃,但是也表达源头的重要性。
看过一些高手写的关系图,应该是非常专业,但也正是因为太专业了,所以才导致新手看的是一头雾水。那么对于先手来说,有没有简单一点的方式呢?我们可以借鉴一下面向对象的思路。
提到面向对象,大家都会想到基类(超类、父类)、子类、继承、多态等。为啥容易记住呢?因为继承关系非常简单,从基类开始,一层一层继承下去,结构非常清晰明了。
我觉得应该借鉴一下这种表达方式,也许这种方式并不契合JavaScript,但是我觉得应该比较方便初学者入门。
经常听说,JavaScript 的世界是基于 Object 的,这句话对但是又不对,为啥这么说呢?我们来看看 Object 的结构:(使用 console.dir() 可以看到细节 )
console.dir(Object)
Object的结构组成
首先请注意一下那个 f 的标识,这表示 Object 其实是一个函数(从 JavaScript 的语法角度来看),我们来验证一下:
Object 其实是函数
这到底是怎么回事呢?后面细说,先把找到源头才好理解。
这个 Object 并不是源头,因为还有 prototype 和 __ proto__, 我们先看看 Object.prototype 的结构:
Object.prototype
console.dir(Object.prototype)
Object原型的结构
可以看到,Object.prototype 才是源头,因为 Object.prototype 没有 prototype(当然没有),_ _ proto__ 也是 null,我们来验证一下:
console.dir(Object.prototype.prototype) console.dir(Object.prototype.__proto__)
Object 验证prototype
Object 验证 proto
Object.__proto __
103-object2.png
这是啥?是不是很头晕,这个其实指向的是 Function的原型,我们来验证一下:
103-object2验证.png
这是咋回事?后面再解释。
小结
是不是有点晕,让我们来梳理一下思路:
Object的三个重要属性
如果看上面的图有点晕的话,可以先看下面的图,灰线说的是构造函数的关系,可以先跳过。(终于画出来了那种绕圈圈的图,向着专业又迈出了一步)
Object的两个重要属性
- 思路一:Object有两个属性,一个是对象原型,一个是函数原型。
- 思路二:Object有两个指针,一个指向对象原型,一个指向函数原型。
我觉得思路二更适合一些,这个是理解 JavaScript 的原型链的第一个门槛,如果绕不清楚的话……没关系,往下看就好,我也是把下面都写出来,然后回头才整理出来这个图的。。。(这个也是给继承和组合做个铺垫)
构建一颗大树 —— 对象的继承关系
找到源头之后,我们就可以构建一颗大树了。
构建原则:xxx.prototype._ _ proto__ === Object.prototype 即:Object.prototype 看做父类,然后把其“子类”画出来。
对象的树
这下是不是清晰多了呢?我们来验证一下:
- Array
190-Array原型.png
- String:
String原型
好长好长,差点截不下来。
- Number
Number原型
- BigInt
BigInt原型
- Boolean
Boolean原型
- Symbol
Symbol原型
- Date
Date原型
- RegExp (正则表达式)
RegExp原型
- Math
Math
共同点
每种类型都有自己的成员,然后_ _ proto__ 指向 Object.prototype。
特例
这里有几个特殊情况:
- Math
没有原型,或者说原型就是 Math 自己。 - Array
这个比较奇怪。 - null 和 undefined
这对兄弟先当做特殊情况来处理。 - Function
Function.prototype._ _ proto__ 也是指向 Object.prototype的,但是 Function.prototype 是一个 f。 - Object
如果说 Object.prototype 是基类的话,那么Object是啥呢?其实 Object 是函数。是不是有点晕?从JavaScript 语法的角度来说,不仅 Object 是函数,String、Number这些都是函数。
再构建一颗大树 —— 函数的继承关系
观察上面的图(对象的树)可以发现,我写的都是xxx.prototype 的形式,那为啥不直接写xxx呢?
因为从 JavaScript 的语法的角度来看,Object、String、Number、Array、Function等都是函数,Object.prototype、String.prototype 等才是对象。我们从函数的角度来构造另一颗大树。
依据:xxx._ _ proto__ === Function.prototype 即:把Function.prototype看做父类,把他的子类(__ proto__指向他的)都画出来。
函数的树
这里加上“()”,明确一下,然后我们来看一下具体的结构:
- Function
200-Function原型.png
- String
String()
- Number
Number()
- Boolean
Boolean()
- BigInt
BigInt()
- Symbol
Symbol()
- Date
Date()
- RegExp
RegExp()
- Array