Logic’s Last Stand

January 11, 2009

Prototype.js Breaks JavaScript Foreach Loops

Filed under: Computers — Tags: , , , , , — Zurahn @ 6:21 pm

This site uses the Prototype.js JavaScript library, primarily for the excellent AJAX methods.  But there’s a lot more to the library designed to simplify cumbersome methods and make your code work cross-browser without worries.

The developers, though, have gone a bit overboard in places, not letting standards get in their way of imposing their own will on language semantics, including modifying core JavaScript objects. Adding onto the Array object caused the return of functions with the IN keyword.

In simplistic terms, they broke a keyword (or the keyword was broken, just not fully implemented, per se).

To write a foreach loop (a loop that goes through every element in an array without requiring the knowledge of the key for each element) can be written like this:


for(var key IN array) {
  …
}

Where key is the array index key for the element for each time through the loop, allowing you to access the element by simply array[key].

Here’s an example

<script type="text/javascript">
var array = ['A', 'B', 'C'];
for (var key in array)
{
  document.write(array[key]);
}
</script>

Nice and easy, outputs

ABC

Now let’s do nothing to the code at all, but import the Prototype.js file.  Remember, Prototype doesn’t execute anything when you load it.


<script type="text/javascript" src="http://thevgpress.com/js/prototype.js"></script&gt;
<script type="text/javascript">
var array = ['A', 'B', 'C'];
for (var key in array)
{
  document.write(array[key]);
}
</script>

Let’s check the output:

ABCfunction(iterator,context){var index=0;iterator=iterator.bind(context);try{this._each(function(value){iterator(value,index++)})}catch(e){if(e!=$break)throw e;}return this}function(number,iterator,context){iterator=iterator?iterator.bind(context): Prototype.K;var index=-number,slices=[],array=this.toArray();while((index+=number)=result)result=value});return result}function(iterator,context){iterator=iterator?iterator.bind(context): Prototype.K;var result;this.each(function(value,index){value=iterator(value,index);if(result==undefined||valueb?1:0}).pluck(‘value’)}function(){return[].concat(this)}function(){var iterator=Prototype.K,args=$A(arguments);if(Object.isFunction(args.last()))iterator=args.pop();var collections=[this].concat(args).map($A);return this.map(function(value,index){return iterator(collections.pluck(index))})}function(){return this.length}function(){return’['+this.map(Object.inspect).join(', ')+']‘}function(iterator,context){iterator=iterator.bind(context);var result;this.each(function(value,index){if(iterator(value,index)){result=value;throw $break;}});return result}function(iterator,context){iterator=iterator.bind(context);var results=[];this.each(function(value,index){if(iterator(value,index))results.push(value)});return results}function(object){if(Object.isFunction(this.indexOf))if(this.indexOf(object)!=-1)return true;var found=false;this.each(function(value){if(value==object){found=true;throw $break;}});return found}function(){return this.map()}function () { [native code] }function () { [native code] }function(){this.length=0;return this}function(){return this[0]}function(){return this[this.length-1]}function(){return this.select(function(value){return value!=null})}function(){return this.inject([],function(array,value){return array.concat(Object.isArray(value)?value.flatten():[value])})}function(){var values=$A(arguments);return this.select(function(value){return!values.include(value)})}function(){return this.length>1?this:this[0]}function(sorted){return this.inject([],function(array,value,index){if(0==index||(sorted?array.last()!=value:!array.include(value)))array.push(value);return array})}function(array){return this.uniq().findAll(function(item){return array.detect(function(value){return item===value})})}function(){return[].concat(this)}function(){var results=[];this.each(function(object){var value=Object.toJSON(object);if(value!==undefined)results.push(value)});return’['+results.join(', ')+']‘}

Not exactly what we want.

There are a couple ways to work around this.  If you want to continue to the use the conventional JavaScript foreach loop using the IN keyword, here’s an example of how to dodge the weird Prototype glitch:


<script type="text/javascript">
var array = ['A', 'B', 'C'];
var len = array.length;
var count = 0;
for (var key in array)
{
  if(count >= len)
    break;
  else
    ++count;
  document.write(array[key]);
}
</script>

This doesn’t affect the actual length of the array and the functions it returns are at the end, so making sure you only process the correct number of elements eliminates the problem.

Alternatively, you can use the Prototype built-in function for enumerating elements (though this only works on numeric indexes).


<script type="text/javascript">
var array = ['A', 'B', 'C'];
array.each(function(element) {
  document.write(element);
});
</script>

Or, perhaps simplest, setting the array initially to an Object type to wipe out any extension, and hence any weird returns. This however, is dependent on Object not being extended.


<script type="text/javascript">
var array = new Object();
array = ['A', 'B', 'C'];
for (var key in array)
{
document.write(array[key]);
}
</script>

Having to work around Prototype defeats its purpose in the first place.

About these ads

13 Comments »

  1. It does not break JavaScript!

    https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Statements/for…in

    “Although it may be tempting to use this as a way to iterate over an Array, this is a bad idea. The for…in statement iterates over user-defined properties in addition to the array elements, so if you modify the array’s non-integer or non-positive properties (e.g. by adding a “foo” property to it or even by adding a method or property to Array.prototype), the for…in statement will return the name of your user-defined properties in addition to the numeric indexes. Also, because order of iteration is arbitrary, iterating over an array may not visit elements in numeric order. Thus it is better to use a traditional for loop with a numeric index when iterating over arrays. “

    Comment by kamil — January 27, 2009 @ 10:05 am

  2. The point is that it eliminates the functionality, not that it’s against the JavaScript spec. As written in the post, it’s the result of extending the Array object. Thanks for posting the link, though, it’s good additional material.

    Comment by Zurahn — February 8, 2009 @ 8:55 pm

  3. So basically you admit that it doesnt break javascript foreach loops at all and that in fact, prototype enforces it by making anyone stupid enough to try it, deal with the consequences.

    perhaps you should rename the title of the blog post, it’s misleading and deliberately sensationalist.

    Comment by Christopher Thomas — February 18, 2009 @ 8:28 am

  4. Silly post.
    1) This is an old complaint.
    2) You should never iterate arrays using for i in array, as it’s silly slow. Use a number index < length.

    Comment by Henrik — April 30, 2009 @ 3:45 am

  5. This is a very old complaint, and one of Prototypes major drawbacks. Extending native javascript objects just isn’t a good idea, nor is adding so many things to the global namespace.

    Saying a coding style is ‘stupid’ because your favorite library breaks it is just plain rude. for each in looks quite nice and compact compared to a for loop and it should work. Period. Basic Javascript supports it. It might be slower, but that isn’t significant for smaller loops.

    That said, Prototype solves way more problems then it causes. It should just solve them without causing new problems.

    Comment by Anonymous — May 3, 2009 @ 12:21 am

  6. This is a very old complaint, and one of Prototypes major drawbacks. Extending native javascript objects just isn’t a good idea, nor is adding so many things to the global namespace.

    Saying a coding style is ‘stupid’ because your favorite library breaks it is just plain rude. for each in looks quite nice and compact compared to a for loop and it should work. Period. Basic Javascript supports it. It might be slower, but that isn’t significant for smaller loops.

    That said, Prototype solves way more problems then it causes. It should just solve them without causing new problems.

    Comment by JP — May 3, 2009 @ 12:21 am

  7. Glad to find this. I’ve been looking for a JS foreach and keep having the same issue as you. Figured it was a JS issue. Didn’t even think of Prototype.

    But there are cases where a foreach loop works better than iterating with i++ or whatever numeric step you want to use. I suppose that’s the point of the Hash.each() method in Prototype.

    Comment by Ryan — July 7, 2009 @ 5:30 pm

  8. Plain and simple, Prototype should not change the expected behavior of standard constructs and force you to code messier workarounds to avoid it.

    “for(var in array)” is a lot easier to read than an iterative walkthrough, especially when you’re walking through a multidimensional array.

    It’s like having to code messy workarounds for IE6, IMO. If Microsoft can be berated for their browser making various things not work as expected, why shouldn’t the makers of Prototype get the same?

    Comment by Greg — August 2, 2009 @ 2:24 am

    • No greg, this is the point, prototype DOESNT change the expected behaviour of standard constructs at all, if you at least READ the link attached to the first reply, you’ll find that actually, you are not SUPPOSED to use for(in) with an array, you are SUPPOSED to use for(a=0), it’s written down AND explained for you and you still don’t understand that?

      Comment by Christopher Thomas — August 2, 2009 @ 3:04 am

  9. 1. You’re saying you shouldn’t use the “prototype” feature of a prototypal language because it makes it harder for you to *incorrectly use a feature of JavaScript*.

    2. There’s a difference between “for … in” loops and “foreach” loops. This is the javascript Array#forEach loop (which Prototype does not break):

    https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach

    3. “for … in” is for iterating over custom properties on an object, not elements in an array. The funny (and sometimes confusing) thing about arrays, is that when you put things in them, they are assigned to numerically indexed custom properties of the array.

    So, these two statements are therefore equal:

    var stuff = ["some", "stuff"];
    console.log(stuff, stuff.length);
    // –> ["some", "stuff"], 2

    var stuff = [];
    stuff["0"] = “some”;
    stuff["1"] = “stuff”;
    console.log(stuff, stuff.length);
    // –> ["some", "stuff"], 2

    The problem is not Prototype, it’s that you don’t understand how arrays work.

    Comment by Dan — August 19, 2009 @ 9:57 pm

  10. I suspected prototype being the cause of the problems in my script. Glad to see I’m not alone.

    Removed the prototype.js include and all was working again.

    It’s funny how a bunch of know-it-all coders criticize us for using for..in loops to cycle arrays. If they work, why not use them? They’ve worked well so far and are a hell of a lot neater than messy manual iterations.

    You’re suggesting the many respectable javascript reference sites out there I’ve visited are all wrong to suggest using for..in loops for arrays.

    If including prototype suddenly gives arrays all these unwanted/unneeded additional properties and what not… there’s your problem… PERIOD.

    Comment by forcedalias — July 1, 2010 @ 6:55 am

  11. @forcedalias

    no, your problem is that you refuse to learn and think that because 500,000 idiots repeating the same crap reference examples is actually proof that those examples aren’t complete rubbish.

    the fact is that when teaching, you take shortcuts because you are more interested in teaching the lesson, than teaching good style, good style comes from practice, it comes from being good at what you do and learning when you are wrong.

    I knew lecturers who were 40 years old when I was at university who told me complete rubbish and I fought them in arguments and won on the basis that my argument was right and they knew it, so please don’t say that “people teach that way, so it must be right” cause it’s not true at all.

    for(a in array){
    var b = array[a];
    }

    is not really much more complex than

    for(a=0;a<array.length;a++){
    var b = array[a];
    }

    if a SINGLE LINE is enough to make you ignore the standard and do a for(in) loop, then go for it, but please, don't misinform people who come here for answers, you are not helping people by repeating the same crap that all the other commenters have done.

    It's a provable fact that for(in) on arrays is against the spec and _BAD_ the fact that everyone ignores this, doesn't make it right, it just means there are lots of people out there who refuse to learn.

    get with the program, prototype is not the problem, YOU ARE.

    Comment by chrisalexthomas — July 1, 2010 @ 7:04 am

  12. Until today I had no real reason to prefer one Javascript framework over another, wth the main choices usually being Prototype and jQuery, then I ran across this “problem” myself today and it tilted my opinion a tiny bit towards jQuery.

    While it’s not the best style to use for..in to iterate numerically-indexed arrays, I’ve always found it more readable and prefer not to be an asshole about it.

    After all, it’s completely stupid that when you /do/ use for..in on an Array in Javascript, it iterates the /indices/ rather than the /elements/. They could have made for..in operate sensibly on arrays, but I guess they just didn’t care. So it’s hard to be too much of a style-nazi when the language is already ugly.

    Javascript has always had this goofy “objects are also associative arrays” thing going on, and it’s very common, and quite correct, to use a vanilla Object as an associative array and use for..in iterate /that/. Thank goodness Prototype’s developers recognized the intrusiveness of adding instance methods to Object; it just happens that they didn’t treat Array the same way.

    But for loops are ugly too, so it’s understandable to want to give Array methods like “each” and “collect” like we lucky Ruby coders have. If you don’t like that Prototype adds these as instance methods of Array, you could always get the same functionality, less-obtrusively albeit with slightly uglier syntax, out of jQuery ($.each(myArray, function(element)…) or Underscore _.each(myArray, function(element)…) You pick your trade-offs in this line of work, and if you don’t like something, chances are there’s an another choice.

    Comment by nothinghappens — October 7, 2010 @ 3:55 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Shocking Blue Green Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: