8

I faced this strange situation where foreach like construct of javascript does not work in IE but it works in FF. Well not all for..in just this special funciton does not work. I will post the code. Tested in IE8. Tested also with XHTML DTD.

 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <HTML>
  <HEAD>
   <TITLE> Test </TITLE>
   <META NAME="Generator" CONTENT="EditPlus">
   <META NAME="Author" CONTENT="">
   <META NAME="Keywords" CONTENT="">
   <META NAME="Description" CONTENT="">
  </HEAD>
 <script type="text/javascript">
 <!--
  String.prototype.format = function() {         
   var formatted = this;       
   var mycars = new Array(); //some 
   mycars[0] = "Saab";
   mycars[1] = "Volvo";
   mycars[2] = "BMW";
    var arg;
     for (arg in mycars) {        alert('it comes here');     
      formatted = formatted.replace("{" + arg + "}", arguments[arg]);         }         
      return formatted;
     };

  String.prototype.format2 = function() {         
  var formatted = this;       
  var arg;
     for (arg in arguments) {        alert('it does not come here');     
      formatted = formatted.replace("{" + arg + "}", arguments[arg]);         }         
      return formatted;    
  };

 function fn() {
  var s = 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP'); 
  alert('format:'+s);
   var s2 = 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format2('ASP', 'PHP'); 
 alert('format2:'+s2); //does not replace {0}s and {1}s
 } 
 //-->
 </script>
  <BODY>
  <input type="button" value="click " onclick="fn();" />

  </BODY>
 </HTML>

Update I posted a wrong question that it works in FireFox but not in IE8 which was wrong. It does not work in FireFox too. Actually I got this code from post JavaScript equivalent to printf/string.format.

Community
  • 1
  • 1
samarjit samanta
  • 1,285
  • 2
  • 16
  • 29

6 Answers6

23

First of all, while the arguments object available within a function is not an array, it is "array-like" enough such that an incremental for loop (for (var i = 0, len = arguments.length; i < len; i++) { ... }) is preferable - not only because it runs faster, but also because it avoids other pitfalls - one of which is exactly what you're falling into.

To actually answer the question of why the second loop doesn't work, it's important to realize just what for ... in loop does: it iterates through all enumerable properties found in an object. Now, I've bolded 2 words in that statement, because I used these two words purposefully to indicate a couple of nuances that, while they may seem subtle, can drastically affect the behavior of your code if you don't realize what's going on.

First let's focus on all - by which I mean to say, not just properties of the object itself, but also potentially properties said object has inherited from its prototype, or its prototype's prototype, or so on. For this reason, it is very often recommended that you "guard" any for ... in loop by immediately additionally qualifying it with the condition if (obj.hasOwnProperty(p)) (assuming your loop were written for (var p in obj)).

But that's not what you're running into here. For that, let's focus on that second word, enumerable. All properties of objects in JavaScript are either enumerable or non-enumerable, which pretty much directly relates to whether the property shows up in a for ... in loop or not. In browsers such as Firefox and IE, as it turns out, the arguments object's numeric properties are not enumerable (nor is its length as it were), which is precisely why you're not iterating through anything!

But really, in the end, for iterating through anything that is an Array or array-like, you are better off using an incremental loop (as M. Kolodny also stated), and avoiding these shenanigans altogether (not to mention potential cross-browser inconsistencies - I seem to be noticing that in Chrome 10, the numeric properties of arguments objects are enumerable!)

Ken Franqueiro
  • 10,559
  • 2
  • 23
  • 40
  • Thank you Ken, +1 interesting answer, so do you know why if I build the object with the braces, the properties are enumerable.. then what kind of Object is arguments? Or put it in another way, how do I declare an Object with properties that are not enumerable? I feel kind of weird of having an Object "arguments" and not understanding it, seems built with "magic" not with the ordinary language constructs. But perhaps I'm missing something. – stivlo Jan 28 '11 at 04:15
  • 1
    Well, `arguments` is sort of special in that it's one of the objects natively implemented by JavaScript runtimes. If you're interested in these features of objects, ECMAScript 5 gives you ways to fully control such things in your own custom objects, but realize that not all browsers support ES5. Have a look at MDN for more information, perhaps starting [here](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create) and continuing from the "see also" links. – Ken Franqueiro Jan 28 '11 at 04:35
  • Thank you.. I see now "the magic" as in the example: Object.defineProperty(o, "b", { value: 2, enumerable: false }); However as I understand, you suggest is better to not use this syntax, because it's not implemented in all browsers. Weird that they decided to define the arguments as not enumerable then, looks like an oversight. – stivlo Jan 28 '11 at 04:46
2

Try using this as a format function:

String.prototype.format = function() {
    var me = this;
    for (var i = 0; i < arguments.length; i++)
        me = me.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i]);
    return me;
}

Now this should work:

alert('The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP'))

DEMO

Tested and working in IE

Moshe K.
  • 700
  • 1
  • 6
  • 9
  • I knew this works, what i wanted to know what why for..in does not work. – samarjit samanta Jan 28 '11 at 07:13
  • 1
    @samarjitsamanta That's because arguments[] is not really an array, it just looks like one. You can convert it to an Array like this: `args = Array.prototype.slice.call(arguments)` You will get alot of other properties that it inherits from the prototype object but you can filter it out with `hasOwnProperty()` . Check out what I mean: **http://jsfiddle.net/C3nWG/** . Have console open if you don't want to get too annoyed by that fiddle – Moshe K. Jan 28 '11 at 15:04
1

From my test, with Firefox 3.6.13 and IE 8 I don't see any difference in behavior, they both don't go in the second loop.

One difference between mycars and arguments is that mycars is an Array, while arguments is an Object.

To demonstrate this:

alert(mycars.constructor);    //shows: "function Array() { [native code] }"
alert(arguments.constructor); //shows: "function Object() { [native code] }"

However, with some test code I can see that "for in" works for Array and Object

var showWithForIn = function (myArgument) {
    for (i in myArgument) {
        alert(myArgument[i]);
    }
};

var testArray = function() {
    var mycars = new Array(); //some 
    mycars[0] = "Saab";
    mycars[1] = "Volvo";
    mycars[2] = "BMW";
    showWithForIn(mycars);
};

var testObject = function() {
    var myFriends = {
        0: 'John',
        1: 'Aileen'
    };
    showWithForIn(myFriends);
};

function testAll() {
    testArray();
    testObject();
}

So, I am not sure how the arguments Object is different from an Object you construct yourself with the curly braces literal. I think it is confusing, because in this test for in works for both the Array and the Object. While the "for in" doesn't work with arguments.

Again, in all tests I didn't notice any difference between FF 3.6 and IE 8.

UPDATE: As I discovered thanks to Ken comments, arguments properties are defined as not enumerable, while when defining an Object literal properties are implicitly defined as enumerable.

stivlo
  • 83,644
  • 31
  • 142
  • 199
  • I am terribly sorry it does work IE8 & FF3.6 in the same way. I tried again just now. Well in chrome it works differently. I dont know why I posted a wrong question, may be i will try to edit the question put Chrome in place of FF. But credit goes to Ken he solved the riddle, arguments being not enumerable. – samarjit samanta Jan 28 '11 at 07:31
0

Some browsers support for..in like Chrome and Firefox 4 to iterate arguments, but other browsers don't see it's parameters while iterating like that. I bet that on these browsers if you did JSON.stringify(arguments) the result would be an empty object. By the specification of JavaScript 1.1 and further arguments have a length parameter, so that means you can iterate them using for (var i = 0; i < arguments.length; i++) and while(i < arguments.length) loops.

Personally once I got burned using for..in for argument iteration I wrote a simple function for argument object iteration that doesn't depend on length, because the arguments are always labeled in order by IDs.

var eachArg = function(args, fn, start_from, end_where) {
    var i = start_from || 0;
    while (args.hasOwnProperty(i)) {
        if (end_where !== undefined && i === end_where)
            return i;
        if (fn !== undefined)
            fn(i, args[i]);
        i++;
    }
    return i;
};

I use it ever since when I iterate arguments and it doesn't fail me. More on about it on my blog post http://stamat.wordpress.com/iterating-arguments-in-javascript/

stamat
  • 1,832
  • 21
  • 26
  • If you are providing start_from and end_where its not called iteration http://en.wikipedia.org/wiki/Iterator_pattern. You are running away from the problem. I asked about the reason for discrepancy and not a workaround, and I got a perfect answer from [Ken](http://stackoverflow.com/users/237950/ken-franqueiro). Actually quoting your use case when you got burned using `for..in` might actually enlighten us more. I appreciate your effort anyway, trying to provide a solution. – samarjit samanta Dec 20 '13 at 02:25
0

use for(i=0;i<length;i++){} because for loop in and of is not working IE. will use normal for loop it will work fine.

-2

Well, it's supposed to work, so if it doesn't, the answer is as simple as "it's yet another bug in IE".

But the real question here is why you're using for ... in to iterate over arrays or array-like objects (such as arguments) -- just use a simple for loop instead, which works in all major browsers:

for (var i = 0; i < arguments.length; i++) {
  // do something with arguments[i]
}
casablanca
  • 69,683
  • 7
  • 133
  • 150
  • @Raynos: I highly doubt even the developers of IE can answer that question. – casablanca Jan 28 '11 at 02:32
  • @casablance I want to find it out. Can you also point everything else he's doing wrong in that example code whilst your at it. – Raynos Jan 28 '11 at 02:39
  • Which version of Firefox are you using? With FF 3.6.13 and IE 8 I don't notice any difference in behavior, they both don't go in the second loop. – stivlo Jan 28 '11 at 03:00
  • @stivlo: I actually didn't test the code. I was just mentioning that the OP shouldn't be using `for ... in` anyway. – casablanca Jan 28 '11 at 03:09
  • I don't think it's correct to claim "it's yet another bug in IE" without testing it, especially when FF has exactly the same behavior. – stivlo Jan 28 '11 at 04:19