在 JavaScript 中,类和一般的构造器函数创建对象实例 this 的过程并不完全相同。
首先,函数的 prototype 属性是可修改的。当它被修改为 null 时,其子类对象是以 null 为原型的;当它被修改为非对象值时,其子类对象是以 Object.prototype 为原型的;而当它是一个对象类型的值时,其子类会使用该对象作为原型来创建实例。
对构造器函数使用 new 运算时,new 运算会使用该函数的 prototype 属性作为原型来创建一个 this 对象;然后调用该函数,并将其执行过程理解为类抄写(向用户实例抄写类所声明的属性)。
但这就带来了一个问题:无法创建一个有特殊性质的对象,也无法声明一个具有这类特殊性质的类。比如,在通过构造器函数的方式(即指定构造器函数的 prototype 属性)创建一个 Function 的子类的时候,其子类的实列虽然 ... instanceof Function 为 true,但却无法使用 () 调用。因为 JavaScript 的函数是一个有 [[Call]] 内部槽的对象,而 Function 作为 JavaScript 原生的函数构造器,它能够在创建的对象中添加这个内部槽。但当使用上面构造器函数的继承逻辑时,用户代码就只是创建了一个普通的对象,因为用户代码没有能力操作 JavaScript 引擎层面才支持的那些内部槽。所以在 ECMAScript 6 前,例如 Function、Date 等的类 / 构造器是不能派生子类的。
而 ECMAScript 6 后的类声明采用了不同的构造逻辑。如果类声明中通过 extends 指定了父类,那么:
1. 必须在构造器方法(constructor)中显式地使用 super() 来调用父类的构造过程;
2. 在上述调用结束之前,是不能使用 this 引用的。
而 this 的创建就通过层层的 super() 交给了父类或祖先类中支持创建这个实例的构造过程,这样子类中也能得到一个拥有父类所创建的带有内部槽的实例,Function、Date 等的子类也就可以实现了。简单地讲,ECMAScript 6 的类是由父类或祖先类创建 this 实例的。而如果类声明中不带有 extends 子句,那么它所创建出来的类与传统的构造器函数是一样的,也就是由自己来创建 this 对象。
----- 极客时间《JavaScript 核心原理解析》学习笔记 Day 15 -----