tags:

views:

93

answers:

5

I'm writing some Javascript code and I need to keep track of a global state. The state is only ever changed by a single function, but it needs to be widely accessible from other functions.

I'm currently doing it in this manner:


function foo(bar) {
    var self = arguments.callee;
    if (!self.state) {
        self.state = 0; // called 0 times
    } else {
        self.state++; // increase the number of times i've been called
    }
}

Is it sensible to bind the global state variable to the function like this? It's nifty but I'm worried that there's something dangerous that I've missed.

+1  A: 

It's fine. People do it all the time with constructor functions. A built-in example would be String.fromCharCode

If however other functions don't need to reference the state, I would prefer doing the following:

var foo = (function () {
  var state = 0;
  return function (bar) {
    state++;
  };
}) ();

Even if they do need to reference it, I would probably try to make the above code work in some way. Or even use objects and avoid the singleton-like usage.

trinithis
+1  A: 

I've done things like this before. I think it is slightly messy, but it won't cause World War III.

Instead of doing this, could you define that function inside a closure, and have the 'state' variable local to that closure?

var foo = (function() {
  var state = 0;
  return function(bar) {
    state++;
  };
}());

foo(bar);
thomasrutter
The OP said that the state variable needs to be accessible from other functions. You could add a "getter" accessible via the inner function, but that would be messy.
Dan Breslau
I presumed that the return value of foo(bar) would be, or incorporate, the state. Note that neither the OP or my example show foo(bar) returning a value but you could imagine this.
thomasrutter
So you would need to invoke the function just to read the state? What if the function includes other side-effects (such as incrementing the call counter, just for starters)?
Dan Breslau
Like I said, that's what I presumed that the purpose of the function was. If not, you could I suppose define all the other functions that need to read this state in the same scope, or if you need a separate getter you could define and return that as part of an object.
thomasrutter
+1  A: 

It's fine, except that your state variable will never be > 0. Try this instead:

function foo(bar) {
    var self = arguments.callee;
    if (!self.state) { // Or, as trinithis suggested: if (self.state === undefined) {
        self.state = 1; // called once
    } else {
        self.state++; // increase the number of times i've been called
    }
}
Dan Breslau
Ah, good catch. That's not actually the code I'm using, but you've got sharp eyes.
Long Ouyang
Oh I see. I would instead prefer using `if (self.state === undefined)`.
trinithis
The point is that !self.state evaluates to 1 if self.state is 0, so you never get to the "else" part of the logic.
Long Ouyang
I think trinithis was suggesting that edit to my version of the function.
Dan Breslau
Yeah, sorry about that. I must have edited at the same time as Ouyang posted his comment.
trinithis
+2  A: 

There's nothing wrong with assigning properties to function objects. What you should worry about is that arguments.callee is deprecated in ECMAScript 5, which is the most recent language specification that's currently making its way into the mainstream browsers. In strict mode, referring to arguments.callee will give an error.

Tim Down
+1  A: 

Rather than a public field, you could make the counter read-only. Assuming you want to count calls to multiple functions, you could even define a utility method to wrap functions with this behaviour:

function countCallsToFunc(func) {
  var count = 0;
  var self = function() {
    count++;
    func.apply(this, arguments);
  };
  self.getCount = function() {
    return count;
  };
  return self;
}

var foo = countCallsToFunc(function(bar) {
  // do something
});

foo();
foo();
alert(foo.getCount());
Douglas