Prototype.js Breaks JavaScript Foreach Loops
Log in or Register for free to comment
Recently Spotted:
*crickets*
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>
<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.
---
Tell me to get back to rewriting this site so it's not horrible on mobile