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

● ARCHIVED · READ-ONLY
Started by nio kasgami 113 posts Page 4 of 6 View original ↗
  1. That's not the same thing as redoing the entire source code to make it full ECS. For that to work, every element on the screen would have to be an object to which we attach component, like in Unity. I'm not saying we shouldn't borrow from that system when it serves us, I'm just saying that converting the entire engine just to achieve that is a huge project, not even counting the fact that the editor isn't adapted for that.

    As for ES5 vs ES6, I'll be using whatever MZ code is using for all my shared plugins. For my personal plugins for my projects that I won't be sharing I'll be using ES6 though.
  2. The ECS comment was just me talking about game engine design in general. Not saying it would be a good fit for RM’s general userbase, although it could’ve; Unity is very popular and easy to use and extend because of it, from a programmer/plugin maker perspective. To write plugins you already have to be quite an advanced user.
  3. This is my latest progress on trying to use ES6 class inheritance with aliasing but without directly typing prototypes for plugin developers:
    Spoiler
    JavaScript:
    /*
    * Do these 2 additional things when using ES6 class inheritance aliasing
    * without directly typing prototypes:
    * 1. Add the following code right below a new class inheriting another one:
    *    - ExtendedClassAlias.inherit(Klass);
    *    Where Klass is the new class inheriting another one
    * 2. Add the following code right below extending an existing class as a way
    *    to alias its methods:
    *    - ExtendedClassAlias.updateClass(Klass);
    *    Where Klass is the existing class being extended as a way to alias its
    *    methods
    * Right now it doesn't work well with inheriting static functions in classes,
    * so those in children classes should use ParentClass.staticFunc.call(this)
    * instead of super.staticFunc()
    */
    
    // Codes allowing ES6 class inheritance aliasing without direct prototyping
    class ExtendedClassAlias {
    
        static inherit(Child) {
            const childProto = Child.prototype;
            const parentName = Object.getPrototypeOf(childProto).constructor.name;
            this._inherit(Child, parentName);
        }
    
        static updateClass(Parent) {
            const parentName = Parent.prototype.constructor.name;
            // There's no need to update anything if the passed class's no children
            if (!this._inheritances.has(parentName)) return;
            this._updateClass(this._inheritances.get(parentName), Parent);
            //
        }
    
        static _inherit(Child, parentName) {
            // So the parent class will know which classes are its children
            if (this._inheritances.has(parentName)) {
                const oldChildProtos = this._inheritances.get(parentName);
                const newChildProtos = oldChildProtos.concat([Child]);
                this._inheritances.set(parentName, newChildProtos);
            } else {
                this._inheritances.set(parentName, [Child]);
            }
            //
        }
    
        static _updateClass(children, Parent) {
            this._updateProtoMethods(children, Parent.prototype);
            this._updateStaticFuncs(children, Parent);
        }
    
        static _updateProtoMethods(children, parentProto) {
            // So all the children will inherit the new rather than the old parent
            children.forEach(Child => Child.prototype.__proto__ = parentProto);
            //
        }
    
        static _updateStaticFuncs(children, Parent) {
            // So all children will inherit all new static functions from new parent
            Object.getOwnPropertyNames(Parent).forEach(name => {
                const desc = Object.getOwnPropertyDescriptor(Parent, name);
                if (!desc || typeof desc.value !== "function") return;
                children.forEach(Child => {
                    Child[name] = Child[name] || Parent[name];
                });
            });
            //
        }
    
    }
    ExtendedClassAlias._inheritances = new Map();
    //
    I've tested that it works for adding new instance variables and prototype methods in base classes, and extending and overriding existing prototype methods there(you can place the linked snippet into a JavaScript sandbox and verify the console output yourselves).
    While I failed to inherit the changes in the static functions of the base classes from plugins as well, this can be easily mitigated by using BaseClass.staticFunc.call(this) instead of super.staticFunc().

    Basically, the essence of the issue when aliasing ES6 class inheritance without direct prototyping is this:
    1. The original child class inherits the original base class
    2. A plugin extends the original base class to alias some of its prototype methods
    3. The child class still inherits the original base class
    So to solve this, simply store the linkage between the child class and the base class right after creating that child class, then points the parent of the child class to the extended base class right after extending it.

    As for the static functions in classes, while I tried to use the linkage to let the exiting children class use the new static functions from the extended parent class, I failed to cover the case for aliasing existing parent class static functions, because it's just impossible:
    1. The super in the static functions of the child class always points to the original parent class
    2. The super in the static functions of the extened parent class always points to the original parent class
    3. The super in the static functions of the child class's supposed to always point to the extended parent class
    Clearly, combining 1 and 2 will contradict with 3, which is the goal I've trying to achieve.

    For those not being familiar with ES5 or avoiding prototypes like a plague, I hope using ExtendedClassAlias won't be too demanding for you, as all you need to do is sticking to these:
    JavaScript:
    /*
    * Do these 2 additional things when using ES6 class inheritance aliasing
    * without directly typing prototypes:
    * 1. Add the following code right below a new class inheriting another one:
    *    - ExtendedClassAlias.inherit(Klass);
    *    Where Klass is the new class inheriting another one
    * 2. Add the following code right below extending an existing class as a way
    *    to alias its methods:
    *    - ExtendedClassAlias.updateClass(Klass);
    *    Where Klass is the existing class being extended as a way to alias its
    *    methods
    * Right now it doesn't work well with inheriting static functions in classes,
    * so those in children classes should use ParentClass.staticFunc.call(this)
    * instead of super.staticFunc()
    */

    P.S.: I've spent almost 24 hours on this and I enjoyed the process a lot, even though this might not be practical enough to be used in MZ :)
  4. I was already planning to just continue to use regular prototype aliasing if I ever get MZ, and reading through this thread hasn't done anything to change my mind. The only thing I saw that I might consider doing in the future is storing the prototype in a variable.

    JavaScript:
    var GA_proto = Game_Actor.prototype;
    var old_setup = GA_proto.setup;
    GA_proto.setup = function() {...}
  5. I have tried various methods to do stuffs with class since MV first released, decided to stick with prototype in most case. To be honest, except the class X extends Y being shorter, class doesn't offer any other advantage over prototype.

    I even have this kind of alias in my Luna Engine, so just use whatever you feel comfortable with

    Screenshot 2020-08-08 at 14.59.40.png
  6. I am not sure if this is correct place to post this... but I am worried about this ES6 hype.

    Examples:

    Arrow (shorthand) functions
    Arrow function will use same scope where it's called and 'old' function will use objects scope. For example Game_Map.prototype.eventsXy will scope 'this' object to current instance of Game_Map. On top of that you can't bind arrow function...

    MZ codebase at the moment doesn't use ES6 class structure
    So ES6 class aliasing isn't something to worry about to extend RMMZ base functions. Of course it is good to know how to do it - for example if you want to extend some other plugins that uses ES6 classes.

    Semicolons are not obsolete in ES6
    This is just my personal opinion, but JS code should be able to run in single line.
    Good article about JS semicolons today found here
  7. OcRam said:
    Arrow (shorthand) functions
    Arrow function will use same scope where it's called and 'old' function will use objects scope. For example Game_Map.prototype.eventsXy will scope 'this' object to current instance of Game_Map. On top of that you can't bind arrow function...
    I think arrow functions are best used for lambdas as arguments to higher-order functions, as they allow referencing the enclosing 'this' without an explicit bind. I don't think I'd use an arrow function anywhere else.

    (Note: by higher-order functions I mean things like reduce, map, filter, etc.)

    OcRam said:
    MZ codebase at the moment doesn't use ES6 class structure
    So ES6 class aliasing isn't something to worry about to extend RMMZ base functions. Of course it is good to know how to do it - for example if you want to extend some other plugins that uses ES6 classes.
    It's not something to worry about regardless of whether the MZ codebase uses ES6 class structure. ES6 classes are just a syntax sugar.

    One possible reason for them not to switch everything to ES6 style classes is the JsonEx serialization functions. In MV, at least, these functions won't be able to find the name of an ES6 class, which I assume will break the serialization. I don't know if that's any different in MZ, does anyone know?

    (The reason it won't work is because the serializer calls toString() on the constructor function and tries to extract the class name from that by looking for the "function" keyword... but a class declared in ES6 style won't even have the function keyword in its toString(). Fixing this is easy - look for either the "function" or "class" keyword - but I don't know if they would've done that. Or better still, don't call toString() at all and just do ClassName.name, which might even work for the "Class = function() {...}" style, though it can also fail if someone defines Class.name explicitly...)

    OcRam said:
    Semicolons are not obsolete in ES6
    This is just my personal opinion, but JS code should be able to run in single line.
    Good article about JS semicolons today found here
    The semicolon behaviour in JS is really bad, honestly. I'd actually prefer if they made them compulsory, because the automatic semicolon insertion sometimes produces strange results.
  8. I always omit semicolon in js/ts, it looks really ugly and isn't necessary.

    Edit: Most cases where semicolon is needed, the code is smelly. Also modern JS is not only revolve about classes and arrow functions. Tbh I use babel in many projects so I don't remember what added between ES version, but I think there are many features that some dev feel useful while the other doesn't.
  9. Solar_Flare said:
    One possible reason for them not to switch everything to ES6 style classes is the JsonEx serialization functions. In MV, at least, these functions won't be able to find the name of an ES6 class, which I assume will break the serialization. I don't know if that's any different in MZ, does anyone know?

    (The reason it won't work is because the serializer calls toString() on the constructor function and tries to extract the class name from that by looking for the "function" keyword... but a class declared in ES6 style won't even have the function keyword in its toString(). Fixing this is easy - look for either the "function" or "class" keyword - but I don't know if they would've done that. Or better still, don't call toString() at all and just do ClassName.name, which might even work for the "Class = function() {...}" style, though it can also fail if someone defines Class.name explicitly...)
    It's different in MZ:
    Code:
    JsonEx._encode = function(value, depth) {
        // [Note] The handling code for circular references in certain versions of
        //   MV has been removed because it was too complicated and expensive.
        if (depth >= this.maxDepth) {
            throw new Error("Object too deep");
        }
        const type = Object.prototype.toString.call(value);
        if (type === "[object Object]" || type === "[object Array]") {
            const constructorName = value.constructor.name;
            if (constructorName !== "Object" && constructorName !== "Array") {
                value["@"] = constructorName;
            }
            for (const key of Object.keys(value)) {
                value[key] = this._encode(value[key], depth + 1);
            }
        }
        return value;
    };
    
    JsonEx._decode = function(value) {
        const type = Object.prototype.toString.call(value);
        if (type === "[object Object]" || type === "[object Array]") {
            if (value["@"]) {
                const constructor = window[value["@"]];
                if (constructor) {
                    Object.setPrototypeOf(value, constructor.prototype);
                }
            }
            for (const key of Object.keys(value)) {
                value[key] = this._decode(value[key]);
            }
        }
        return value;
    };
    MZ doesn't look for the function keyword anymore :)
  10. Dr.Yami said:
    I always omit semicolon in js/ts, it looks really ugly and isn't necessary.

    Edit: Most cases where semicolon is needed, the code is smelly. Also modern JS is not only revolve about classes and arrow functions. Tbh I use babel in many projects so I don't remember what added between ES version, but I think there are many features that some dev feel useful while the other doesn't.
    I agree that they look ugly :)

    BUT omitting semicolons where they are needed may change intended execution behavior.

    For example below code block won't work because most JS interpreters thinks that bracket array is property of 0.
    Code:
    let i = 0
    [0, 1, 2, 3, 4, 5, 6].forEach(item => console.log(item))

    But if you add semicolon it will work - since we tell JS interpreter that it's end of declaration.
    Code:
    let i = 0;
    [0, 1, 2, 3, 4, 5, 6].forEach(item => console.log(item))
  11. OcRam said:
    I agree that they look ugly :)

    BUT omitting semicolons where they are needed may change intended execution behavior.

    For example below code block won't work because most JS interpreters thinks that bracket array is property of 0.
    Code:
    let i = 0
    [0, 1, 2, 3, 4, 5, 6].forEach(item => console.log(item))

    But if you add semicolon it will work - since we tell JS interpreter that it's end of declaration.
    Code:
    let i = 0;
    [0, 1, 2, 3, 4, 5, 6].forEach(item => console.log(item))
    I’ve omitted semicolons for over 4 years and never once ran into an issue or “gotcha”, not many people write code like the examples above and if you do then I guess now you know when to use semicolons lol. In fact you’ll likely run into more issues using semicolons than you will omitting them. Even the devs at NodeJS don’t use semicolons. But whatever works for you is what I say

    Edit: Also, to add about aliasing, I see no problem with using prototype to alias. I've been writing my plugins in ES6 for years now and I've come accustomed to seeing any classes I've edited within MV to be in prototype format and any of my new written classes are using class syntax.
    I personally would not focus so much on how to alias or trying to find new ways to alias and I would stick to the same ol' method and focus on coding lol, I mean the old method is easy and it makes sense. I have found many ways to avoid aliasing all together, while I still alias some methods, its minimal, you'd be surprised how often you can simply create a new method of your own or find a whole new way of doing something that never required aliasing to begin with.
  12. OcRam said:
    I agree that they look ugly :)

    BUT omitting semicolons where they are needed may change intended execution behavior.

    For example below code block won't work because most JS interpreters thinks that bracket array is property of 0.
    Code:
    let i = 0
    [0, 1, 2, 3, 4, 5, 6].forEach(item => console.log(item))

    But if you add semicolon it will work - since we tell JS interpreter that it's end of declaration.
    Code:
    let i = 0;
    [0, 1, 2, 3, 4, 5, 6].forEach(item => console.log(item))

    Yep, this and also anonymous function (function() {})(), but mostly people won't write this way. For your example, it's kind of magic array. But well, there might be some niche cases, I always have a tool to automatically fix those kind of expression, if stuffs happen, I just have to change the way to write so that semicolon can be omitted cuz like I said, the code where semicolon needed was often smelly.
  13. DoubleX said:
    It's different in MZ:
    MZ doesn't look for the function keyword anymore :)
    Ah, it's using the "name" property now, nice. :)
  14. LTN Games said:
    I’ve omitted semicolons for over 4 years and never once ran into an issue or “gotcha”, not many people write code like the examples above and if you do then I guess now you know when to use semicolons lol. In fact you’ll likely run into more issues using semicolons than you will omitting them. Even the devs at NodeJS don’t use semicolons. But whatever works for you is what I say

    Edit: Also, to add about aliasing, I see no problem with using prototype to alias. I've been writing my plugins in ES6 for years now and I've come accustomed to seeing any classes I've edited within MV to be in prototype format and any of my new written classes are using class syntax.
    I personally would not focus so much on how to alias or trying to find new ways to alias and I would stick to the same ol' method and focus on coding lol, I mean the old method is easy and it makes sense. I have found many ways to avoid aliasing all together, while I still alias some methods, its minimal, you'd be surprised how often you can simply create a new method of your own or find a whole new way of doing something that never required aliasing to begin with.
    Dr.Yami said:
    Yep, this and also anonymous function (function() {})(), but mostly people won't write this way. For your example, it's kind of magic array. But well, there might be some niche cases, I always have a tool to automatically fix those kind of expression, if stuffs happen, I just have to change the way to write so that semicolon can be omitted cuz like I said, the code where semicolon needed was often smelly.
    Why I'm worried about this is that one who is learning JS and thinks that semicolons aren't needed anymore might get confused.

    Previous example I gave was just one of the many cases. Not everyone writes code the way semicolons aren't needed or use babel / other tools.

    But here's 2 more examples:

    Code:
    let a = 1
    let b = a
    (a + b).toString() // Will give an error

    Or this is unexpected behavior IMO:
    Code:
    let a = 1
    -a + a === 0 ? console.log(0) : console.log(2)
    // Is it 0? NOPE without semicolons it will be 2!
    let a = 1;
    -a + a === 0 ? console.log(0) : console.log(2) // Now it will be 0

    But of course everyone writes the code that "floats their boat" - like I do my self (single line rulez! :rock-right:).
  15. Why I'm worried about this is that one who is learning JS and thinks that semicolons aren't needed anymore might get confused.
    Semicolons aren't needed, except for a few special cases, and those few special cases are easy to remember, possibly easier to remember than simply throwing semicolons everywhere or trying to get a newbie to understand all the places to put semicolons.
    I'd rather tell someone just learning about JS to not use semicolons, and the only time it would be an issue is when you start a line with any of these [, (, `, +, *, /, -, ,, ., and most people don't start lines like that anyway.

    In both examples both worked fine in the console, mind you I don't write code like that either, so for me to run into that issue is unlikely.
    1597005623085.png1597005659343.png

    I'm not saying you must not use semicolons! I'm just stating its unlikely anyone will run into an issue with omitting them, and as someone who has written without them for over 3 years and never once had a bug or problem because of it, I can say its pretty safe. Besides, look at everyone who omits semicolons

    Spoiler
    1597006213698.png
  16. OcRam said:
    Previous example I gave was just one of the many cases. Not everyone writes code the way semicolons aren't needed or use babel / other tools.
    I think we (or just me) get what you're saying. As someone who regularly switches from a language that does need semicolon and the one that doesn't, it isn't really a big deal. Sometimes, when writing JS, I have a habit of omitting it and putting it to feel safe. Most of the time when I omit it, it usually because I forgot. And when I do put them, I simply just want to explicitly put it as the end of the statement. I don't think semicolons are ugly.

    LTN Games said:
    I'd rather tell someone just learning about JS to not use semicolons, and the only time it would be an issue is when you start a line with any of these [, (, `, +, *, /, -, ,, ., and most people don't start lines like that anyway.
    Since it isn't needed, telling someone not to use a semicolon is not bad advice. In SQL, I discovered that you can use a semicolon to put an end to a query statement. But most of the time it is not really needed. Only in a specific case, you're required to put a semicolon to prevent query ambiguousness, that's it.

    You can do the same thing in JS of course. "If you want to write this kind of code, be sure to put a semicolon here".

    EDIT:
    Btw, it's really off topic
  17. LTN Games said:
    I'd rather tell someone just learning about JS to not use semicolons, and the only time it would be an issue is when you start a line with any of these [, (, `, +, *, /, -, ,, ., and most people don't start lines like that anyway.
    I would tend to disagree on your final point here. I think starting a line with [, (, or ` would actually be pretty common in some styles of code.

    LTN Games said:
    In both examples both worked fine in the console, mind you I don't write code like that either, so for me to run into that issue is unlikely.
    View attachment 154678View attachment 154679
    Well, of course that would work fine. Two lines in the console are not two lines in the same program / file. They are entirely separate scripts. If you pressed shift+enter in order to execute both lines in a single console call, I'm sure you'd see the issue.

    LTN Games said:
    I'm just stating its unlikely anyone will run into an issue with omitting them,
    While I don't think it's extremely likely, I think it's more likely than you believe. I recall running into an issue with them myself a couple of times.
  18. Solar_Flare said:
    I would tend to disagree on your final point here. I think starting a line with [, (, or ` would actually be pretty common in some styles of code.
    and if that is someone's style then just put a semicolon at beginning of the line and omit them everywhere else, its still preference and still possible to omit them and make a great codebase.


    Solar_Flare said:
    While I don't think it's extremely likely, I think it's more likely than you believe. I recall running into an issue with them myself a couple of times.
    Most times someone who runs into semicolons problems are already using semicolons everywhere else in the code. Like I said it can be an issue, but its pretty easy to remember where you need a semicolon, like the very beginning of an IIFFE or beginning of any line that starts with the above mentioned, if you remember that you will likely never have a problem.

    We could argue all day about semicolons, but my 3+ years experience without them is proof enough for me, so yea, I'm done with this, I also don't want to further derail this topic, sorry everyone. If you want to read further on semicolon, check out this fun article https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding

    Oh, fun little tidbit, creator of JS Brenden Eich also omits semicolons, butttttt he also made JS :stickytongue:
  19. LTN Games said:
    and if that is someone's style then just put a semicolon at beginning of the line and omit them everywhere else, its still preference and still possible to omit them and make a great codebase.



    Most times someone who runs into semicolons problems are already using semicolons everywhere else in the code. Like I said it can be an issue, but its pretty easy to remember where you need a semicolon, like the very beginning of an IIFFE or beginning of any line that starts with the above mentioned, if you remember that you will likely never have a problem.

    We could argue all day about semicolons, but my 3+ years experience without them is proof enough for me, so yea, I'm done with this, I also don't want to further derail this topic, sorry everyone. If you want to read further on semicolon, check out this fun article https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding

    Oh, fun little tidbit, creator of JS Brenden Eich also omits semicolons, butttttt he also made JS :stickytongue:
    This is also my final answer and sorry all for derailing... But it's important to change ideas and get some new perspectives.

    It was good article about understanding JS and totally agreed on everything he said (especially "If you don’t understand how statements in JavaScript are terminated, then you just don’t know JavaScript very well").
    BUT my point was that semicolons are not obsolete.

    And you can't run JS in single line if there are no semicolons to end statement - Not even JS leaders can do this (or any referenced institute/organization/individual)...
    Code:
    let i = 0 let j = 1 ... // Errors
    let i = 0; let j = 1; ... // No errors
    
    let obj = function {} obj.prototype.x = function () {} let y = 1 function b() {} let a = '' // Several errors
    let obj = function () {}; obj.prototype.x = function () {}; let y = 1; function b() {} let a = ''; // No errors...
    // Notice that for example function b() {} doesn't need semicolons even in single line mode

    I have been programmed with different languages all my life (now about 30 years and JS about 12 years) and I've seen these pit falls many times during this time... Just trying to help others here.
  20. JS uses automatic semicolon insertion anyway. As someone who is new to JS they will learn whether to use them or not.
    I think it's important to just understand edge cases. Also, as said above if you do want to write in a single line then go for putting semi colons between statements and expressions.