Search code examples
javascriptclassoopinstance

JS nested prototype access parent node


According to following code, I have a small problem with accessing this variable in prototypes.

var MyClass = function(number) {
  this.number = number || 4;
};
MyClass.prototype = {
  run: function() {
    //direct access to object
    console.log(this.number);

    //access to object with the "self" object
    var self = this;
    setTimeout(function() {
      console.log(self.number);
    }, 1000);

    //access to object with the "self" object as a parameter
    this.events.test_a(self);

    //here is the problem
    this.events.test_b();
  },
  events: {
    test_a: function(self) {
      //access to object with the "self" object as a parameter
      console.log(self.number);
    },
    test_b: function() {
      console.log(this.number); // 👈 The problem
    }
  }
};

//----

var myClass = new MyClass(110);
myClass.run();


Is there any way to access the this object and having some structure like following?

myClass.events.test_b();

I need this👆 without using the instance that I've just created like following:👇

myClass.events.test_a(myClass);

Solution

  • In general, you're best off avoiding designing the structure that way.

    But you can do it by binding the events functions in the constructor, which means creating an "own" copy of the events object. See *** comments in this minimal-changes version:

    // NOTE: Sticking to ES5 as the OP seems to be doing that
    
    var MyClass = function(number) {
      this.number = number || 4;
      // *** Bind the functions on `this.events` to `this`
      var self = this;
      var events = self.events;
      self.events = {};
      Object.keys(events).forEach(function(key) {
        if (typeof events[key] === "function") {
          self.events[key] = events[key].bind(self);
        }
      });
    };
    // I've added the "name" parameter that's being passed around
    // so we can be sure that the results for multiple
    // instances are correct
    MyClass.prototype = {
      constructor: MyClass, // *** Don't break prototype.constructor
      run: function(name) {
        //direct access to object
        console.log(name, "Direct access in object:", this.number);
    
        //access to object with the "self" object
        var self = this;
        setTimeout(function() {
          console.log(name, "In setTimeout callback:", self.number);
        }, 1000);
    
        //access to object with the "self" object as a parameter
        this.events.test_a(name, self);
    
        //here is the problem
        this.events.test_b(name);
      },
      events: {
        test_a: function(name, self) {
          //access to object with the "self" object as a parameter
          console.log(name, "In test_a:", self.number);
        },
        test_b: function(name) {
          console.log(name, "In test_b:", this.number); // 👈 Not a problem anymore
        }
      }
    };
    
    //----
    
    var mc1 = new MyClass(110);
    var mc2 = new MyClass(220);
    setTimeout(function() {
        mc1.run("mc1");
    }, 1000);
    setTimeout(function() {
        mc2.run("mc2");
    }, 2000);
    .as-console-wrapper {
        max-height: 100% !important;
    }


    Side note: See this line I added to the object you're assigning to prototype:

    constructor: MyClass, // *** Don't break prototype.constructor
    

    By default, the prototype object on a function has a constructor property pointing back to the function, so best to do that.