JavaScript-super

lxf2023-04-12 22:47:02

介绍

super关键字用于访问对象字面量或类的原型([[Prototype]])上的属性,或调用父类的构造函数。

super.propsuper[expr]表达式在类和对象字面量任何方法定义中都是有效的。super(...args)表达式在类的构造函数中有效。

语法

super([arguments]) // 调用父类的构造函数
super.propertyOnParent
super[expression]

描述

super关键字有两种使用方式:作为“函数调用”(super(...args)),或作为“属性查询”(super.propsuper[expr])。

super是一个关键字,并且有一些特殊的语法结构。super不是一个指向原型对象的变量。试图读取super本身会导致SyntaxError。

const child = { 
      myPrent() { 
         console.log(super); // SyntaxError: 'super' keyword unexpected here
      } 
};

在派生类的构造函数体中(使用extends),super关键字可以作为“函数调用”(super(...args))出现,它必须在使用this关键字之前和构造函数返回之前被调用。它调用父类的构造函数并绑定父类的公共字段,之后派生类的构造函数可以进一步访问和修改this

"属性查询"形式可以用来访问一个对象字面或类的[[Prototype]]的方法和属性,在一个类的主体中,super的引用可以是父类的构造函数本身,也可以是构造函数的prototype,这取决于执行环境是实例创建还是类的初始化。更多细节请参见示例部分。

注意,super的引用是由super声明的类或对象字面决定的,而不是方法被调用的对象。因此,取消绑定或重新绑定一个方法并不会改变其中super的引用(尽管它们会改变this的引用)。你可以把super看作是类或对象字面范围内的一个变量,这些方法在它上面创建了一个闭包。(但也要注意,它实际上并不是一个变量,正如上面所解释的那样)。

当通过super设置属性时,该属性将被设置在this上。

示例

在类中使用super

这里调用super()是为了避免重复在RectangleSquare的构造函数之间共同的部分。

class Polygon {
    constructor(height, width) {
        this.name = "Rectangle";
        this.height = hegiht;
        this.width = width;
    }
    sayName() {
        console.log('Hi, I am a ', this.name + '.');
    }
    get area() {
        return this.height * this.width;
    }
    set area(value) {
        this._area = value;
    }
}

class Square extends Polygon {
    constructor(length) {
        // this.height; ReferenceError,super 需要先被调用!
        
        // 这里,它调用父类的构造函数并传入length
        // 作为Polygon的height,width
        super(length,length);
        
        // 注意:在派生的类中,在你可以使用'this'之前,必须先调用(super)。
        // 现在可以使用'this'了,忽略'this'将导致引用错误(ReferenceError)
        this.name = 'Square';
    }
}

var a = new Square(10);
a.sayName(); // Hi, I am a Square.
a.area = 500;
console.log(a.area); // 100
console.log(a._area); // 500

调用父类上的静态方法

你也可以用super调用父类的静态方法

class Rectangle {
    static logNbSides() {
        return 'I have 4 sdies'; 
    }
}

class Square extends Rectangle {
    static logDescription() {
        return `${super.logNbSides()} which are all equal`;
    }
}

Square.logDescription(); // 'I have 4 sides which are all equal'

在类字段声明中访问super

super也可以在类字段初始化时被访问。super的引用取决于当前字段是一个实例字段还是静态字段。

class Base {
    static baseStaticField = 90;
    baseMethod() {
        return 10;
    }
}

class Extended extends Base {
    extendedField = super.baseMethod();
    static extendsedStaticField = super.baseStaticField;
}

console.log(Extended.extendedField); // undefined
console.log(Extended.extendsedStaticField); // 90

注意,实例字段是在实例上设置的,而不是构造函数的原型上,所以你不能用super来访问父类的实例字段

在上面的例子中,baseMethodundefined而不是10,因为baseMethod被定义为Base的实例的自有属性,而不是Base.prototype。在这种情况下,super只查找Base.prototype的属性,因为它是Extended.prototype的[[Prototype]]。

删除super上的属性将抛出异常

你不能使用delete操作符加super.prop或者super[expr]去删除父类的属性,这种做会抛出ReferenceError。

class Base {
    foo() {}
}
class Derived extends Base {
    delete() {
        delete super.foo; // 这很糟糕
    }
}

new Derived().delete(); // ReferenceError: invalid delete involving 'super'.

在对象字面量中使用super.prop

super也可以在对象初始化器/对象字面量符号中使用。在这个例子中,两个对象定义了一个方法。在第二个对象中,super调用第一个对象的方法。这是在Object.setPrototypeOf()的帮助下实现的,我们将obj2的原型设置为obj1,这样super就能够在obj1上找到method1。

const obj1 = {
    method1() {
        console.log('method 1');
    }
}

const obj2 = {
    method2() {
        super.method1();
    }
}
Object.setPrototypeOf(obj2, obj1);
obj2.method2(); // method 1

Object.setPrototypeOf():为现有对象设置原型,返回一个新对象 接收两个参数:第一个是现有对象,第二是原型对象。

读取super.prop的方法在绑定到其他对象时不会有不同的表现

访问super.x的行为类似于Reflect.get(Object.getPrototypeOf(objectLiteral), "x", this),这意味着该属性总是在对象字面/类声明的原型上寻找,取消绑定和重新绑定方法不会改变super的引用。

class Base {
    baseGetX() {
        return 1;
    }
}

class Extended extends Base {
    getX() {
        return super.baseGetX();
    }
}
const e = new Extended();
console.log(e.getX()); // 1
const { getX } = e;
console.log(getX()); // 1

同样的情况也发生在对象字面量中。

const parent1 = { prop: 1 };
const parent2 = { prop: 2 };
const child = {
    myParent() {
        console.log(super.prop)
    }
}
Object.setPrototypeOf(child, parent1);
child.myParent(); // 1
const myParent = child.myParent;
myParent(); // 1
const anotherChild = { __proto__: parent2, myParent };
anotherChild.myParent(); // 依然打印1

只有重设整个继承链才能改变super的引用。

class Base {
    baseGetX() { return 1 };
    static staticBaseGetX() { return 3 };
}
class AnotherBase {
    baseGetX() { return 2 };
    static staticBaseGetX() { return 4 };
}
class Extended extends Base {
    getX() { return super.baseGetX() };
    static staticGetX() { return super.staticBaseGetX(); }
}
const e = new Extended();
// 重置实例部分的继承=
Object.setPrototypeOf(Extended.prototype, AnotherBase.prototype);
console.log(g.getX()); // 打印2 而不是1 因为原型链已经改变
console.log(Extended.staticGetX()); // 依然打印3,因为我们还没有修改静态部分。
// 重置静态部分的继承
Object.setPrototypeOf(Extended, AnotherBase);
console.log(Extended.staticGetX()); // 现在打印4

设置super.prop将在此基础上设置属性

设置,super的属性,比如super.x = 1;,就像Reflect.set(Object.getprototypeOf(objectLiteral)), “x”, 1,this)的行为,这是一个将super简单理解为“原型对象的引用”的情况,因为它实际上是在this上设置属性。

class A {}
class B extends A {
    setX() {
        super.x = 1;
    }
}
const b = new B();
b.setX();
console.log(b); // { x: 1};
console.log(Object.hasOwn(b , 'X')); // true

super.x = 1将在A.protoype上寻找x的属性描述符(并调用那里定义的setter),但this的值将被设置为this,在这种情况下就是b

这意味着,虽然get super.prop的方法通常不会受到this上下文的影响,但set(super.prop)的方法却容易受到

// Reusing asme declarations as above
const b2 = new B();
b2.setX.call(null); // TtypeError: cannot assign to read only property 'x' of Object('null');

然而,super.x = 1仍然会查询原型对象的属性描述符,这意味着你不能重写不可写的部属性,而且setter会被调用

class X {
    constructor() {
        // Create a non-writable, property
        Object.defineProperty(this, 'prop', {
            configurable: true,
            writable: false,
            value: 1
        });
    }
}
class Y extends X {
    constructor(){
        super();
    }
    foo() {
        super.prop = 2;
    }
}
const y = new Y();
console.log(y.prop); // 1
y.foo(); // TypeError: 'prop' is read-only