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]]
?
Class yiibehaviorsAttributeBehavior. AttributeBehavior automatically assigns a specified value to one or multiple attributes of an ActiveRecord object when certain events happen.
Every class contains attributes and behaviors. Attributes are the characteristics of the class that help to distinguish it from other classes. Behaviors are the tasks that an object performs. A person's attributes, for example, include their age, name, and height, while their behaviors include the fact that a person can speak, run, walk, and eat.
Attributes are the characteristics of the class that help to distinguish it from other classes. Behaviors are the tasks that an object performs. A person's attributes, for example, include their age, name, and height, while their behaviors include the fact that a person can speak, run, walk, and eat.
The Marquee behavior attribute in HTML is used to set the behavior of scrolling. The default value is scroll.
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 implicit toString()
is this:
"" + window.localStorage
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 casing window.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 "" + obj
construct 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 === window
and 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 the localStorage
issue 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 named setInterval
. We could have selected any well known, well supported property of the window
object that is unlikely to be on any other object. In this case, I use setInterval
because 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 to window
at all because there can be more than one window
object (frames, iframes, popups, etc...) so this way, it will return "Window" for any window object.
Here's the code:
Object.type = function _type( obj ) {
function parseType(str) {
var split = str.split(" ");
if (split.length > 1) {
return(split[1].slice(0, -1));
}
return("");
}
var res = parseType(Object.prototype.toString.call(obj));
// if type is generic, see if we can get a better name
if (res === "Object") {
res = parseType("" + obj);
if (!res) {
res = "Object";
}
}
// protect against errors when comparing some objects vs. the window object
if(typeof obj === "object" && "setInterval" in obj) {
res = 'Window';
}
else if( res === 'Window' || res === 'Global' ) {
res = 'Undefined';
}
else if( res.indexOf( 'HTML' ) === 0 ) {
res = 'Node';
}
return ( res );
};
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 like localStorage
so that is either left to individual browsers or is found in some other spec document.
Further, the spec says this about [[Class]]
:
The value of a [[Class]] internal property is used internally to distinguish different kinds of objects. Note that this specification does not provide any means for a program to access that value except through Object.prototype.toString (see 15.2.4.2).
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.toString
is how it is supposed to work. Obviously IE8 has bugs in this regard for the localStorage
object. We can't know inside of IE8 whether toString()
is not using [[Class]]
or whether [[Class]]
is not set properly. In any case, it appears that console.log()
in IE8 is not directly using Object.prototype.toString()
because it generates a different result.
The behavior of the "" + obj
work-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 methods ToString(ToPrimitive(input argument, hint String))
and apparently in IE8, ToPrimitive
when passed a hint that we want a string is giving us the actual class name that Object.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 "" + obj
work-around (normally the code only uses that path when Object.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 which Array
apparently 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]]
using Object.prototype.toString()
"Object"
then, use it "" + obj
to try to get at the string version of [[DefaultValue]]
"Object"
, then just return "Object"
You wrote:
This only happens if you try to compare
localStorage
withwindow
, you can compare it against anything else without any trouble.
Then why don't you do so?
var ToStr = Object.prototype.toString;
Object.type = function _type( obj ) {
if ( window.localStorage && obj === window.localStorage )
return 'Storage';
if ( obj === window )
return 'Window';
var res = ToStr.call( obj ).split( ' ' )[ 1 ].replace( ']', '' );
if ( res === 'Window' || res === 'Global' ) {
return 'Undefined';
if ( res.indexOf( 'HTML' ) === 0 ) {
return 'Node';
return res;
};
Addition to respond to the questions directly:
obj === window.localStorage
. It cannot get any simpler than that, can it?Regards, Steffen
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With