forEach() trouble when an element is removed during execution

Jeff Walden jwalden at MIT.EDU
Mon Jun 18 04:10:33 PDT 2007


Peter Michaux wrote:
> I ran the following snip of code on Firefox 2 with Firebug installed.
> 
> var arr = [0,1,2,3];
> arr.forEach(function(el) {
>   console.log(el);
>   if (el == 1) {
>     arr.splice(el, 1);
>   }
> });
> 
> // output
> // 0
> // 1
> // 3
> 
> 
> Note that 2 is skipped yet 2 is in the array both before and after the
> the forEach() execution.

(It's iterating over indices, and when el == 1 the next property is 2, which after the splice produces the value 3, for those who don't have splice memorized and didn't look it up.)


> iterators are supposed to hide implementation.

Not necessarily, although that may sometimes be a good thing.  An array has a well-defined ordering, and having iteration match that ordering seems better to me than slightly-gratuitous abstraction.


> If I say I want to iterate over the elements of an array then I think
> at least all the elements that are in the array both before and after
> the iteration should be iterated.

How do you propose doing this?  Any solution that comes to my mind is complicated and uses O(# props) memory.  Do you still wish for order of iteration to be preserved?  You'll have to specify these cases for interop.  There's a reason that for loops don't have exactly specified behavior in the presence of mutation of the object over which iteration occurs.  Mutation in general plays havoc with sequential execution, and this is just one case demonstrating its perils.  Functional style discourages mutation, and I think forEach should follow in that tradition.


> Elements that are removed during the forEach execution but have not
> yet been iterated...that is debatable if the removed elements should
> be iterated. (In the case of DOM2 event handlers these would not be
> iterated. Two browsers do it one way and two do it the other way.)

The DOM isn't a programming language, so they can to a degree "get away" with sloppiness in the behavior their specification promises, to the chagrin of implementers, with behaviors which are undefined.  ES4 is a programming language, and I for one very much want exactness.


> Elements that are added during the forEach execution, I think those
> should not be iterated since that could lead to infinite loops.

Many things can lead to infinite loops; this particular one doesn't seem like a huge problem if you avoid mutation, as the intent of forEach was not for use with a mutating method.


> So does forEach() behave properly in the above example?

It behaves according to the description of its behavior as given on <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach>.  I happen to think this is the right behavior.  It allows the procedure to be written in JavaScript if you so desire (you might need |delete| hooks if you want to detect them), which at least for the web is an important property (and makes it more understandable to implementors and users who wish to fully understand the behavior).  The behavior's reasonably simple to understand, and attempting to handle mutation will make the behavior more difficult to understand.  The user who wishes to handle mutation won't have that behavior swept under the rug of forEach; it will be explicit in his code, and thus that much more understandable.  Finally, we do have precedent in doing this in other languages: Java's iterators over its collections attempt to throw a ConcurrentModificationException when they detect mutation and
 don't promise some special behavior that tries to handle mutation.

On a further note, forEach is in at least two shipping browsers with the precise behavior documented on that page, so inertia if nothing else suggests the existing definition is the one which should be in the spec.

Jeff



More information about the Es4-discuss mailing list