Shim sniffing
Monday, June 4th, 2012Extending 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)