Search code examples
javascriptecmascript-6gulp

Instantiation and ordering of class causing bugs in ES6


I'm using ES6 classes inside my game. I have two questions:

Question 1) I'm instantiating two singleton classes for BattleMode and ExplorationMode and passing in an initial parameter type.

Even though I'm console.logging explorationMode (an instance of ExplorationMode), it shows Class BattleMode with type exploration. Why is this?

Question 2) I'm using gulp with babel to compile scripts into main.js. Both ExplorationMode and BattleMode above inherit from parent class Mode. Gulp runs through modes/ folder and adds them sequentially to main.js.

enter image description here

Apparently ES6 cares about the order of JavaScript, meaning if BattleMode is a child of Mode, then it will say it cannot find its parent.

Uncaught TypeError: Super expression must either be null or a function, not undefined

This forces renaming of mode.js such that it appears first in the folder order. That's pretty hacky. If I prepend A_mode, the error goes away.

enter image description here

Is there a better solution for this besides renaming or specifying each parent in gulp to appear before its children?

Otherwise, I could just use Objects:

var Mode = Mode || {};

Mode.BattleMode = Mode.BattleMode || {
    doThing : function () {
         ...

Question 1:

class StateManager {
    constructor(type) {
        if (!stateManagerInstance) {
            stateManagerInstance = this;
        }
        this.type = type;
        const battleMode = new BattleMode('battle');
        const explorationMode = new ExplorationMode('exploration');

        console.log('MODE: ', explorationMode); // Wrong
        ...

Console Output:

MODE: Class BattleMode { type: "exploration" , screenState: "battle" }

Mode:

/*
  Define as Singleton
 */
let modeInstance = null;

class Mode {
    constructor() {
        if (!modeInstance) {
            modeInstance = this;
        }

        return modeInstance;
  }

}

ExplorationMode:

/*
  Define as Singleton
 */
let explorationInstance = null;

class ExplorationMode extends Mode {
    constructor(type) {
        super(type);

    this.type = type;

        if (!explorationInstance) {
            explorationInstance = this;
        }

    this.screenState = '';

        return explorationInstance;
  }

BattleMode:

/*
  Define as Singleton
 */
let battleInstance = null;

class BattleMode extends Mode {
    constructor(type) {
        super(type);

    this.type = type;

        if (!battleInstance) {
            battleInstance = this;
        }

    this.screenState = '';

        return battleInstance;
  }

Question 2:

Gulp:

gulp.task('build-dev', function() {
    gulp.run('scripts');
  gulp.watch('js/**/*.js', function() {
    gulp.run('scripts');
  });
});

gulp.task('scripts', function() {
    return gulp.src([
            'js/**/*.js',
        ])
        .pipe(babel({
            presets: ['es2015']
        }))
        .pipe(concatJs('main.js'))
        .pipe(gulp.dest('build/js'));
});

Solution

  • This is an example why singleton can be antipattern. It provides extra complexity without real benefits. Children classes don't even benefit from inheritance.

    Since both BattleMode and ExplorationMode inherit from Mode, Mode constructor is evaluated first. The first instance is saved to modeInstance. Since BattleMode is instantiated first, it's an instance of BattleMode, no matter what happens in child constructors.

    In this case and many others singleton classes should just be replaced with plain objects.