Is it possible to inherit attributes and methods from one class to another?

As rabbits are animals,

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 class should be based on
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
9, have access to animal methods, so that rabbits can do what “generic” animals can do.

The syntax to extend another class is:

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
5.

Let’s create

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
2 that inherits from
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
9:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!

Object of

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 class have access both to
function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 methods, such as
class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
0, and also to
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
9 methods, such as
class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
2.

Internally,

class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
3 keyword works using the good old prototype mechanics. It sets
class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
4 to
class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
5. So, if a method is not found in
class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
6, JavaScript takes it from
class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
5.

Is it possible to inherit attributes and methods from one class to another?

For instance, to find

class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
8 method, the engine checks (bottom-up on the picture):

  1. The
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    9 object (has no
    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      run(speed) {
        this.speed = speed;
        alert(`${this.name} runs with speed ${this.speed}.`);
      }
    
      stop() {
        this.speed = 0;
        alert(`${this.name} stands still.`);
      }
    
    }
    
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    
      stop() {
        super.stop(); // call parent stop
        this.hide(); // and then hide
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
    0).
  2. Its prototype, that is
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    6 (has
    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      run(speed) {
        this.speed = speed;
        alert(`${this.name} runs with speed ${this.speed}.`);
      }
    
      stop() {
        this.speed = 0;
        alert(`${this.name} stands still.`);
      }
    
    }
    
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    
      stop() {
        super.stop(); // call parent stop
        this.hide(); // and then hide
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
    2, but not
    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      run(speed) {
        this.speed = speed;
        alert(`${this.name} runs with speed ${this.speed}.`);
      }
    
      stop() {
        this.speed = 0;
        alert(`${this.name} stands still.`);
      }
    
    }
    
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    
      stop() {
        super.stop(); // call parent stop
        this.hide(); // and then hide
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
    0).
  3. Its prototype, that is (due to
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    3)
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    5, that finally has the
    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      run(speed) {
        this.speed = speed;
        alert(`${this.name} runs with speed ${this.speed}.`);
      }
    
      stop() {
        this.speed = 0;
        alert(`${this.name} stands still.`);
      }
    
    }
    
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    
      stop() {
        super.stop(); // call parent stop
        this.hide(); // and then hide
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
    0 method.

As we can recall from the chapter Native prototypes, JavaScript itself uses prototypal inheritance for built-in objects. E.g.

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stands still.`);
  }

}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // call parent stop
    this.hide(); // and then hide
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
7 is
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stands still.`);
  }

}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // call parent stop
    this.hide(); // and then hide
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
8. That’s why dates have access to generic object methods.

Any expression is allowed after

class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
3

Class syntax allows to specify not just a class, but any expression after

class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
3.

For instance, a function call that generates the parent class:

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello

Here

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
1 inherits from the result of
class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
2.

That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them.

Overriding a method

Now let’s move forward and override a method. By default, all methods that are not specified in

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
2 are taken directly “as is” from
class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
4.

But if we specify our own method in

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3, such as
class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
6 then it will be used instead:

class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}

Usually, however, we don’t want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process.

Classes provide

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
7 keyword for that.

  • class Rabbit extends Animal {
      stop() {
        setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
      }
    }
    8 to call a parent method.
  • class Rabbit extends Animal {
      stop() {
        setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
      }
    }
    9 to call a parent constructor (inside our constructor only).

For instance, let our rabbit autohide when stopped:

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stands still.`);
  }

}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // call parent stop
    this.hide(); // and then hide
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stands still. White Rabbit hides!

Now

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 has the
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
1 method that calls the parent
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
2 in the process.

Arrow functions have no

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3

As was mentioned in the chapter Arrow functions revisited, arrow functions do not have

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3.

If accessed, it’s taken from the outer function. For instance:

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}

The

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3 in the arrow function is the same as in
class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
6, so it works as intended. If we specified a “regular” function here, there would be an error:

// Unexpected super
setTimeout(function() { super.stop() }, 1000);

Overriding constructor

With constructors it gets a little bit tricky.

Until now,

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 did not have its own
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
8.

According to the specification, if a class extends another class and has no

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
8, then the following “empty”
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
8 is generated:

class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}

As we can see, it basically calls the parent

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
8 passing it all the arguments. That happens if we don’t write a constructor of our own.

Now let’s add a custom constructor to

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3. It will specify the
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
3 in addition to
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
4:

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }

  // ...
}

// Doesn't work!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.

Whoops! We’ve got an error. Now we can’t create rabbits. What went wrong?

The short answer is:

  • Constructors in inheriting classes must call
    class Rabbit extends Animal {
      stop() {
        setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
      }
    }
    9, and (!) do it before using
    class Rabbit extends Animal {
      // generated for extending classes without own constructors
      constructor(...args) {
        super(...args);
      }
    }
    6.

…But why? What’s going on here? Indeed, the requirement seems strange.

Of course, there’s an explanation. Let’s get into details, so you’ll really understand what’s going on.

In JavaScript, there’s a distinction between a constructor function of an inheriting class (so-called “derived constructor”) and other functions. A derived constructor has a special internal property

class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
7. That’s a special internal label.

That label affects its behavior with

class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
8.

  • When a regular function is executed with
    class Rabbit extends Animal {
      // generated for extending classes without own constructors
      constructor(...args) {
        super(...args);
      }
    }
    8, it creates an empty object and assigns it to
    class Rabbit extends Animal {
      // generated for extending classes without own constructors
      constructor(...args) {
        super(...args);
      }
    }
    6.
  • But when a derived constructor runs, it doesn’t do this. It expects the parent constructor to do this job.

So a derived constructor must call

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3 in order to execute its parent (base) constructor, otherwise the object for
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6 won’t be created. And we’ll get an error.

For the

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 constructor to work, it needs to call
class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }

  // ...
}

// Doesn't work!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
4 before using
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6, like here:

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10

Overriding class fields: a tricky note

Advanced note

This note assumes you have a certain experience with classes, maybe in other programming languages.

It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often).

If you find it difficult to understand, just go on, continue reading, then return to it some time later.

We can override not only methods, but also class fields.

Although, there’s a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages.

Consider this example:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
0

Here, class

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 extends
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
9 and overrides the
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
4 field with its own value.

There’s no own constructor in

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3, so
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
9 constructor is called.

What’s interesting is that in both cases:

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
1 and
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
2, the
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
3 in the line
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
4 shows
function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
0.

In other words, the parent constructor always uses its own field value, not the overridden one.

What’s odd about it?

If it’s not clear yet, please compare with methods.

Here’s the same code, but instead of

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
6 field we call
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
7 method:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
1

Please note: now the output is different.

And that’s what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method.

…But for class fields it’s not so. As said, the parent constructor always uses the parent field.

Why is there a difference?

Well, the reason is the field initialization order. The class field is initialized:

  • Before constructor for the base class (that doesn’t extend anything),
  • Immediately after
    class Animal {
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
      // ...
    }
    
    class Rabbit extends Animal {
    
      constructor(name, earLength) {
        this.speed = 0;
        this.name = name;
        this.earLength = earLength;
      }
    
      // ...
    }
    
    // Doesn't work!
    let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
    4 for the derived class.

In our case,

function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 is the derived class. There’s no
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
00 in it. As said previously, that’s the same as if there was an empty constructor with only
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
01.

So,

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
2 calls
class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }

  // ...
}

// Doesn't work!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
4, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no
function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
3 class fields yet, that’s why
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
9 fields are used.

This subtle difference between fields and methods is specific to JavaScript.

Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what’s going on, so we’re explaining it here.

If it becomes a problem, one can fix it by using methods or getters/setters instead of fields.

Super: internals, [[HomeObject]]

Advanced information

If you’re reading the tutorial for the first time – this section may be skipped.

It’s about the internal mechanisms behind inheritance and

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3.

Let’s get a little deeper under the hood of

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3. We’ll see some interesting things along the way.

First to say, from all that we’ve learned till now, it’s impossible for

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3 to work at all!

Yeah, indeed, let’s ask ourselves, how it should technically work? When an object method runs, it gets the current object as

class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6. If we call
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
10 then, the engine needs to get the
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
11 from the prototype of the current object. But how?

The task may seem simple, but it isn’t. The engine knows the current object

class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6, so it could get the parent
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
11 as
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
14. Unfortunately, such a “naive” solution won’t work.

Let’s demonstrate the problem. Without classes, using plain objects for the sake of simplicity.

You may skip this part and go below to the

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 subsection if you don’t want to know the details. That won’t harm. Or read on if you’re interested in understanding things in-depth.

In the example below,

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
16. Now let’s try: in
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
17 we’ll call
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
18, using
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
19:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
2

At the line

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
4 we take
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
21 from the prototype (
function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
0) and call it in the context of the current object. Please note that
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
23 is important here, because a simple
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
24 would execute parent
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
21 in the context of the prototype, not the current object.

And in the code above it actually works as intended: we have the correct

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
3.

Now let’s add one more object to the chain. We’ll see how things break:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
3

The code doesn’t work anymore! We can see the error trying to call

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
27.

It may be not that obvious, but if we trace

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
27 call, then we can see why. In both lines
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
4 and
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
30 the value of
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6 is the current object (
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
32). That’s essential: all object methods get the current object as
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6, not a prototype or something.

So, in both lines

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
4 and
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
30 the value of
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
19 is exactly the same:
class Rabbit extends Animal {
  stop() {
    // ...now this will be used for rabbit.stop()
    // instead of stop() from class Animal
  }
}
9. They both call
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
38 without going up the chain in the endless loop.

Here’s the picture of what happens:

Is it possible to inherit attributes and methods from one class to another?

  1. Inside

    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    27, the line
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    30 calls
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    38 providing it with
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    42.

    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    4

  2. Then in the line

    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      // ...
    }
    
    class Rabbit extends Animal {
    
      constructor(name, earLength) {
        super(name);
        this.earLength = earLength;
      }
    
      // ...
    }
    
    // now fine
    let rabbit = new Rabbit("White Rabbit", 10);
    alert(rabbit.name); // White Rabbit
    alert(rabbit.earLength); // 10
    4 of
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    38, we’d like to pass the call even higher in the chain, but
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    42, so
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    46 is again
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    38!

    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    5

  3. …So

    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    38 calls itself in the endless loop, because it can’t ascend any further.

The problem can’t be solved by using

class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6 alone.

class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides!15

To provide the solution, JavaScript adds one more special internal property for functions:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15.

When a function is specified as a class or object method, its

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 property becomes that object.

Then

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3 uses it to resolve the parent prototype and its methods.

Let’s see how it works, first with plain objects:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
6

It works as intended, due to

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 mechanics. A method, such as
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
55, knows its
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 and takes the parent method from its prototype. Without any use of
class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6.

Methods are not “free”

As we’ve known before, generally functions are “free”, not bound to objects in JavaScript. So they can be copied between objects and called with another

class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}
6.

The very existence of

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 violates that principle, because methods remember their objects.
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 can’t be changed, so this bond is forever.

The only place in the language where

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 is used – is
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3. So, if a method does not use
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3, then we can still consider it free and copy between objects. But with
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3 things may go wrong.

Here’s the demo of a wrong

// Unexpected super
setTimeout(function() { super.stop() }, 1000);
3 result after copying:

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
7

A call to

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
66 shows “I’m an animal”. Definitely wrong.

The reason is simple:

  • In the line
    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      // ...
    }
    
    class Rabbit extends Animal {
    
      constructor(name, earLength) {
        super(name);
        this.earLength = earLength;
      }
    
      // ...
    }
    
    // now fine
    let rabbit = new Rabbit("White Rabbit", 10);
    alert(rabbit.name); // White Rabbit
    alert(rabbit.earLength); // 10
    4, the method
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    68 was copied from
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    9. Maybe we just wanted to avoid code duplication?
  • Its
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    15 is
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    9, as it was created in
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    9. There’s no way to change
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    15.
  • The code of
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    66 has
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} hides!`);
      }
    }
    
    let rabbit = new Rabbit("White Rabbit");
    
    rabbit.run(5); // White Rabbit runs with speed 5.
    rabbit.hide(); // White Rabbit hides!
    75 inside. It goes up from
    class Rabbit extends Animal {
      stop() {
        // ...now this will be used for rabbit.stop()
        // instead of stop() from class Animal
      }
    }
    9 and takes the method from
    function f(phrase) {
      return class {
        sayHi() { alert(phrase); }
      };
    }
    
    class User extends f("Hello") {}
    
    new User().sayHi(); // Hello
    0.

Here’s the diagram of what happens:

Is it possible to inherit attributes and methods from one class to another?

Methods, not function properties

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 is defined for methods both in classes and in plain objects. But for objects, methods must be specified exactly as
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
79, not as
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
80.

The difference may be non-essential for us, but it’s important for JavaScript.

In the example below a non-method syntax is used for comparison.

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
15 property is not set and the inheritance doesn’t work:

Can a class inherit from another class?

In the Java language, classes can be derived from other classes, thereby inheriting fields and methods from those classes. Definitions: A class that is derived from another class is called a subclass (also a derived class, extended class, or child class).

What is the method of inheriting the variable of one class to any other class?

Inheritance can be defined as the process where one class acquires the properties (methods and fields) of another. With the use of inheritance the information is made manageable in a hierarchical order.

Is a class from which attributes and methods can be inherited?

The class that inherits the properties/behavior of some other class is called the subclass while the class from whom properties/attributes are being inherited is called the superclass.