Shim sniffing
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)
June 4th, 2012 at 2:04 am
You forgot “typeof” from the third code example.
I think this is a bit exaggerated…
June 4th, 2012 at 2:12 am
Thanks Kai, fixed!
can you elaborate on “exaggerated”?
June 4th, 2012 at 12:50 pm
I think Kai means over the top. When I first read it it seemed a bit over the top, but having thought about it, that function could turn out to be pretty useful.
Nice article, thanks!
June 4th, 2012 at 5:58 pm
Array.prototype.map = Array.prototype.forEach
And now you have [native code] problems
Nah I like the approach, I just think it’s very very defensive. Might as well implement your own if you want to take this route.
June 5th, 2012 at 3:40 am
Agreed with Peter van der Zee … but if you want to trust a bit more the fact a function is native or not I would use a different approach:
var isNativeFunction = function(Function){
var s, toString = Function.prototype.toString;
return function isNativeFunction(f) {
try {
s = toString.call(f);
try {
Function(“return ” + s);
} catch(_) {
return true;
}
} catch(_) {}
return false;
};
}(Function);
June 5th, 2012 at 8:11 pm
@Peter, @Andrea Unfortunatelly the need for this has come up in practice not just an excercise
@Andrea, why the first `try`? How about a simpler: https://gist.github.com/2878985
June 6th, 2012 at 2:17 am
f.toString does not ensure it’s a function in first place, neither the toString is the original one.
Function.prototype.toString can be used for functions/callable objects only so it ensure, without needing extra checks, that the given object is a function, it will fail otherwise.
As summary, Function.prototype.toString.call({}) will throw an error since it’s not a function, and not native
June 6th, 2012 at 2:27 am
so … two examples here, the reason I have used those try/catch
https://gist.github.com/2878985#comments
June 6th, 2012 at 4:15 am
So how do/did you defend against replacing one native method with another?
June 7th, 2012 at 2:45 am
the easiest way to check that is something like isNativeFunction([].map) && [].map.name == “map” … this should work cross platform except for IE less than 9
June 7th, 2012 at 2:55 am
And what about Function.prototype.toString = alert; Obviously we have to stop somewhere
The point here was not to be overly defensive. isNative doesn’t have to deal with non-function input etc. Just defend against shims that are well-intentioned but possibly slightly off
June 7th, 2012 at 4:18 am
Andrea: the array (and object) literal will only use the built-in constructor. They still use the (global) Array.prototype. As such, replacing Array.prototype.map with something else will cause [].map to reflect that.
Stoyan: Okay, that sounds valid
June 7th, 2012 at 4:20 am
Oh by the way, there used to be a trick where if you deleted a built-in global object you would get back the built-in one. I just tried on Chrome and it didn’t work so maybe I’m doing it wrong, maybe it’s a different browser, or maybe that just doesn’t work anymore
July 16th, 2012 at 2:46 am
[...] http://www.jspatterns.com/shim-sniffing/ Share on Twitter Facebook Google Reader Ping.fm No Comments Related [...]
April 23rd, 2013 at 4:56 pm
I have been surfing online greater than three hours today, but I by no means found any fascinating article like yours. It is pretty price sufficient for me. In my opinion, if all website owners and bloggers made good content material as you did, the web will be much more useful than ever before.