先附上代码
class A{}
class B extends A{}
B.__proto__ === A //true
B.__proto__ === Function.prototype //false
typeof B //function
B.constructor === Function //true
我们知道,每个实例对象( object )都有一个私有属性(称之为 __proto__
)指向它的构造函数的原型对象(prototype )
class
是语法糖,本质上还是个函数,因此B
实际上是个函数,可以看作通过new Function
创建出来的Function
对象实例
那么问题来了B.__proto__
不应该等于Function.prototype
吗?为什么等于A
呢?这是特殊规定的吗?
B.__proto__.__proto__ 确实是
Function.prototype
,但首先它的原型是 A ,其原型的原型才是函数原型。因为定义在 A 上的静态方法 B 也要继承。更新:
每一个对象都有原型,但是对象的原型并不一定是对象的构造函数的 prototype 属性。
我在这里先声明一下几个术语,前端届对原型的称呼一直比较混乱:
原型:
点操作符
或者[ ]
访问属性时,会先检查对象本身的属性,如果不存在则会检查对象的原型,如果还不存在则会继续检查对象原型的原型直到原型的尽头 Object.prototype ,它没有原型,它的原型是 null,这个就是所谓的原型链。new 一个构造函数发生了什么:
什么是继承?
基于 prototype 的继承模拟基于 class 的继承:
说的有点多,回到你的问题:
每一个对象都有原型,但是对象的原型并不一定是对象的构造函数的 prototype 属性。
且不说 constructor 属性并不是一个锁死的属性,JS 中的有些对象也并不一定存在一个构造器。
如上代码展示,其实 JS 中对象的构造器不是在所有场合都合理且有意义。构造函数这个概念在基于 prototype 继承的体系里其实是不需要的,它是 JS 模拟基于类的继承加入的东西。
那么题主的问题到底出现在哪里呢?出现在这里:
class
是语法糖,本质上还是个函数,因此B
实际上是个函数,可以看作通过new Function
创建出来的Function
对象实例class 是语法糖没错,本质是函数也没错,错在 class B 并不能看作是 new Function 构造出来的,class A 可以,但是 B 不行。因为 B extends A,B 是通过 A 构造出来的。你忘了,JS 继承的本质是对象继承。 class B extends A 这里隐含了两个 prototype 继承。 B.prototype 继承了 A.prototype, 同时 B 继承了 A。
因为静态属性也是需要继承的,所以 B 并不是直接通过 new Function 构造出来的,B 是通过 A 作为原型构造出来的,即 B = Object.create(A); 这样 B 才能获得 A 上定义的静态属性和方法,才符合基于 class 的继承的表现。
所以,B.constructor 在这里并没有很符合实际的意义,并不存在一个函数它把 B 构造了出来。从原型的角度看待就不存在这种问题,B 用了 A 当原型,A 用了 Function.prototype 当原型,仅此而已。