for...in loop broken in Safari
That browser really tires me these days...
Try the following:
var Person = function(name) {
this.name = name;
};
Person.prototype.name = 'anonymous';
var john = new Person('John');
var result = '';
for (var property in john)
result += property + ': ' + john[property] + '\n';
What would you expect result to look like?
"name: John"
right?
Well... not in Safari! This is what you get instead:
"name: John
name: John"
Don't believe me? See for yourself!
Note that this has been corrected in WebKit.
Honestly, I'm not exactly sure where the problem lies, so if anyone has some input on this issue, please feel free to share.
UPDATE: I posted this on the JavaScript IRC channel. And Woosta came up with a bright explanation of the issue. A for... in loop iterates over all the properties of an object. Our object 'John' above does technically possess two 'name' properties: his own and the one inherited from his contructor. So Safari's behaviour would actually be the correct one. (Note that both properties display the value ('John') because that's the one we are targeting in the last line of code of the above example).
Woosta suggested using the hasOwnProperty method to target the properties of our object only (i.e. not of it's constructor's prototype) like so:
var Person = function(name) {
this.name = name;
};
Person.prototype.name = 'anonymous';
var john = new Person('John');
var result = '';
for (var property in john)
if(john.hasOwnProperty(property))
result += property + ': ' + john[property] + '\n';
Surprise, surprise. This still doesn't work!
Comments
-
“Enumerating the properties of an object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; but a property of a prototype is not enumerated if it is “shadowed” because some previous object in the prototype chain has a property with the same name.” — ECMA-262, §12.6.4
Sun, January 28 at 22:35 PM
-
That second example doesn’t work because the loop receives a “name” string, checks to see if
johnhas a property of its own by that name (which it does), and then appendsjohn.nametoresult. ThathasOwnPropertycall doesn’t fix anything becausejohn.namedoes exist on the object itself.The bug is as you describe it—Safari passes a shadowed property to
for..in. ButhasOwnPropertywon’t fix it. The only way to work around that bug, as far as I can tell, is to keep track of which property names you’ve seen before and ignore them if they come up more than once.Sun, January 28 at 23:50 PM
-
you can simply copy key/values pairs into an assoc. array, and eliminate duplicated properties without additional testing, but yes it’s slow..
Mon, January 29 at 10:47 AM
-
ECMA standard says:
“Enumerating the properties of an object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; but a property of a prototype is not enumerated if it is “shadowed” because some previous object in the prototype chain has a property with the same name.”
Mon, January 29 at 16:51 PM
-
Aarrghh. I somehow missed deltab posting that until a second after I posted. Oh, for a comment delete. Sorry.
That’s the second time I’ve done that this year. I’ll have to be more careful in the future.
Mon, January 29 at 16:53 PM
-
We can find and mark the nearest position where a property is defined in the prototype chain and eliminate the shadowed properties from the result set.
Tue, January 30 at 02:39 AM
-
Has anyone reported this to Apple via bugreport.apple.com?
Tue, February 27 at 13:43 PM
-
Prototype 1.5.1 has a fix for this. See Hash.prototype._each().
Wed, March 14 at 02:16 AM