Tuesday, December 1, 2015

JavaScript: editing the built-ins

Whilst working on my newest project Jolf, I found the need to “edit” the built-ins of JavaScript. What does that mean? I wanted to edit the alert function so that it would also apply JSON.stringify on the input. How can I do this? Abusing JavaScript closures, of course!

JavaScript is interesting in that it really doesn't care. For the record. JavaScript doesn't care what you do with it. It grumbles only when it cannot possibly complete the action. Other than arguing with you when you use undefined variables (or make a mistake with variables at all) and when you completely garble the text you type, JavaScript rolls with it. That is why the aim of the post is even feasible.

Now, let's do a simple example. Let's fix the abomination of the isNaN function. One would expect that this function would return true if and only the input is NaN; however, this function tries first to evaluate the input as a number, then detecting if the result is NaN. So, isNaN(30) => false, but isNaN("Now in 3d!") => true. We want it so that isNaN(x) iff x is NaN. How? Observe that typeof NaN === "number". So, we check for a number type first, then call the old isNaN function, like so:

(function(oldNaNTest){
  return isNaN = function(inputNum){
    if(typeof inputNum!=="number") return false;  // NaN is a number
    else return oldNaNTest(inputNum);
  }
})(isNaN);

This creates an anonymous function with isNaN as an argument. And, as an argument, oldNaNTest is it's own, “separate” function (i.e. being distinct from `isNaN` and not merely a reference), so it retains `isNaN`s value. We do a type-check on the input, and proceed with returning the correct results.

But why an anonymous function? Well, consider what might happen if it wasn't. We would use something like this:

isNaN = function(inputNum){
  if(typeof inputNum!=="number") return false;
  else return isNaN(inputNum);
}

All would be well with non-numbers, as they return false; however, when given a number, we look to the else statement for our instruction. And what are we pointed to? Yep, isNaN. Is this wrong? Oh good heavens, yes it is! JavaScript looks at this statement and thinks, now, I need to look at the scope for a function named isNaN. Does that exist…yes! I found one! Here, take it! JavaScript hands you the function you just made. Why? Because you overwrote the old one, silly. We could create a reference, say, oldNaNTest, in the global scope, then proceed as normal. Two problems with that: one, you have an extra variable in the global scope. That's dirty! Secondly, if you try to get rid of that variable, you encounter an error, because 'ol JavaScript cannot find the function reference. :( It works, but it is neither elegant nor efficient.

Let's edit the eval command so that it it only evaluates the argument if it is a valid number. We can even use our new, correct isNaN function! We'll assume that is appended in the code snippet below.

(function(oldEval){
  return eval = function(arg){
    // test if valid number
    var testArg = Number(arg);  // will be NaN if invalid
    return isNaN(testArg)?arg:oldEval(arg);
  }
})(eval);

Simplex enough, eh?