How to Extends / Alias / Overwrite existing class in MZ/ ES6 [Tutorial]

● ARCHIVED · READ-ONLY
Started by nio kasgami 113 posts Page 1 of 6 View original ↗
  1. Hi there!

    As MZ is now officially confirmed to be full ES6 us programmer can now use the ES6 javascript as the official javascript coding standard.

    ES6? what is the differences between ES5?
    Well here's a quick look at how class are defined in ES5 and ES6
    Code:
    // ES5 class
    
    function Scene_Dummy() { this.initialize.apply(this, arguments); }
    Scene_Dummy.prototype = Object.create(Scene_Base.prototype);
    Scene_Dummy.prototype.constructor = Scene_Dummy;
    
    Scene_Dummy.prototype.initialize = function(){
        Scene_Base.prototype.initialize.call(this);
    };
    
    Scene_Dummy.prototype.createFadeSprite = function(white){
        Scene_Base.prototype.createFadeSprite.call(this, white);
    };
    
    // ES6 class
    
    class Scene_Dummy extends Scene_Base {
    
        constructor(){
            super();
            this.initialize.apply(this,arguments); // should not be existing anymore for MZ but just in case
        }
    
        initialize(){
            super.initialize();
        }
    
        createFadeSprite(white){
            super.createFadeSprite(white);
        }
    }

    As you can see, it's make the code WAY more clean and shortened!
    Although now you might ask : Do I use the same syntax when I Overwrite, Alias or extends an existing class?
    The short answer is : Yes and no.

    the longer answer is No since Class in ES6 are considerate final and can't be redeclared
    so if you did
    Code:
    class Something {
    
    }
    
    class Something {
    
    }

    It would throw an error of duplicate member.
    So how do you actually extends existing class then???

    The short answer : The good old MV way!

    The long answer : ES6 is just syntactic sugar and in concept is still ES5 prototype class and still behave like ES5 just more 'stricter' and prettified.

    so here's the method to overwrite/ alias and extends an existing class!
    Code:
    // alias
    const alias = Scene_Base.prototype.createFadeSprite;
    Scene_Base.prototype.createFadeSprite = (white) => { // Just a shortend for 'function'
    alias.call(this, white);
    // new content
    };
    
    // Overwrite
    Scene_Base.prototype.createFadeSprite = (white) => { // Just a shortend for 'function'
    // new overwritten content
    };
    
    // Extends existing class.
    
    Scene_Base.prototype.newFunction = () => {
        // content
    };


    From @Galenmereth
    He showed me how to extends existing class using ES6 syntax thanks to him!
    Code:
    // named function.
    class Scene_Alias extends Scene_Base {
        create(){
        super.create();
        this.something();
        }
        
        something(){
        
        }
    }
    
    Scene_Base = Scene_Alias; // Will do the same thing than the es5 counterpart without having a global!
    
    // anonymous class for avoid nameclashing.
    Scene_Base = class extends Scene_Base {
      constructor(){
      super();
      }
     
      create(){
      super.create();
      this.something();
      }
     
      something(){
     
      }
    }


    EXTRA NOTE :

    Every function from ES5 will still work so you can still declare class like how it worked in MV!

    On That I hope it helped you and will remove any worries you had for MZ plugin development!
    Happy Game dev!


    On a quick note : I know it's very basic ES6 programming but for people using MZ they might google : how to extends function in MZ.
  2. nio kasgami said:
    so you can still declare class like how it worked in MV!
    Is this confirmed? I'm asking because the implementation of JsonEx._getConstructorName appears to depend on declaring the class in a certain way, and it would be good to know if the ES6 way works with that.

    If ES6 classes work with JsonEx in MV, that should be good enough as confirmation.

    (Personally, I don't care for ES6 classes. I suppose they're sort of a useful syntactic sugar, but if I recall correctly, they paper over the range of possibilities you can achieve with the old method.)
  3. I dont know if they would work in mv since the tutorial was catter for mz
  4. This is a very useful thread.

    I've stickied this thread for now.

  5. MushroomCake28 said:
    This is a very useful thread.
    [MOD]I've stickied this thread for now.[/MOD]
    thanks : D!
  6. From @Galenmereth
    He showed me how to extends existing class using ES6 syntax thanks to him!
    Code:
    // named function.
    class Scene_Alias extends Scene_Base {
        create(){
        super.create();
        this.something();
        }
        
        something(){
        
        }
    }
    
    Scene_Base = Scene_Alias; // Will do the same thing than the es5 counterpart without having a global!
    
    // anonymous class for avoid nameclashing.
    Scene_Base = class extends Scene_Base {
      constructor(){
      super();
      }
     
      create(){
      super.create();
      this.something();
      }
     
      something(){
     
      }
    }

    thanks again Galen!
  7. So basically nothing's changed? lol
  8. Tsukihime said:
    So basically nothing's changed? lol
    nope! for extending class you can either go ES5 prototype or you can do the Galenmereth technique.
    but many people who begin javascript will try to redeclare the class lol.
  9. nio kasgami said:
    nope! for extending class you can either go ES5 prototype or you can do the Galenmereth technique.
    but many people who begin javascript will try to redeclare the class lol.

    How do I alias a method with the new syntax?

    Code:
    class MyClass {
       something() {
          return 1;
       }
    }
    
    // new plugin
    MyClass = class extends MyClass {
      // ???? 
      something() {
         // return previous value + 2?
     }
    }
  10. with the (super) call since it's work the same as '.call' from the ES5

    in simple super.method(arguments) = Alias.call(this,arguments)

    @Tsukihime
  11. nio kasgami said:
    with the (super) call since it's work the same as '.call' from the ES5

    in simple super.method(arguments) = Alias.call(this,arguments)

    @Tsukihime

    I tried it and it works lol
    But kind of strange that it works. It's a strange place to use "super" since by design there isn't a MyClass parent. I'd probably get really confused if I see the super there.

    Anyways so I tried something else

    Code:
    class MyClass {
       something() {
          return 1;
       }
    }
    
    class Sub extends MyClass {
      something() {
        return super.something();
      }
    }
    
    a = new Sub();
    console.log(a.something()) // prints 1
    
    MyClass = class extends MyClass {
      something() {
       return super.something() + 2;
    }
    }
    
    console.log(a.something()) // prints 1
    a = new Sub();
    console.log(a.something()) // still prints 1???

    The first print will say "1" because sub inherits from MyClass. Which is expected.

    But then when I change MyClass, sub doesn't inherit the change.

    And when I create a new Sub, thinking maybe it's using the old methods, it STILL prints 1???
    This is going to be problematic I think.
  12. Tsukihime said:
    I tried it and it works lol
    But kind of strange that it works. It's a strange place to use "super" since by design there isn't a MyClass parent. I'd probably get really confused if I see the super there.

    Anyways so I tried something else

    Code:
    class MyClass {
       something() {
          return 1;
       }
    }
    
    class Sub extends MyClass {
      something() {
        return super.something();
      }
    }
    
    a = new Sub();
    console.log(a.something()) // prints 1
    
    MyClass = class extends MyClass {
      something() {
       return super.something() + 2;
    }
    }
    
    console.log(a.something()) // prints 1
    a = new Sub();
    console.log(a.something()) // still prints 1???

    The first print will say "1" because sub inherits from MyClass. Which is expected.

    But then when I change MyClass, sub doesn't inherit the change.

    And when I create a new Sub, thinking maybe it's using the old methods, it STILL prints 1???
    This is going to be problematic I think.
    I see I will ping @Galenmereth about this.
  13. People over on the Japanese forum are attempting to work out the issues, as well (you are mentioned @nio kasgami). Someone ran into the same issue. One of the offered solutions used the following :

    Code:
    class MyClass {
       something() {
          return 1;
       }
    }
    
    class Sub extends MyClass {
      something() {
        return super.something();
      }
    }
    
    var a = new Sub();
    
    console.log(a.something()); // 1
    
    const Old_Something = MyClass.prototype.something;
    
    MyClass.prototype.something = function () {
      return Old_Something.call(this) + 2;
    }
    
    console.log(a.something()); // 3
    
    var a = new Sub();
    
    console.log(a.something()); // 3



    Or you could use ...



    Code:
    class MyClass {
       something() {
          return 1;
       }
    }
    
    class Sub extends MyClass {
      something() {
        return super.something();
      }
    }
    
    a = new Sub();
    
    console.log(a.something()); // 1
    
    const Old_Something = MyClass.prototype.something;
    
    const somethingMixin = {
      something () {
        return Old_Something.call(this) + 2;
      }
    }
    
    Object.assign(MyClass.prototype, somethingMixin);
    
    console.log(a.something()); // 3
    
    a = new Sub();
    
    console.log(a.something()); // 3


    Eeh ... none of the solutions looks 'sexy'. I kind of wish someone on the developer team would come forward. Surely someone has created a few default plugins.
  14. yeah this is why I am asking galenmereth about it.
  15. I went back to ruby to see how it looks

    Code:
    # Base Case
    class MyClass
      def number
        return 1
      end
    end
    
    class Sub < MyClass
      def number
        return super * 2
      end
    end
    
    a = Sub.new
    p a.number # 2, because 1 * 2
    
    # Common aliasing that we do
    class MyClass
    
      alias :old_number :number
      def number
        return old_number + 100
      end
    end
    
    p a.number # 202, now (1 + 100) * 2

    So good. So clean. This is the ideal syntax but of course javascript is going to throw wrenches at us.

    I guess the issue is related to extend and how it affects the original class' prototype.
    When we say something like

    Code:
    MyClass = class extend MyClass

    We create a new MyClass prototype, but the sub class is still pointing to the OLD MyClass prototype.
  16. So the problem is that in these examples, class `Sub` has already extended class `MyClass`. Even if you change the definition of `MyClass` after the fact, `Sub` doesn't change because it's still extended the original `MyClass` from where it was parsed. The file is parsed top to bottom; at the time when `Sub` was extending `MyClass` is the snapshot it will retain, regardless of whether `MyClass` is then changed later. The only solution to that is to use prototype overrides and ignore inheritance, which isn't what we do today.

    If you want to keep changing and extending `MyClass` in the same manner that we do with prototype today in MV's style, it looks like this with the class syntax:

    Code:
    class A {
        print() {
          return "A";
      }
    }
    
    let a = new A();
    
    console.log("Before extend", a.print()); // Prints "A"
    
    A = class extends A {
        print() {
          return super.print() + "B";
      }
    }
    
    a = new A();
    
    console.log("First extend", a.print()); // Prints "AB"
    
    A = class extends A {
        print() {
          return "C" + super.print();
      }
    }
    
    a = new A();
    
    console.log("Latest extend that prepends to print()", a.print()); // Prints "CAB

    Imagine in each of the two extending cases, it's a different plugin doing it. Just like today, if a plugin later extends the prototype method of an object, if any plugin has done so before that, you'd be handed that plugin's extended prototype method as your "super" when calling it.

    If you wanted to make a separate class that extends class `A` in your plugin, that would still work alongside overrides from other plugins.

    @Tsukihime The example above is practically doing exactly what's happening in the ruby code, except you don't have to type out the alias; `super.number()` does that for you :)
  17. Galenmereth said:
    So the problem is that in these examples, class `Sub` has already extended class `MyClass`. Even if you change the definition of `MyClass` after the fact, `Sub` doesn't change because it's still extended the original `MyClass` from where it was parsed. The file is parsed top to bottom; at the time when `Sub` was extending `MyClass` is the snapshot it will retain, regardless of whether `MyClass` is then changed later. The only solution to that is to use prototype overrides and ignore inheritance, which isn't what we do today.

    Ya that's mostly the issue.
    In the sub example, I would want changes to the parent's methods to be inherited as well.

    Code:
    Parent: return 1
    Child: Return parent's value * 2, // so we get 2
    Parent: return 2
    Child: Still thinks parent is returning 1. // we still get 2, instead of 4

    So basically the extend syntax won't help here, and we'd have to go back to prototype aliasing.

    Code:
    var old_method = method
    Parent.prototype.method = function() {
       return 2;
    }

    Which isn't really too bad I mean I kind of liked not having to type "prototype" so much lol but if there's no option then we'd have to just settle with the old way.
  18. Code:
    // I added missing "Parent.prototype.method" syntax here
    var old_method = Parent.prototype.method;
    Parent.prototype.method = function() {
       return 2;
    }
    
    // The following is effectively the same as the above in terms of how the JS engine eventually compiles the code
    
    Parent = class extends Parent {
        method() {
            return 2;
        }
    }

    The only difference is you saved a ref to the original prototype method, however any other plugin would never see that and have access to it anyway. You could've done the same in the class extends example, though, as the class extend syntax is still prototype inheritance; they're interchangeable, as we've seen, but the latter is cleaner.
  19. @Galenmereth

    Hmm. What about this:

    Code:
    // our base soldier class
    class Pawn {
      get hp() {
        return 100;
      }
    }
    
    // some special unit with twice the HP
    class Tank extends Pawn {
      get hp() {
        return super.hp * 2
      }
    }
    
    console.log(new Pawn().hp) // 100
    console.log(new Tank().hp) // 200. As expected
    
    // NEW PLUGIN that gives all of our units 1000 HP bonus!
    Pawn = class extends Pawn {
      get hp() {
        return super.hp + 1000
      }
    }
    
    console.log(new Pawn().hp) // 1100. Works well.
    console.log(new Tank().hp) // still 200. Doesn't apply new logic

    Is there a way to get the tank's HP to reflect the huge boost that the pawn got?
  20. I'm following this, but @Galenmereth will you mind removing the javascript syntax color in your code tag? Because it looks horrible in the dark theme and I don't think this will get fixed.
    Screenshot_23.jpg

    EDIT:
    @Tsukihime the only thing I could think of it just the load order issue.
    The new plugin to give the pawn HP boost must be placed above the tank plugin.

    I'm still thinking that aliasing the prototype function and replacing it still more robust than ES6 syntax somehow.