Archive for the ‘Coding practices’ Category

Shim sniffing

Monday, June 4th, 2012

Extending native objects and prototypes is bad. If not vile, mean and Jesuitic.

// Noooooo!
Array.prototype.map = function() {
  // stuff
};

Unless it's desirable, for example for adding ECMAScript5 methods in legacy browsers.

In which case we do something like:

if (!Array.prototype.map) {
  Array.prototype.map = function() {
    // stuff
  };
}

If we're paranoid enough we can even try to protect from somebody defining map as something unexpected like true or "the peaches are this way":

if (typeof Array.prototype.map !== "function") {
  Array.prototype.map = function() {
    // stuff
  };
}

(Although that ultimately breaks the other developer's map to the peach trees)

But in a hostile dog-eat-dog cut-throat environment (in other words when you provide or consume a library), you trust no one. What if that other smartass JS loads before your badass JS and defines map() in a way that is not really ES5-compliant and your code doesn't work anymore?

You can always trust browsers though. If Webkit implements map() you can relax that it should probably work OK. Otherwise you'd want to go ahead with your shim.

Luckily that's easy to do in JavaScript. When you call toString() of a native function it should return a string with a function that has a body of [native code]

So for example in Chrome's console:

> Array.prototype.map.toString();
"function map() { [native code] }"

A proper check is ever-so-slightly painful because browsers seem to be a little frivolous with white spaces and new lines. Testing:

Array.prototype.map.toString().replace(/\s/g, '*');
// "*function*map()*{*****[native*code]*}*"  // IE
// "function*map()*{*****[native*code]*}" // FF
// "function*map()*{*[native*code]*}" // Chrome

Simply stripping all \s will give you something more workable:

Array.prototype.map.toString().replace(/\s/g, '');
// "functionmap(){[nativecode]}"

You can opt in for a reusable shim() function so you don't have to repeat all that !Array.prototype... jazz. It can take an object to augment (e.g. Array.prototype), a property to add (e.g. 'map') and a function that implements the shim.

function shim(o, prop, fn) {
  var nbody = "function" + prop + "(){[nativecode]}";
  if (o.hasOwnProperty(prop) &&
      o[prop].toString().replace(/\s/g, '') === nbody) {
    // native!
    return true;
  }
  // shim
  o[prop] = fn;
}

Testing:

// this is native, cool
shim(
  Array.prototype, 'map',
  function(){/*...*/}
); // true
 
//  this is new
shim(
  Array.prototype, 'mapzer',
  function(){alert(this)}
);
 
[1,2,3].mapzer(); // alerts 1,2,3

p.s. And then
there's JJD's! (backstory)

arguments considered harmful

Tuesday, February 16th, 2010

Inside every JavaScript function an arguments object is available containing all the parameters passed to the function.

function aha(a, b) {
  console.log(arguments[0] === a); // true
  console.log(arguments[1] === b); // true
}
 
aha(1, 2);

However, it's not a good idea to use arguments for the reasons of :

  • performance
  • security

The arguments object is not automatically created every time the function is called, the JavaScript engine will only create it on-demand, if it's used. And that creation is not free in terms of performance. The difference between using arguments vs. not using it could be anywhere between 1.5 times to 4 times slower, depending on the browser (more info and bench)

As for the security, there is the POLA (Principle of Least Authority) which is violated when one function A passes arguments to another B. Then a number of bad things can happen including:

  • B calls A through arguments.callee - something A never intended when calling B in the first place
  • B overwrites some arguments and causes A to misbehave

While in these scenarios B looks a little malicious, it can actually cause trouble unvoluntarilly. Consider this example that illustrates the second case (B changing values behind A's unsuspecting back)

function A(obj, ar) {
 
  console.log(obj); // {p: 1}
  console.log(ar);  // [1, 2, 3]
 
  B(arguments);
 
  // oops!
  console.log(obj); // {p: 2}
  console.log(ar);  // [1, 2]
}
 
function B(args) {
 
  // convenient innocent-looking local vars
  var o=args[0],
      a=args[1];
 
  // do something with the local variables
  o.p = 2;
  a.pop();
 
  // now the original arguments is 
  // messed up because objects/arrays
  // are passed by reference
}
 
A({p: 1}, [1, 2, 3]);

ECMAScript 5

In ECMAScript's "strict mode", using arguments.callee will throw a syntax error.

Recursive anonymous function

Probably the biggest argument for keeping arguments and arguments.callee is so that recursive anonymous functions can be created, because by using the callee property a function can call itself without knowing its own name. Now, this is not such a common scenario, but even if so, you can wrap a named function inside of an anonymous function and voila! - call that named function recursively without leaking a variable to the global scope.

Separating behavior

Monday, September 14th, 2009

The modern understanding of a web page is that it consists of three distinct parts:

  1. Content (HTML)
  2. Presentation (CSS)
  3. Behavior (JavaScript)

These should be kept separate, ideally each in its own file (or files). What it means for JavaScript is that there should be no inline such as onclick, onmouseover, etc. Ideally there should be little to no inline scripts.

The idea is that content (the text on the page) is independent of:

  • how it looks like, so the page should be usable even with CSS disabled, or not supported by the browser, and
  • how it behaves, meaning the page should be usable even with JavaScript disabled. The content should be accessible independent of any mouseovers, animations, and so on.