Skip to content

Commit

Permalink
super methods and this binding (mdn#22324)
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Cena authored Nov 16, 2022
1 parent f979062 commit 4a11b35
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 22 deletions.
28 changes: 19 additions & 9 deletions files/en-us/web/javascript/reference/classes/constructor/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ class Square extends Polygon {

### Calling super in a constructor bound to a different prototype

Here the prototype of `Square` class is changed, but the constructor of its base class, `Polygon`, is still called when a new instance of a square is created. For more information on why, see [the `super` reference](/en-US/docs/Web/JavaScript/Reference/Operators/super#methods_that_read_super.prop_do_not_behave_differently_when_bound_to_other_objects).
`super()` calls the constructor that's the prototype of the current class. If you change the prototype of the current class itself, `super()` will call the constructor of the new prototype. Changing the prototype of the current class's `prototype` property doesn't affect which constructor `super()` calls.

```js
class Polygon {
Expand All @@ -241,23 +241,33 @@ class Polygon {
}
}

class Rectangle {
constructor() {
this.name = "Rectangle";
}
}

class Square extends Polygon {
constructor() {
super();
}
}

class Rectangle {}

// Make Square extend Rectangle (which is a base class) instead of Polygon
Object.setPrototypeOf(Square.prototype, Rectangle.prototype);

// Polygon is no longer part of Square's prototype chain
console.log(Square.prototype instanceof Polygon); // false
console.log(Square.prototype instanceof Rectangle); // true
Object.setPrototypeOf(Square, Rectangle);

const newInstance = new Square();
console.log(newInstance.name); // Polygon

// newInstance is still an instance of Polygon, because we didn't
// change the prototype of Square.prototype, so the prototype chain
// of newInstance is still
// newInstance --> Square.prototype --> Polygon.prototype
console.log(newInstance instanceof Polygon); // true
console.log(newInstance instanceof Rectangle); // false

// However, because super() calls Rectangle as constructor, the name property
// of newInstance is initialized with the logic in Rectangle
console.log(newInstance.name); // Rectangle
```

## Specifications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,22 +187,38 @@ class BaseClassWithPrivateStaticField {
static #PRIVATE_STATIC_FIELD;

static basePublicStaticMethod() {
this.#PRIVATE_STATIC_FIELD = 42;
return this.#PRIVATE_STATIC_FIELD;
}
}

class SubClass extends BaseClassWithPrivateStaticField {}

try {
SubClass.basePublicStaticMethod();
} catch (e) {
console.log(e);
// TypeError: Cannot write private member #PRIVATE_STATIC_FIELD
// to an object whose class did not declare it
SubClass.basePublicStaticMethod(); // TypeError: Cannot read private member #PRIVATE_STATIC_FIELD from an object whose class did not declare it
```

This is the same if you call the method with `super`, because [`super` methods are not called with the super class as `this`](/en-US/docs/Web/JavaScript/Reference/Operators/super#calling_methods_from_super).

```js
class BaseClassWithPrivateStaticField {
static #PRIVATE_STATIC_FIELD;

static basePublicStaticMethod() {
// When invoked through super, `this` still refers to Subclass
return this.#PRIVATE_STATIC_FIELD;
}
}

class SubClass extends BaseClassWithPrivateStaticField {
static callSuperBaseMethod() {
return super.basePublicStaticMethod();
}
}

SubClass.callSuperBaseMethod(); // TypeError: Cannot read private member #PRIVATE_STATIC_FIELD from an object whose class did not declare it
```

You are advised to always access static private fields through the class name, not through `this`, so inheritance doesn't break the method.

### Private methods

#### Private instance methods
Expand Down
32 changes: 26 additions & 6 deletions files/en-us/web/javascript/reference/operators/super/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The `super` keyword can be used in two ways: as a "function call" (`super(...arg
> };
> ```
In the constructor body of a derived class (with `extends`), the `super` keyword may appear as a "function call" (`super(...args)`), which must be called before the `this` keyword is used, and before the constructor returns. It calls the parent class's constructor and binds the parent class's public fields, after which the derived class's constructor can further access and modify `this`.
In the [constructor](/en-US/docs/Web/JavaScript/Reference/Classes/constructor) body of a derived class (with `extends`), the `super` keyword may appear as a "function call" (`super(...args)`), which must be called before the `this` keyword is used, and before the constructor returns. It calls the parent class's constructor and binds the parent class's public fields, after which the derived class's constructor can further access and modify `this`.
The "property lookup" form can be used to access methods and properties of an object literal's or class's [[Prototype]]. Within a class's body, the reference of `super` can be either the superclass's constructor itself, or the constructor's `prototype`, depending on whether the execution context is instance creation or class initialization. See the Examples section for more details.
Expand Down Expand Up @@ -139,9 +139,7 @@ Here, `extendedField` is `undefined` instead of 10, because `baseField` is defin

### Deleting super properties will throw an error

You cannot use the [delete operator](/en-US/docs/Web/JavaScript/Reference/Operators/delete) and
`super.prop` or `super[expr]` to delete a parent class' property,
it will throw a {{jsxref("ReferenceError")}}.
You cannot use the [`delete` operator](/en-US/docs/Web/JavaScript/Reference/Operators/delete) and `super.prop` or `super[expr]` to delete a parent class' property — it will throw a {{jsxref("ReferenceError")}}.

```js
class Base {
Expand All @@ -158,7 +156,7 @@ new Derived().delete(); // ReferenceError: invalid delete involving 'super'.

### Using super.prop in object literals

Super can also be used in the [object initializer / literal](/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) notation. In this example, two objects define a method. In the second object, `super` calls the first object's method. This works with the help of {{jsxref("Object.setPrototypeOf()")}} with which we are able to set the prototype of `obj2` to `obj1`, so that `super` is able to find `method1` on `obj1`.
Super can also be used in the [object initializer](/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) notation. In this example, two objects define a method. In the second object, `super` calls the first object's method. This works with the help of {{jsxref("Object.setPrototypeOf()")}} with which we are able to set the prototype of `obj2` to `obj1`, so that `super` is able to find `method1` on `obj1`.

```js
const obj1 = {
Expand Down Expand Up @@ -247,7 +245,29 @@ Object.setPrototypeOf(Extended, AnotherBase);
console.log(Extended.staticGetX()); // Now logs "4"
```

### Setting super.prop will set the property on this instead
### Calling methods from super

When calling `super.prop` as a function, the `this` value inside the `prop` function is the current `this`, not the object that `super` points to. For example, the `super.getName()` call logs `"Extended"`, despite the code looking like it's equivalent to `Base.getName()`.

```js
class Base {
static getName() {
console.log(this.name);
}
}

class Extended extends Base {
static getName() {
super.getName();
}
}

Extended.getName(); // Logs "Extended"
```

This is especially important when interacting with [static private properties](/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields#private_static_fields).

### Setting super.prop sets the property on this instead

Setting properties of `super`, such as `super.x = 1`, behaves like `Reflect.set(Object.getPrototypeOf(objectLiteral), "x", 1, this)`. This is one of the cases where understanding `super` as simply "reference of the prototype object" falls short, because it actually sets the property on `this` instead.

Expand Down
4 changes: 4 additions & 0 deletions files/en-us/web/javascript/reference/operators/this/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ console.log(o.a); // 38

In the second example (`C2`), because an object was returned during construction, the new object that `this` was bound to gets discarded. (This essentially makes the statement `this.a = 37;` dead code. It's not exactly dead because it gets executed, but it can be eliminated with no outside effects.)

#### super

When a function is invoked in the `super.method()` form, the `this` inside the `method` function is the same value as the `this` value around the `super.method()` call, and is generally not equal to the object that `super` refers to. This is because `super.method` is not an object member access like the ones above — it's a special syntax with different binding rules. For examples, see the [`super` reference](/en-US/docs/Web/JavaScript/Reference/Operators/super#calling_methods_from_super).

### Class context

A [class](/en-US/docs/Web/JavaScript/Reference/Classes) can be split into two contexts: static and instance. [Constructors](/en-US/docs/Web/JavaScript/Reference/Classes/constructor), methods, and instance field initializers ([public](/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields) or [private](/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields)) belong to the instance context. [Static](/en-US/docs/Web/JavaScript/Reference/Classes/static) methods, static field initializers, and [static initialization blocks](/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks) belong to the static context. The `this` value is different in each context.
Expand Down

0 comments on commit 4a11b35

Please sign in to comment.