Next: , Previous: Understanding Member Inheritance, Up: Inheritance


2.2.4 Overriding Methods

When a method is inherited, you have the option of either keeping the parent's implementation or overriding it to provide your own. When you override a method, you replace whatever functionality was defined by the parent. This concept was used to make our LazyDog lazy and our TwoLeggedDog walk on two legs in Figure 2.11.

After overriding a method, you may still want to invoke the parent's method. This allows you to augment the functionality rather than replacing it entirely. ease.js provides a magic __super() method to do this. This method is defined only for the overriding methods and calls the parent method that was overridden.

In order to demonstrate this, let's add an additional subtype to our hierarchy. AngryDog will be a subtype of LazyDog. Not only is this dog lazy, but he's rather moody.

    var AngryDog = Class( 'AngryDog' ).extend( LazyDog,
    {
        'public poke': function()
        {
            // augment the parent method
            console.log( 'Grrrrrr...' );

            // call the overridden method
            this.__super();
        }
    } );

    // poke a new AngryDog instance
    AngryDog().poke();

    // Output:
    // Grrrrrr...
    // Woof!

Figure 2.13: Using __super() method

If you remember from Figure 2.12, we added a poke() method to LazyDog. In Figure 2.13 above, we are overriding this method so that AngryDog growls when you poke him. However, we still want to invoke LazyDog's default behavior when he's poked, so we also call the __super() method. This will also make AngryDog bark like LazyDog.

It is important to note that __super() must be invoked like any other method. That is, if the overridden method requires arguments, you must pass them to __super(). This allows you to modify the argument list before it is sent to the overridden method.

2.2.4.1 Arbitrary Supertype Method Invocation

The aforementioned __super method satisfies invoking an overridden method within the context of the method that is overriding it, but falls short when needing to invoke an overridden method outside of that context.

As an example, consider that AngryDog also implemented a pokeWithDeliciousBone method, in which case we want to bypass the dog's angry tendencies and fall back to behaving like a LazyDog (the supertype). This poses a problem, as we have overridden LazyDog#poke, so calling this.poke would not yield the correct result (the dog would still respond angerly). __super cannot be used, because that would attempt to invoke a supermethod named pokeWithDeliciousBone; no such method even exists, so in this case, __super wouldn't even be defined.

We can remedy this using this.poke.super, which is a strict reference to the overridden poke method (in this case, LazyDog.poke):

    var AngryDog = Class( 'AngryDog' ).extend( LazyDog,
    {
        'public poke': function()
        {
            // ...
        },

        'public pokeWithDeliciousBone': function()
        {
            // invoke LazyDog.poke
            this.poke.super.call( this );
        }
    } );

    // poke a new AngryDog instance with a delicious bone
    AngryDog().pokeWithDeliciousBone();

    // Output:
    // Woof!

Figure 2.14: Using the method-supecific super reference

It is important to note that, in its current implementation, since super is a reference to a function, its context must be provided using the ECMAScript-native apply or call (the first argument being the context); using this as the context (as shown above) will invoke the method within the context of the calling instance.1


Footnotes

[1] Specifically, it will invoke the method within the context of the calling instance's private visibility object (see The Visibility Object). While this may seem like a bad idea—since it appears to give the supermethod access to our private state—note that the method wrapper for the overridden method will properly restore the private state of the supertype upon invocation.