super

头像
fefe
    阅读 8 分钟
    3

    super

      1. superCall
      1. superProperty
      2. 2.1. Reference
      3. 2.2. [[HomeObject]]

        • 2.2.1. 类
        • 2.2.2. object literal
      4. 2.3. 从 Reference 中取值

        • 2.3.1. [[Get]]
      5. 2.4. 赋值
      6. 2.5. 函数调用
      7. 2.6. base value 与 thisValue
      8. 2.7. 小结

    注:标准引用基于 ECMA262 10.0

    super 是一个 javascript 得关键字。也正是由于它是一个关键字,使得标准有机会为它得每一种用法单独定义行为,从而使它得用法与众不同。ECMA262 有专门的一节 The super keyword 来介绍 super 的用法。

    1. superCall

    在派生类(class Derived extends Base)的构造里,可以通过 super(...) 的方式调用基类的构造函数。这个称作 superCall。在调用 superCall 之前,this 绑定并未初始化,不能使用 this 。superCall 会使用基类构造函数的“返回值”初始化当前的 this 。(这里的“返回值”,类似于 new Base(...) 的结果,而不是构造函数里的 return

    2. superProperty

    在派生类的构造,或者成员函数中,可以使用 super[prop] 或者 super.property 形式,称作 superProperty

    superProperty 的行为跟其他的 A.B 的形式的结构表现很不相同。主要是 super.property 中,super 不是一个对象,这不是一个取对象属性的表达。标准直接定义了 super.property 作为一个整体的含义。

    2.1. Reference

    在继续说 superProperty 之前,先要介绍一下 Reference ,因为 A.B 或 superProperty 的结果都是一个 Reference 。Reference 是一种标准内置类型。它用来表示标识符解析的结果,也就是说,在什么地方找到了某一个标识符。在需要取值的时候或赋值的时候,会通过 Reference 进行。

    下面主要介绍 Reference 中结构中,与 A.B 及 superProperty 相关的部分。

    A.B 、superProperty 生成的 Reference 一般记录了以下几个信息:

    1. base value: 这个标识符是在哪里找到的。它可以一个 Object, 基本类型的值,或者是一个环境(Environment Record),或者是 undefined

      • A.B 中,为 A。即将会从 A 中查找属性 "B"。(注意此时不会检查对象中是否真的存在这个属性)
      • super.B 中,base value 为 Object.getPrototypeOf([[HomeObject]])([[HomeObject]]的原型对象。[[HomeObject]]是啥一会介绍)
    2. referenced name: 这是一个字符串。表示标识符的名字。

      • A.B 中,为 "B"
    3. strict: 引用标识符的地点是否处于严格模式
    4. thisValue: 仅在 super.B 得到的 Reference 中存在,为 super.B 所在环境的 this

    2.2. [[HomeObject]]

    [[HomeObject]] 是专门为 superProperty 存在的。Object.getPrototypeOF([[HomeObject]]) 将成为 superProperty 结果 Reference 的 base value。

    只有成员函数(包括 getter、setter)、contructor 才有 [[HomeObject]] ,其它函数没有 [[HomeObject]] 。除箭头函数外,只有在有 [[HomeObject]] 的函数中,才可以使用 superProperty。对箭头函数来说,如果箭头函数定义所在的最内层非箭头函数有 [[HomeObject]] ,那么在箭头函数中也可以使用 superProperty ,他们共享同一个 [[HomeObject]]。(也就是说,直接定义在类成员方法中的箭头函数中可以使用 superProperty。)

    2.2.1. 类

    class 的定义是通过 ClassDefinitionEvaluation 算法处理的。它为 class 的每一个成员函数(包括 getter 、setter)定义了 [[HomeObject]] 。

    对于 class Derived extends Base ,非 static 方法的 [[HomeObject]] 是 Derived.prototypestatic 方法的 [[HomeObject]] 是 Derived

    根据类的定义,Object.getPrototypeOf(Derived.prototype) === Base.prototype 。当 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

    2.2.2. 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

    2.3. 从 Reference 中取值

    Reference 只记录了应该从何处去找到一个对象,并没有记录对象的值。取值的时候,需要到这个地方把值拿出来。这个操作,在 ECMA262 中称为 GetValue(V) (其中 V 是需要被取值的 Reference):

    1. ReturnIfAbrupt(V).
    2. If Type(V) is not Reference, return V.
    3. Let base be GetBase(V).
    4. If IsUnresolvableReference(V) is true, throw a ReferenceError exception.
    5. If IsPropertyReference(V) is true, then

      1. If HasPrimitiveBase(V) is true, then

        1. Assert: In this case, base will never be undefined or null.
        2. Set base to ! ToObject(base).
      2. Return ? base.[[Get]](GetReferencedName(V), GetThisValue(V)).
    6. Else base must be an Environment Record,

      1. 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]] 的第二个参数。

    2.3.1. [[Get]]

    [[Get]] 是 javascript 对象的一个内置槽,不同的对象会不太相同。对普通的对象来说,它是如下定义的:

    O.[[Get]](P, Receiver)

    1. Return ? OrdinaryGet(O, P, Receiver).

    OrdinaryGet(O, P, Receiver)

    1. Assert: IsPropertyKey(P) is true.
    2. Let desc be ? O.[[GetOwnProperty]](P).
    3. If desc is undefined, then

      1. Let parent be ? O.[[GetPrototypeOf]]().
      2. If parent is null, return undefined.
      3. Return ? parent.[[Get]](P, Receiver).
    4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
    5. Assert: IsAccessorDescriptor(desc) is true.
    6. Let getter be desc.[[Get]].
    7. If getter is undefined, return undefined.
    8. Return ? Call(getter, Receiver).

    对比 GetValue ,可以看到,OrdinaryGet 中的 OReceiver ,对普通对象属性,均为 base value ;对 superProperty ,分别是 base value 与 thisValue 。这个属性一定是从 O 中查找的。对于 superProperty 来说,就是从原型对象中查找这一个属性。

    这里仅有第 8 步用到了 Receiver 。也就是说,当查到到的属性是一个 getter ,那么 getter 函数中的 this 对 superProperty 来说将是 thisValue ,而在普通对象属性中,将是 base value 。

    也就是说,在 superProperty 中, base value 提供了属性的定义(getter 函数),thisValue 提供了具体实现(this)。这个定义如果是一个值,那么就直接返回了。这个定义如果是一个 getter ,那么这个 getter 在 thisValue (具体实现)上执行。

    在普通属性取值中,在经过 OrdinaryGet 第三步顺着原型链向上走,并递归调用到 OrdianryGet 时,OReceiver 也不再相同。O 此时是原型对象,Receiver 依然是原始取属性的对象。这同样可以理解为原型对象提供了属性定义,而实际对象提供的属性的实现。

    2.4. 赋值

    向 Reference V 赋值 W ,是通过 PutValue(V, W) 实现的:

    1. ReturnIfAbrupt(V).
    2. ReturnIfAbrupt(W).
    3. If Type(V) is not Reference, throw a ReferenceError exception.
    4. Let base be GetBase(V).
    5. If IsUnresolvableReference(V) is true, then

      1. If IsStrictReference(V) is true, then

        1. Throw a ReferenceError exception.
      2. Let globalObj be GetGlobalObject().
      3. Return ? Set(globalObj, GetReferencedName(V), W, false).
    6. Else if IsPropertyReference(V) is true, then

      1. If HasPrimitiveBase(V) is true, then

        1. Assert: In this case, base will never be undefined or null.
        2. Set base to ! ToObject(base).
      2. Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
      3. If succeeded is false and IsStrictReference(V) is true, throw a TypeError exception.
      4. Return.
    7. Else base must be an Environment Record,

      1. Return ? base.SetMutableBinding(GetReferencedName(V), W, IsStrictReference(V)) (see 8.1.1).

    与本文有关的是第 6 步,它使用了 base value 的 [[Set]] 方法来设置属性值。

    对普通对象来说,它是如下定义的:

    O.[[Set]](P, V, Receiver)

    1. Return ? OrdinarySet(O, P, V, Receiver).

    OrdinarySet(O, P, V, Receiver)

    1. Assert: IsPropertyKey(P) is true.
    2. Let ownDesc be ? O.[[GetOwnProperty]](P).
    3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).

    OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc)

    1. Assert: IsPropertyKey(P) is true.
    2. If ownDesc is undefined, then

      1. Let parent be ? O.[[GetPrototypeOf]]().
      2. If parent is not null, then

        1. Return ? parent.[[Set]](P, V, Receiver).
      3. Else,

        1. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
    3. If IsDataDescriptor(ownDesc) is true, then

      1. If ownDesc.[[Writable]] is false, return false.
      2. If Type(Receiver) is not Object, return false.
      3. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
      4. If existingDescriptor is not undefined, then

        1. If IsAccessorDescriptor(existingDescriptor) is true, return false.
        2. If existingDescriptor.[[Writable]] is false, return false.
        3. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
        4. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
      5. Else Receiver does not currently have a property P,

        1. Return ? CreateDataProperty(Receiver, P, V).
    4. Assert: IsAccessorDescriptor(ownDesc) is true.
    5. Let setter be ownDesc.[[Set]].
    6. If setter is undefined, return false.
    7. Perform ? Call(setter, Receiver, « V »).
    8. Return true.

    这里,可以看到,O 依然只是提供了属性的定义。如果 O 中的属性不可写(即属性定义为不可写),那么这个赋值将失败(3.1)。但是,实际写入时发生了 Receiver (实现)中的(3.4.4;3.5.1)。如果实现(Receiver)中已经存在了这个属性,那么 Receiver 中的属性也要可写,并且类型一致(3.4.1;3.4.2)。如果 O 的定义是一个 setter ,那么已 Receiver (实现)为 this 调用 setter (7)。

    对 superPreperty ,O 最初为 base value ,Receiver 为 thisValue 。对普通属性来说,OReceiver 本来是一致的。但是,经过 2.2.1 递归进入原型链之后,O 将为原型对象,但 Receiver 依然为原调用对象。

    2.5. 函数调用

    当 Reference V 的值是一个函数,直接调用这个函数时,函数中的 this 绑定为 GetThisValue(V) (参见 EvaluateCall)。对 superProperty 来说,getThisValue(V) 的结果就是 SuperProperty 所在环境的 this

    2.6. 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 通常的取值,那么上述关系将不再成立。

    2.7. 小结

    javascipt 中对对象属性的读写,都要通过原型链先找到属性的定义,然后根据定义在实际对象中读写。对普通对象属性来说,查找的起点与被读写的对象是同一个对象。

    对于 superProperty ,定义的查找是从基类的 prototype(类成员非 static 方法),或者 this 的原型对象(object literal,类 static 成员方法)开始的。而实际读写的对象都是 this 。由于查找是从原型对象开始的,所以“派生类”的属性是找不到的。派生类的实例中的属性也是找不到的。

    可以认为,superProperty 只是改变的属性定义查找的起点,从而只有基类中的属性才可以被找到,但实际操作的对象依然为 this

    1. 取值的时候,只有定义在基类/原型对象中的属性可以被读取到。定义在派生类/类实例中的属性是不可见的。(会返回 undefined

      • 如果定义为 getter ,会以当前的 this 来调用 getter 。
    2. 赋值的时候,值实际会被写入 this

      • 从而,对 superProperty 赋值之后再读取,其结果依然可能是 undefined
      • 如果定义为 setter ,会以当前的 this 来调用 setter 。
    3. 直接函数调用,函数中的 this 绑定,为 superProperty 所处环境的 this

    fefe
    18k 声望7.1k 粉丝