Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying all members of a class via an instance method

In JavaScript, is it possible to call an instance method on an object that affects all of its siblings?

For example, say I have the following class:

function Thing() {
  this.active = false;
}

Thing.prototype = {
  constructor: Thing,

  activate: function() {
    this.active = true;
  },
  deactivate: function() {
    this.active = false;
  }
  
};

Is it possible for me to create an activateAll method that can activate all of the instances of class Thing?

I need this.active to be an instance variable.

like image 223
Luke Avatar asked May 24 '15 23:05

Luke


1 Answers

You could store all the instances in an array, and iterate them to change their active property. However, storing all the instances means they won't be garbage collected, so you will waste memory. And if the array becomes huge, iterating it will be slow.

var Thing = (function() { // Use a closure to hide `instances`
  var instances = [];
  function Thing() {
    this.active = false;
    instances.push(this);
  }
  Thing.prototype.activate = function() {
    this.active = true;
  };
  Thing.prototype.activateAll = function() {
    instances.forEach(function(instance) {
      instance.active = true;
    });
  };
  return Thing;
})();

A better way would be defining common default activity and default priority, and an own activity and an own priority for each instance. Then,

  • To get the activity of an instance, compare the default priority and the own priority. The greatest decides whether the default activity or the own activity should be returned.
  • To set the activity of an instance, update the own activity. If the default activity is greater, increase the own priority.
  • To set the activity of all instances, update the default activity and increase the default priority.
var Thing = (function() {
    var defActive = false, /* default activity */
        defPriority = 0; /* default priority */
    /* `defPriority >= ownPriority` will always hold */
    function Thing() {
        var ownActive,
            ownPriority = -1;
        Object.defineProperty(this, 'active', {
            get: function() {
                return defPriority > ownPriority ? defActive : ownActive;
            },
            set: function(val) {
                ownActive = val;
                ownPriority = defPriority;
            }
        });
    }
    Thing.prototype.activate = function() {
        this.active = true;
    };
    Thing.prototype.activateAll = function() {
        defActive = true;
        ++defPriority;
    };
    return Thing;
})();

If activateAll will be called lots of times and you don't want to increase defPriority unnecessarily, you can add a boolean variable to know if some own property reached the default one.

var Thing = (function() {
  var defActive = false, /* default activity */
      defPriority = 0, /* default priority */
      someOwnPriorityReachedDefPriority = false;
  function Thing() {
    var ownActive,
        ownPriority = -1;
    Object.defineProperty(this, 'active', {
      get: function() {
        return defPriority > ownPriority ? defActive : ownActive;
      },
      set: function(val) {
        ownActive = val;
        ownPriority = defPriority;
        someOwnPriorityReachedDefPriority = true;
      }
    });
  }
  Thing.prototype.activate = function() {
    this.active = true;
  };
  Thing.prototype.activateAll = function() {
    defActive = true;
    if(someOwnPriorityReachedDefPriority) {
      ++defPriority;
      someOwnPriorityReachedDefPriority = false;
    }
  };
  return Thing;
})();

In any case, note that adding that method in the prototype doesn't make much sense. According to the OOP principles, calling a method in a variable should not affect other variables. Instead, consider adding the method to the constructor itself.

A full example could be like this:

var Thing = (function() {
  var defActive = false, /* default activity */
      defPriority = 0, /* default priority */
      someOwnPriorityReachedDefPriority = false;
  function Thing() {
    var ownActive,
        ownPriority = -1;
    Object.defineProperty(this, 'active', {
      get: function() {
        return defPriority > ownPriority ? defActive : ownActive;
      },
      set: function(val) {
        ownActive = val;
        ownPriority = defPriority;
        someOwnPriorityReachedDefPriority = true;
      }
    });
  }
  Thing.prototype.activate = function() {
    this.active = true;
  };
  Thing.prototype.deactivate = function() {
    this.active = false;
  };
  Object.defineProperty(Thing, 'activeAll', {
    set: function(val) {
      defActive = val;
      if(someOwnPriorityReachedDefPriority) {
        ++defPriority;
        someOwnPriorityReachedDefPriority = false;
      }
    }
  });
  Thing.activateAll = function() {
    Thing.activeAll = true;
  };
  Thing.deactivateAll = function() {
    Thing.activeAll = false;
  };
  return Thing;
})();
like image 141
Oriol Avatar answered Oct 21 '22 22:10

Oriol