7

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.

Luke
  • 5,567
  • 4
  • 37
  • 66
  • This might help: http://stackoverflow.com/questions/1535631/static-variables-in-javascript – Max Meijer May 24 '15 at 23:52
  • It is possible, but you'd have to keep an array of all the live object instances somewhere that you could iterate and call a method on each object of that type. And, you may have a garbage collection problem if you aren't properly removing items from that array. – jfriend00 May 24 '15 at 23:54

2 Answers2

7

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;
})();
Oriol
  • 274,082
  • 63
  • 437
  • 513
1

You need an instances array, like this:

var instances = [];

Modify Thing as follows:

function Thing() {
  this.active = false;
  instances[instances.length] = this;
}

Implement methods by iterating:

function activateAll() {
    for (var index in instances) {
        instances[index].active = true;
    }
}

You can generalize this, like follows:

function genericOperation(f, p) {
    for (var index in instances) {
        instances[index].f(p);
    }
}
Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • It would be preferable for *instances* to be a property of *Thing*. Also, *for..in* iteration over Array members is not considered a good idea, better to use the built–in *forEach*. And there should be a way to remove members from *instances* too. – RobG May 25 '15 at 00:11
  • You can define it as a property of Thing. I did not really want to assume anything, the reader of this post should have the freedom of choice. In general it is good to be a property of Thing indeed. Removing elements from instances should be done as simple array element removals and a destructor or some cleaner methods are needed. I do not add these to the answer to keep it simple. – Lajos Arpad May 25 '15 at 00:22