I recently had some trouble with IE8 (I don’t know about 9 at this point) with reading and comparing the value of some [[Class]] properties. Actually it’s only in the case for the localStorage object.
I’m using a method like this
var ToStr = Object.prototype.toString;
Object.type = function _type( obj ) {
var res = ToStr.call( obj ).split( ' ' )[ 1 ].replace( ']', '' );
if( obj === window ) {
res = 'Window';
}
else if( res === 'Window' || res === 'Global' ) {
res = 'Undefined';
}
else if( res.indexOf( 'HTML' ) === 0 ) {
res = 'Node';
}
return ( res );
};
This method will return this values for instance:
var foo = { },
bar = [ ],
num = 52,
win = window;
Object.type( foo ) === 'Object'; // true
Object.type( bar ) === 'Array'; // true
Object.type( num ) === 'Number'; // true
Object.type( win ) === 'Window'; // true
That works of course, in all browsers I’m aware of by simply checking that [[Class]] property from an object itself. Now, I’m calling this method on the localStorage object
Object.type( win.localStorage ) === 'Storage' // true (not in IE8)
IE8 just returns Object here. However, that is not the actuall problem, the problem happens when you try to compare the localStorage object with the window object. As you can see, I’m checking if the passed in argument is the current window object
if( obj === window ) { }
If obj now is the window.localStorage object, this will end up in an error
"Class does not support automation"
This only happens if you try to compare localStorage with window, you can compare it against anything else without any trouble. Is this just another bug or can I workaround this issue somehow ?
I guess basically my question is:
How do you know in IE8 (possibly IE9 too) if you’re dealing with the localStorage object?
The last thing I want to do is to inner-wrap the whole method with a try-catch because it gets called fairly often.
To entirely confuse me here it comes: When you do a console.log( obj ) in IE8’s console it returns you [object Storage] (nice!) but if you call Object.prototype.toString.call( obj ) it returns [object Object]. Same goes for typeof obj, will return object.
Second question:
How does the IE8 console print out the correct [[Class]] ?
I’ve found a way to work around the IE8 behavior using an implicit
toString()operation and the ECMAScript spec explains why the work-around makes sense. The implicittoString()is this:This is implicitly forcing a call to the object’s internal
toString()method and, in IE, this will return the desired form you want[object Storage]and you can get your code to work without special casingwindow.localStorage.So, I was looking for the minimal risk way to incorporate this into your existing code. The approach chosen was to get the type that same way you use to get it and if and only if it returns a generic “Object” type, then see if there is a better name available with the new method. So, all things that used to work just fine will continue to work the way they did and we might find a better name for some objects (like
window.localStorage) that used to return a generic “Object” name. The one other change is that I felt less confident about the exact type of return we might get from the"" + objconstruct so I wanted a parsing method that wouldn’t throw an error on unexpected data so I switched to a regex from the split/replace method you were using. The regex also enforces that it’s really the[object Type]format too which seems desirable.Then, to protect against the odd issue of comparing
localStorage === windowand getting an error, you can add a type check (duck typing) that a non-window like object would not pass and this will filter out thelocalStorageissue and any other objects with the same issue. In this particular case, I make sure the type of the object is"object"and that it has a property namedsetInterval. We could have selected any well known, well supported property of thewindowobject that is unlikely to be on any other object. In this case, I usesetIntervalbecause that’s the same test that jQuery uses when it wants to know if an object is a window. Note, I also changed the code to not explicitly compare towindowat all because there can be more than onewindowobject (frames, iframes, popups, etc…) so this way, it will return “Window” for any window object.Here’s the code:
See a demo with various test cases here: http://jsfiddle.net/jfriend00/euBWV
The desired value of
"[object Storage]"that you were after in order to parse out the “Storage” class name comes from the internal[[Class]]property as defined in the ECMAScript spec. In section 8.6.2, the spec defines specific Class names for"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", and "String". It does not define Class names for host objects likelocalStorageso that is either left to individual browsers or is found in some other spec document.Further, the spec says this about
[[Class]]:And, it is in 15.2.4.2 that we find the specification for generating the output like
[object Array]or[object String]by using the[[Class]as the second word.So,
Object.prototype.toStringis how it is supposed to work. Obviously IE8 has bugs in this regard for thelocalStorageobject. We can’t know inside of IE8 whethertoString()is not using[[Class]]or whether[[Class]]is not set properly. In any case, it appears thatconsole.log()in IE8 is not directly usingObject.prototype.toString()because it generates a different result.The behavior of the
"" + objwork-around is more complicated to understand. The spec describes how a type coercion of an object to a string is supposed to work. It’s a bit complicated to follow the thread all the way through the spec as one part depends upon another which depends upon another and so on. But, in the end, it executes internal methodsToString(ToPrimitive(input argument, hint String))and apparently in IE8,ToPrimitivewhen passed a hint that we want a string is giving us the actual class name thatObject.prototype.toString()is not. There is a path through the spec that winds through[[DefaultValue]]which may be how this happens in IE8, but since we already know IE8 didn’t follow the first part of the spec and it wasn’t generally good at following the spec anyway, it’s not a valid assumption to assume that it follows the spec in this regard. In the end, we just know that a type coercion to string in IE8 ends up giving us the[[Class]]that we wanted.As an interesting test, I tried my test suite in the Chrome browser running all the test cases that are objects through the
"" + objwork-around (normally the code only uses that path whenObject.prototype.toString()doesn’t return a name other than"Object". It works for everything except an array. I think this means that the[[DefaultValue]]for objects is generally[[Class]](unless the object type decides it has a better default value whichArrayapparently does). So, I think we have confirmation that the work-around that fixes IE8 is actually supposed to work per the spec. So, not only is it a work-around for IE8, but it’s an alternate path to get at the[[Class]]name if the object type doesn’t implement a different default value.So, really what this new code I’ve proposed is doing via the spec is this pseudo code:
[[Class]]usingObject.prototype.toString()"Object"then, use it"" + objto try to get at the string version of[[DefaultValue]]"Object", then just return"Object"