ES6的类中有个constructor函数是用来当构造函数的,如果你不写这个函数,ES6规范中会按照一定的条件给你自动添加上,在规范的14.5.14章节中有这样的描述:
ClassTail : ClassHeritageopt { ClassBodyopt }
10. If constructor is empty, then,
a. If ClassHeritageopt is present, then
i. Let constructor be the result of parsing the source text constructor(... args){ super (...args);} using the syntactic grammar with the goal symbol MethodDefinition.
b. Else,
i. Let constructor be the result of parsing the source text constructor( ){ } using the syntactic grammar with the goal symbol MethodDefinition.
在有父类的子类默认constructor里,会先调用一把super(),这里为什么要这样写呢?可不可以不写这句话呢?一旦你在自己的类中声明了constructor方法,那么
我们就需要先弄清几点constructor的规则:
1)在constructor里,默认this是没有初始化的,如果在调用super()之前,就使用this,会报异常
2)this被初始化后,再次调用super()同样报错
3)constructor里没有调用super(),也没有访问this,同样报错。因为constructor默认返回this,而this仍处在未初始化状态
4)constructor只能返回对象类型或者undefined,返回其它类型直接报错。
4.1)如果返回对象类型,那么this可以不被初始化,也就是说可以不调用super()
下面我们用代码验证一下:
'use strict';
class Point{
}
class ColorPoint extends Point{
constructor(x){
this.x = 1;//Uncaught ReferenceError: this is not defined
}
}
var cp = new ColorPoint(1);
constructor(x){
super();
this.x = 1;
super();//Uncaught ReferenceError: this is not defined
}
constructor(x){//Uncaught ReferenceError: this is not defined
}
constructor(x){
super();
return 1;//Uncaught TypeError: Derived constructors may only return object or undefined
}
constructor(x){//OK!
return {};
}
至于上面的几点规则具体在规范中的哪些位置我也并没有都找到,惭愧。想要研究的可以从14.5章节入手。
为什么我们说不调用super方法会导致this未被初始化呢?我们需要查看一下super方法中到底干了什么事,在规范的12.3.5.3章节中:
SuperCall : super Arguments
1. Let newTarget be GetNewTarget().
2. If newTarget is undefined, throw a ReferenceError exception.
3. Let func be GetSuperConstructor().
4. ReturnIfAbrupt(func).
5. Let argList be ArgumentListEvaluation of Arguments.
6. ReturnIfAbrupt(argList).
7. Let result be Construct(func, argList, newTarget).
8. ReturnIfAbrupt(result).
9. Let thisER be GetThisEnvironment( ).
10. Return thisER.BindThisValue(result).
我们继续看BindThisValue,在8.1.1.3.1:
1. Let envRec be the function Environment Record for which the method was invoked.
2. Assert: envRec.[[thisBindingStatus]] is not "lexical".
3. If envRec.[[thisBindingStatus]] is "initialized", throw a ReferenceError exception.
4. Set envRec.[[thisValue]] to V.
5. Set envRec.[[thisBindingStatus]] to "initialized".
6. Return V.
这个流程就比较清楚了。this是super中被构造出来的,而不是在子类的constructor中先被构造出来的。这和以往我们的用法完全不一样:
function Point(){}
function ColorPoint(){//原来我们使用这种方法模拟继承,可以清楚的看出,this是先被构造出来的,然后在传给父类的构造函数
Point.call(this);
}
为什么ES6中改了this的构造过程呢?因为ES6中允许我们继承内置的类,如Array,Error等。如果this先被创建出来,在传给Array等系统内置类的构造函数,这些内置类的构造函数是不认这个this的:
function MyArray(len) {
Array.call(this, len); //this被忽略了
}
MyArray.prototype = Object.create(Array.prototype);
var myArr = new MyArray(0);
myArr.length // 0
myArr[0] = 'foo';
myArr.length//0
*以上全部代码在Chrome 47下通过测试