Konrad Viltersten Konrad Viltersten - 4 months ago 13
Javascript Question

Proper way to handle scope in JavaScript

I have code that schematically follows the pattern below. The significant part is that there's a parameter declared in the outer function that is later used in the inner one.

function outer(){
var parameter = 3;
this.value = parameter;
$("#something").on("click", function(target){
target.value = parameter / 2;
});
}


In reality, the inner functions are quite a few and rather lengthy so I was hoping to move them out in order to improve readability, like so.

var parameter = 3;
function outer(){
this.value = parameter;
$("#something").on("click", inner);
}
function inner(target){
target.value = parameter / 2;
}


However, what I noticed is that, because of the scoping paradigm of JavaScript, I had to move out the parameter declaration out of the outer function, hence making it global, which to me seems like a disadvantage. (In reality, there are many parameters being used so passing them directly defeats the purpose.)

I can't decide which approach is less flawed. The question is if it's OK to pollute the global scope in order to gain readability or if it's an absolute no-no.

Answer

Re your revised question:

The question is if it's OK to pollute the global scope in order to gain readability or if it's an absolute no-no.

It's an absolute no-no. The global namespace is already far, far too polluted. And there's almost never any reason to do it: Instead, you can pollute use a scope that contains all of your code (near-global, if you will) as much as you want:

(function() {
    // Our private scope
    var lots, of, vars, here;

    function outer() {
        // ...
    }
    function inner() {
        // ...
    }
})();

That at least keeps your variables from truly being globals.

It's still best, though, to keep the scope of variables as constrained as possible. The cool thing is you can repeat the above as necessary to create mini-scopes for common code, nesting them like matryoshka.


Earlier answer before the question revision:

If the question is "what's better," the answer is: Neither. They each have their place, they're useful for different things.

If you're looking for a third solution, you can use partial application (sometimes called currying, though purists have an issue with that). JavaScript doesn't have a built-in partial application feature that doesn't mess with this, but you can easily add one:

(function() {
    // Note use of micro-scope here. It's a micro-optimiziation to avoid
    // looping up `slice` every time we need it. Mostly it's a justification
    // for demonstrating a micro-scope. :-)
    var slice = Array.prototype.slice;
    Object.defineProperty(Function.prototype, "curry", { // purists: sorry ;-)
      value: function() {
        var f = this;
        var args = slice.call(arguments, 0);
        return function() {
          return f.apply(this, args.concat(slice.call(arguments)));
        };
      }
    });
})();

Then

function outer(){
  var parameter = 3;
  this.value = parameter;
  $("#something").on("click", inner.curry(parameter));
}
function inner(parameter, target){
  target.value = parameter / 2;
}

You've mentioned that there may be a lot of these; if so, you might consider an object with properties instead, so you just curry the object reference rather than a lot of discrete variables.

Example:

    (function() {
      // Note use of micro-scope here. It's a micro-optimiziation to avoid
      // looping up `slice` every time we need it. Mostly it's a justification
      // for demonstrating a micro-scope. :-)
      var slice = Array.prototype.slice;
      Object.defineProperty(Function.prototype, "curry", { // purists: sorry ;-)
        value: function() {
          var f = this;
          var args = slice.call(arguments, 0);
          return function() {
            return f.apply(this, args.concat(slice.call(arguments)));
          };
        }
      });
    })();

    function outer() {
      var parameter = 3;
      setTimeout(inner.curry(parameter));
    }

    function inner(parameter) {
      console.log("Got parameter = " + parameter);
    }

    outer();