super
super
是一个 javascript 得关键字。也正是由于它是一个关键字,使得标准有机会为它得每一种用法单独定义行为,从而使它得用法与众不同。ECMA262 有专门的一节 The super keyword 来介绍 super 的用法。
superCall
在派生类(class Derived extends Base
)的构造里,可以通过 super(...)
的方式调用基类的构造函数。这个称作 superCall。在调用 superCall 之前,this
绑定并未初始化,不能使用 this
。superCall 会使用基类构造函数的“返回值”初始化当前的 this
。(这里的“返回值”,类似于 new Base(...)
的结果)
superProperty
在派生类的构造,或者成员函数中,可以使用 super[prop]
或者 super.property
形式,称作 superProperty。
superProperty 的行为跟其他的 A.B
的形式的结构表现很对相同。主要的 super.property
,super
不是一个对象,这不是一个取对象属性的表达。标准直接定义了 super.property
作为一个整体的含义。
Reference
在继续说 superProperty 之前,先要介绍一下 Reference,因为 A.B
或 superProperty 的结果都是一个 Reference。Reference 是一种标准内置类型。它用来表示标识符解析的结果,也就是说,在什么地方找到了某一个标识符。在需要取值的时候或赋值的时候,会通过 Reference 进行。
下面主要介绍 Reference
中结构中与,与 A.B
与 superProperty 相关的部分。
A.B
、superProperty 生成的 Reference 一般记录了以下几个信息:
-
base value: 这个标识符是在哪里找到的。它可以一个 Object, 基本类型的值,或者是一个环境(Environment Record),或者是
undefined
- 在
A.B
中,为A
。即将会从A
中查找属性"B"
。(注意此时不会检查对象中是否真的存在这个属性) - 在
super.B
中,base value 为 Object.getPrototypeOf([[HomeObject]])([[HomeObject]] 的原型对象。[[HomeObject]] 是啥一会介绍)
- 在
-
referenced name: 这是一个字符串。表示标识符的名字。
- 在
A.B
中,为"B"
- 在
- strict: 引用标识符的地点是否处于严格模式
- thisValue: 仅在
super.B
得到的 Reference 中存在,为super.B
所在环境的this
。
[[HomeObject]]
[[HomeObject]] 是专门为 superProperty 存在的。Object.getPrototypeOF([[HomeObject]]) 将成为 superProperty 结果 Reference 的 base value。
只有成员函数(包括 getter、setter)、contructor 才有 [[HomeObject]],其它函数没有 [[HomeObject]]。除箭头函数外,只有在有 [[HomeObject]] 的函数中,才可以使用 superProperty。对箭头函数来说,如果箭头函数定义所在的最内层非箭头函数有 [[HomeObject]],那么在箭头函数中也可以使用 superProperty,他们共享同一个 [[HomeObject]]。(也就是说,直接定义在类成员方法中的箭头函数中可以使用 superProperty。)
类
class
的定义是通过 ClassDefinitionEvaluation 算法处理的。它为 class 的每一个成员函数(包括 getter、setter)定义了 [[HomeObject]]。
对于 class Derived extends Base
,非 static
方法的 [[HomeObject]] 是 Derived.prototype
;static
方法的 [[HomeObject]] 是 Derived
。
根据类的定义,Object.getPrototypeOf(Derived.prototype) === Base.protorype
。当 extends Base
不存在时(基类定义),Object.getPrototypeOf(Derived.prototype) === Object.prototype
。也就是说,非 static
方法的 [[HomeObject]] 时当前类的原型对象,superProperty 的 base value 时其基类的原型对象。
Derived
本身时一个构造函数,根据类的定义,Object.getPrototypeOf(Derived) === Base
。当 extends Base
不存在时,Object.getPrototypeOf(Derived) == Function.prototype
。对于 static
方法,[[HomeObject]] 是当前类的构造函数,superProperty 的 base value 是基类的构造函数;没有基类时,是 Function.prototype
。
object literal
javascript 中的 Object literal 中以 MethodDefinition 形式直接定义的方法,也有 [[HomeObject]] 的定义。如下例中,method
中就有 [[HomeObject]] 定义。但是,non_method
对应的 function 并不是直接定义的,而是作为 non_method
属性的值,因而并没有 [[HomeObject]] 定义。
{method(){}
non_method : function(){}
}
MethodDefinition 的形式包括:
{method(...){...}
*generatorMethod(...){...}
async asyncMethod(...){...}
async *asyncGeneratorMethod(...){...}
get propertyName(){...}
set propertyName(...){...}
}
在 object literal 的构建算法,为成员方法指定了 [[HomeObject]]。这个 [[HomeObject]] 就是这个 object literal 本身。其原型对象(也就是 superProperty 的 base value)是 Object.prototype
。
取值
Reference 只记录了应该从何处去找到一个对象,并没有记录对象的值。取值的时候,需要到这个地方把只拿出来。这个操作,在 ECMA262 中称为 GetValue(V)(其中 V 是需要被取值的 Reference):
- ReturnIfAbrupt(V).
- If Type(V) is not Reference, return V.
- Let base be GetBase(V).
- If IsUnresolvableReference(V) is
true
, throw aReferenceError
exception.If IsPropertyReference(V) is
true
, then
If HasPrimitiveBase(V) is
true
, then
- Assert: In this case, base will never be
undefined
ornull
.- Set base to ! ToObject(base).
- Return ? base.[[Get]](GetReferencedName(V), GetThisValue(V)).
Else base must be an Environment Record,
- Return ? base.GetBindingValue(GetReferencedName(V), IsStrictReference(V)) (see 8.1.1).
与本文相关的是第 5 步,最终取值的结果是 base.[[Get]](GetReferencedName(V), GetThisValue(V))。
这个 base 就是 base value。[[Get]] 是取对象属性得内置方法。GetReferencedName(V) 返回 V 中记录得属性名 (referenced name)。GetThisValue(V) 返回 V 中记录得 thisValue (superProperty);如果 thisValue 不存在(不是 superProperty),GetThisValue(V) 将返回 V 中得 baseValue。
这里,superProperty 与普通的取对象属性并不相同,区别就在于 [[Get]] 的第二个参数。
[[Get]]
[[Get]] 是 javascript 对象的一个内置槽,不同的对象会不太相同。对普通的对象来说,它是如下定义的:
O.[[Get]](P, Receiver):
- Return ? OrdinaryGet(O, P, Receiver).
OrdinaryGet(O, P, Receiver):
- Assert: IsPropertyKey(P) is
true
.- Let desc be ? O.[[GetOwnProperty]](P).
If desc is
undefined
, then
- Let parent be ? O.[[GetPrototypeOf]]().
- If parent is
null
, returnundefined
.- Return ? parent.[[Get]](P, Receiver).
- If IsDataDescriptor(desc) is
true
, return desc.[[Value]].- Assert: IsAccessorDescriptor(desc) is
true
.- Let getter be desc.[[Get]].
- If getter is
undefined
, returnundefined
.- Return ? Call(getter, Receiver).
对比 GetValue,可以看到,OrdinaryGet 中的 O 与 Receiver,对普通对象属性,均为 base value;对 superProperty,分别是 base value 与 thisValue。
这里仅有第 8 步用到了 Receiver。也就是说,如果这个属性是一个 getter,那么 getter 函数中的 this
对 superProperty 来说将是 thisValue,而在普通对象属性中,将是 base value。
也就是说,在 superProperty 中,base value 提供了属性的定义,thisValue 提供了具体实现。这个定义如果是一个值,那么就直接返回了。这个定义如果是一个 getter,那么这个函数在 thisValue(具体实现)上执行。
在普通属性取值中,在经过 OrdinaryGet 第三步顺着原型链向上走,并递归调用到 OrdianryGet 时,O 与 Receiver 也不再相同。O 此时是原型对象,Receiver 依然是原始取属性的对象。这同样可以理解为原型对象提供了属性定义,而实际对象提供的属性的实现。
赋值
向 Reference V 赋值 W,是通过 PutValue(V, W) 实现的:
- ReturnIfAbrupt(V).
- ReturnIfAbrupt(W).
- If Type(V) is not Reference, throw a
ReferenceError
exception.- Let base be GetBase(V).
If IsUnresolvableReference(V) is
true
, then
If IsStrictReference(V) is
true
, then
- Throw a
ReferenceError
exception.- Let globalObj be GetGlobalObject().
- Return ? Set(globalObj, GetReferencedName(V), W,
false
).Else if IsPropertyReference(V) is
true
, then
If HasPrimitiveBase(V) is
true
, then1. Assert: In this case, *base* will never be `undefined` or `null`. 2. Set *base* to ! ToObject(*base*).
- Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
- If succeeded is
false
and IsStrictReference(V) istrue
, throw aTypeError
exception.- Return.
Else base must be an Environment Record,
- Return ? base.SetMutableBinding(GetReferencedName(V), W, IsStrictReference(V)) (see 8.1.1).
与本文有关的是第 6 步,它使用了 base value 的 [[Set]] 方法来设置属性值。
对普通对象来说,它是如下定义的:
O.[[Set]](P, V, Receiver):
- Return ? OrdinarySet(O, P, V, Receiver).
OrdinarySet(O, P, V, Receiver):
- Assert: IsPropertyKey(P) is true.
- Let ownDesc be ? O.[[GetOwnProperty]](P).
- Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc):
- Assert: IsPropertyKey(P) is true.
If ownDesc is
undefined
, then
- Let parent be ? O.[[GetPrototypeOf]]().
If parent is not
null
, then
- Return ? parent.[[Set]](P, V, Receiver).
Else,
- Set ownDesc to the PropertyDescriptor {[[Value]]:
undefined
, [[Writable]]:true
, [[Enumerable]]:true
, [[Configurable]]:true
}.If IsDataDescriptor(ownDesc) is
true
, then
- If ownDesc.[[Writable]] is
false
, returnfalse
.- If Type(Receiver) is not Object, return
false
.- Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
If existingDescriptor is not
undefined
, then
- If IsAccessorDescriptor(existingDescriptor) is
true
, returnfalse
.- If existingDescriptor.[[Writable]] is
false
, returnfalse
.- Let valueDesc be the PropertyDescriptor {[[Value]]: V }.
- Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
Else Receiver does not currently have a property P,
- Return ? CreateDataProperty(Receiver, P, V).
- Assert: IsAccessorDescriptor(ownDesc) is true.
- Let setter be ownDesc.[[Set]].
- If setter is
undefined
, returnfalse
.- Perform ? Call(setter, Receiver, « V »).
- Return
true
.
这,可以看到,O 依然只是提供了属性的定义。如果 O 中的属性不可写,哪个赋值将失败(3.1)。但是,实际写入时发生了 Receiver(实现)中的(3.4.4;3.5.1)。如果实现(Receiver)中已经存在了这个属性,那么 Receiver 中的属性也要可写,并且类型一致(4.1.1;4.1.2)。如果 O 的定义是一个 setter,那么已 Receiver(实现)为 this
调用 setter(7)。
对 superPreperty,O 最初为 base value,Receiver 为 thisValue。对普通属性来说,O 与 Receiver 本来时一致的。但是,经过 2.2.1 递归进入原型链之后,O 将为原型对象,但 Receiver 依然为原调用对象。
函数调用
当 Reference V 的值是一个函数,直接调用这个函数时,函数中的 this
绑定为 GetThisValue(V)(参见 EvaluateCall)。对 superProperty 来说,getThisValue(V) 的结果就是 SuperProperty 所在环境的 this
。
base value 与 thisValue
对于 superProperty,base value 提供了属性定义,thisValue 为实现。通常情况下(不去故意改变 this
绑定时),base value 会处于 thisValue 的原型链中。
对于 class Derived extends Base
的非 static
成员方法,this
通常为 new Derived
得到的对象,[[HomeObject]] 为 Derived.prototype
,base value 为 Object.getPrototypeOf(
[[HomeObject]]) === Object.getPrototypeOf(Dereived.prototype) === Base.prototype
。而 Object.getPrototypeOf(this) === Dreived.prototype
。即 Object.getPrototypeOf(Object.getPrototypeOf(
thisValue)) ===
base value;base value 是 thisValue 的原型对象的原型对象。
对 static
成员方法,this
通常为 Derived
;[[HomeObject]] 也时 Derived
。base value 时 [[HomeObject]] 的原型对象(Base
),也就是 thisValue 的原型对象。
对 object literal 内直接定义的方式,this
于 [[HomeObject]] 通常也是一致的,为 object 本身。从而,base value 为 thisValue 的原型对象。
this
绑定是在函数调时决定的。但是 base value([[HomeObject]])时在函数定义时就已经确定的。所以,如果在函数调用时通过各种途径,改变了 this
通常的取值,那么上述关系将不再成立。
小结
javascipt 中对对象属性的读写,都要通过原型链先找到属性的定义,然后根据定义在实际对象中读写。对普通对象属性来说,查找的起点与被读写的对象是同一个对象。
对于 superProperty,定义的查找是从基类的原型对象(类成员非 static
方法),或者 this
的原型对象(object literal,类 static
成员方法)开始的。而实际读写的对象都是 this
。由于查找是从原型对象开始的,所以“派生类”的属性是找不到的。派生类的实例中的属性也是找不到的。
可以认为,superProperty 只是改变的属性定义查找的起点,从而只有基类中的属性才可以被找到,但实际操作的对象依然为 this
。
-
取值的时候,只有定义在基类 / 原型对象中的属性可以被读取到。定义在派生类 / 类实例中的属性是不可见的。(会返回
undefined
)- 如果定义为 getter,会以当前的
this
来调用 getter。
- 如果定义为 getter,会以当前的
-
赋值的时候,值实际会被写入
this
。- 从而,对 superProperty 赋值之后再读取,其结果依然可能是
undefined
。 - 如果定义为 setter,会以当前的
this
来调用 setter。
- 从而,对 superProperty 赋值之后再读取,其结果依然可能是
- 直接函数调用,函数中的
this
绑定,为 superProperty 所处环境的this
。