Skip to main content

Bug Tracker

Side navigation

#11150 closed bug (invalid)

Opened January 10, 2012 03:19PM UTC

Closed January 10, 2012 08:42PM UTC

Last modified January 10, 2012 09:05PM UTC

$.map inconsistent behavior

Reported by: davestein Owned by: davestein
Priority: undecided Milestone: None
Component: unfiled Version: 1.7.1
Keywords: Cc: danheberden
Blocked by: Blocking:
Description

I came across an issue where IE8 will iterate through an object too many times. If an object has 3 items and we use $.map on it, and alter the indexes during the loop for all 3 items, it will loop 6 times. The fiddle shows what I mean since it's pretty hard for me to explain.

http://jsfiddle.net/davestein/xKzyV/9/

Anyway I'm aware it's an IE8 bug. The strange thing is it seems as though $.map attempts to work around it. If I do $.map in IE9 or IE9's IE8 mode it works fine. If I do $.map in IE8 it does not work.

I'm aware that IE8 mode is not identical to IE8. So as a constant I used native JS and it broke in all cases. If jQuery wasn't trying to account for it, I'm unsure why $.map would work on IE8 mode, but not IE8.

Attachments (0)
Change History (14)

Changed January 10, 2012 03:28PM UTC by davestein comment:1

Oh as I note my workaround is:

var x = { a_1 : 1 };

var copy = $.extend( {}, x );

$.map( copy, function( value, key ) {

var new_key = key.replace( 'a_', '' );

x[new_key] = value;

delete( x[key] );

});

Changed January 10, 2012 04:45PM UTC by rwaldron comment:2

owner: → davestein
status: newpending

Can you reduce that fiddle to something a little simpler?

Changed January 10, 2012 05:14PM UTC by Dave Stein <dave@behance.com> comment:3

I went to make it simpler and then found another interesting point of confusion.

http://jsfiddle.net/davestein/xKzyV/16/

Yields bad results every time, no matter what browser/mode/function.

http://jsfiddle.net/davestein/xKzyV/9/

Does not yield bad results for $.map in IE8 mode as I had previously reported.

Expected Results:

a_1 1, a_2 2, a_3 3

Bad results:

a_1 1, a_2 2, a_3 3, 1 1, 2 2, 3 3

Changed January 10, 2012 06:34PM UTC by rwaldron comment:4

I'm not sure this is something jQuery can fix:

http://gyazo.com/bd96a4c4d1ec26f0863e9eede7b755dc.png

Changed January 10, 2012 08:07PM UTC by Dave Stein <dave@behance.com> comment:5

Any idea why it works fine in fiddle version 9, in IE8 mode? That's the part that confuses me most. I'd expect it to duplicate in all situations.

Changed January 10, 2012 08:10PM UTC by rwaldron comment:6

cc: → danheberden

I'm not sure, I'll have danheberden take a look - he most recently authored changes to $.map, so he may have insight that I don't immediately see

Changed January 10, 2012 08:17PM UTC by danheberden comment:7

DaveMethvin was in there recently too. I don't recall any issues with IE8 that I ran into via tests 'n stuff. The disparity between IE8 and IE8 mode seems sensible, sense they aren't the same version.

Yay bugs!

Changed January 10, 2012 08:22PM UTC by Dave Stein <dave@behance.com> comment:8

I'm wondering if map can fix the issue the way I did myself. If it iterates on a copy instead of actual object, and the callback just returns arguments of value and index, I don't see how that would break existing code.

It also seems "this" inside callback refers to window instead of object so that's one less thing to worry about for such a change.

Dan, the disparity is def sensible, if it was at least consistent between my fiddles :) Maybe I'm missing a minute detail of difference between the two because I'm using functions in one and not the other. I can't think of why that would change the outcome.

Changed January 10, 2012 08:31PM UTC by danheberden comment:9

The biggest thing I see as a problem is the cost of copying first - <IE8 is already awfully slow with map, and a copy would just be that much more work (thinking where $.map is used internally as an immediate pain point).

Sounds like the first step would be testing for when this happens?

Changed January 10, 2012 08:37PM UTC by rwaldron comment:10

Something to note... $.map is being used here for "side effects" which is technically an incorrect use. $.map is expected to return an array, if you want to iterate values and do something with them that will produce side effects, then use $.each.

Changed January 10, 2012 08:41PM UTC by danheberden comment:11

Additionally, the bug in question seems to be directly related to deleting keys on an object in IE8, not map's function of iterating.

Changed January 10, 2012 08:42PM UTC by dmethvin comment:12

_comment0: To follow up on rwaldron's comment, would you say it's safe practice to modify the incoming object while you're iterating it? In every language I've used, modifying a collection in the midst of iteration is, at minimum, a point of concern. It is for the very reason that you see here. \ \ For example, C++ STL iterators don't support modifying the collection while it's being iterated. Microsoft Win32 API filesystem calls like FindNextFile behave inconsistently when you add or remove files from the current directory while iterating it. The same goes for Java's Iterator, where items can only be deleted (no additions) during iteration. \ \ Sure enough, ECMA-262, section 12.6.4 says: \ \ > The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property \ that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration. A property name must not be visited more than once in any enumeration. \ \ One way to get a consistent set of results is to obtain the list of keys in advance, before starting the iteration. But that's expensive and potentially wasteful so many iteration classes/implementations try to avoid it. \ \ It's easy to create a new object rather than trying to mess with the current one while it's being iterated. That is actually the semantics of a .map() operator, since it came from functional programming where you would be shot, then drawn and quartered, for mutating an incoming object! \ \ Since this is an edge case and documented to have undefined behavior, if you absolutely must modify the current object then write code that gets the keys in advance. \ 1326228161973672
resolution: → invalid
status: pendingclosed

To follow up on rwaldron's comment, would you say it's safe practice to modify the incoming object while you're iterating it? In every language I've used, modifying a collection in the midst of iteration is, at minimum, a point of concern. It is for the very reason that you see here.

For example, C++ STL iterators don't support modifying the collection while it's being iterated. Microsoft Win32 API filesystem calls like FindNextFile behave inconsistently when you add or remove files from the current directory while iterating it. The same goes for Java's Iterator, where items can only be deleted (no additions) during iteration.

Sure enough, ECMA-262, section 12.6.4 says:

The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration. A property name must not be visited more than once in any enumeration.

One way to get a consistent set of results is to obtain the list of keys in advance, before starting the iteration. But that's expensive and potentially wasteful so many iteration classes/implementations try to avoid it.

It's easy to create a new object rather than trying to mess with the current one while it's being iterated. That is actually the semantics of a .map() operator, since it came from functional programming where you would be shot, then drawn and quartered, for mutating an incoming object!

Since this is an edge case and documented to have undefined behavior, if you absolutely must modify the current object then write code that gets the keys in advance.

Changed January 10, 2012 08:52PM UTC by Dave Stein <dave@behance.com> comment:13

Thanks for the detailed answer Dave. I'm going to stick with getting keys in advance then. I still have to wonder why I got different results between the two fiddles though.

And to your point rwaldron, I'm going to have our code changed from $.map to $.each. I almost never use $.map, didn't click in my head to have coworker switch his $.map to $.each because of side effect.

Changed January 10, 2012 09:05PM UTC by rwaldron comment:14

Glad to have been a help - thanks again for the report.