skip to Main Content

I have the following JavaScript classes, where in the onClick method of the Lesson class the lessonObj property is NOT defined, though the init method of the Lesson class is correctly called. How do I have to change the code, so that it works?

Thanks

class Item
{
    name;

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

class Page
{
    pageObj;
    
    constructor()
    {
        this.init();
    }  

    init()
    {
        this.pageObj = new Item('John');
    }  
}

class Lesson extends Page
{
    lessonObj;
    
    constructor()
    {
        super();
    }  

    onClick(event)
    {
        alert(this.pageObj.name);
        alert(this.lessonObj.name);
    }

    init()
    {
        super.init();

        this.lessonObj = new Item('Mary');
        document.addEventListener('click', event => this.onClick(event));            
    }  
}

new Lesson();
Click somewhere

2

Answers


  1. Here is the correct code

    class Page {
        constructor() {
            this.init();
        }
        init() {} // void
    }
    
    class Lesson extends Page {
        constructor() {
            super();
            this.init(); // Call the init method of the Lesson class
        }
        init() {
            let that = this;
            $('body').on('custom-event', function(event) { that.onCustomEvent(event); });
        }
        onCustomEvent(event) {
            this.doSomething(); // 'this' is now defined
        }
    }
    
    
    Login or Signup to reply.
  2. It’s this line here:

    class Lesson extends Page
    {
        lessonObj; // <<<<<<<<<
        
        constructor()
        {
            super();
        }  
    

    That line basically adds a this.lessonObj = undefined to the constructor after the super() call but before any other code.

    So the overall execution order is this:

            class Page {
     5.         pageObj;
                
     4.         constructor() {
     5.             //this.pageObj = undefined;
     6.             this.init();
                }  
    
    11.         init() {
    12.             this.pageObj = new Item('John');
                }  
            }
    
            class Lesson extends Page {
    13.         lessonObj;
                
     2.         constructor() {
     3.             super();
    13.             //this.lessonObj = undefined;
                }  
    
    later       onClick(event) {
                    alert(this.pageObj.name);
                    alert(this.lessonObj.name);
                }
    
     7.         init() {
     8.             this.lessonObj = new Item('Mary');
     9.             document.addEventListener('click', event => this.onClick(event));
                    
    10.             super.init();
                }  
            }
    
     1.     new Lesson();
    

    so that this.lessonObj = new Item('Mary'); from init() is later overwritten with this.lessonObj = undefined; in the constructor().

    That’s why at the time you get into your click handler, this.lessonObj is empty.

    Here a rundown (in the correct order) of the code executed when you do const me = new Lesson();. I’ve commented out the function calls since I’ve inlined their content.

        const me = {};
        Object.setPrototypeOf(me, Lesson.prototype);
    //  Lesson.prototype.constructor.call(me);
    //    Page.prototype.constructor.call(me);
          me.pageObj = undefined;
    //    Lesson.prototype.init.call(me);
            me.lessonObj = new Item('Mary');
            document.addEventListener('click', event => me.onClick(event));
    //      Page.prototype.init.call(me);
              me.pageObj = new Item('John');
        me.lessonObj = undefined;
    

    What is now the best practice to solve that?

    There’s not the one solution for this.

    class Lesson extends Page {
      lessonObj;
    
      constructor() {
        super();
    
        this.lessonObj = new Item('Mary');
    
        document.addEventListener('click', event => this.onClick(event));
      }
    
      onClick(event) {
        alert(this.pageObj.name);
        alert(this.lessonObj.name);
      }
    }
    

    or even this

    class Page {
      pageObj = new Item('John');
    }
    
    class Lesson extends Page {
      lessonObj = new Item('Mary');
    
      constructor() {
        document.addEventListener('click', event => this.onClick(event));
      }
    
      onClick(event) {
        alert(this.pageObj.name);
        alert(this.lessonObj.name);
      }
    }
    
    new Lesson();
    

    or if you want to keep init() but want to ensure that init() is called after all the constructors, you could use a factory function:

    class Item
    {
        name;
    
        constructor(name)
        {
            this.name = name;
        }
    }
    
    class Page {
      static create() {
        // in here, `this` points to the current constructor()
        const instance = new this();
        instance.init();
        return instance;
      }
    
      pageObj;
    
      init() {
        this.pageObj = new Item('John');
      }
    }
    
    class Lesson extends Page {
      lessonObj;
    
      onClick(event) {
        console.log(this.pageObj.name);
        console.log(this.lessonObj.name);
      }
    
      init() {
        super.init();
    
        this.lessonObj = new Item('Mary');
        document.addEventListener('click', event => this.onClick(event));
      }
    }
    
    const instance = Lesson.create();
    console.log("instance", instance);

    Or you could take a fluid interface approach, let init() always return this and write something like

    class Item
    {
        name;
    
        constructor(name)
        {
            this.name = name;
        }
    }
    
    class Page {
      pageObj;
    
      init() {
        this.pageObj = new Item('John');
        return this;
      }
    }
    
    class Lesson extends Page {
      lessonObj;
    
      onClick(event) {
        console.log(this.pageObj.name);
        console.log(this.lessonObj.name);
      }
    
      init() {
        super.init();
    
        this.lessonObj = new Item('Mary');
        document.addEventListener('click', event => this.onClick(event));
        return this;
      }
    }
    
    const instance = new Lesson().init();
    console.log("instance", instance);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search