1

I am trying to write a cross browser foreach method/function. It should be possible to either use the real, or a copy of the values or properties of the array or object to iterate over (just like the PHP foreach ($array as &$item)).

Neither the forEach nor the map method is cross browser compatible, so I will be needing for loops.

This topic JavaScript for...in vs for warned me to not use for... in to loop through arrays, but when I test with the following code:

var arr = [];
arr[3] = "foo";
arr[0] = "bar";
arr["baz"] = "baz";

for (var i in arr) {
    alert(i);
}

I get the results 3, 0 and baz (which is correct). This also seems to work on indexed arrays (0, 1, 2). Yet Object.prototype.toString.call(arr) === "[object Array]" returns true.

So what is the best cross browser approach to create a foreach method which will iterate over both array values and object properties, ideally offering the option to work with the actual values/properties of the array/object?

Community
  • 1
  • 1
user2180613
  • 699
  • 6
  • 20
  • easy enough to deal with the actual array, just do `arr[i] = 'new value'` inside the loop. – Marc B Apr 29 '13 at 20:20
  • Ok, I'm confused. If you're calling the result you're getting from `for-in` "correct", then what's the issue? What exactly do you want? –  Apr 29 '13 at 20:25
  • If you want to use arrays and objects properly, only set values for arrays by index, and set properties as you like with objects. If you want to create a wrapper function that iterates either (based on what is passed), you can check what `Object.prototype.toString.call` returns, and either use a normal `for` loop or a `for in` loop depending on the type. – Ian Apr 29 '13 at 20:25
  • @squint Because the topic I linked to warned not to use `for-in` loops on arrays, I wonder why it actually did work. – user2180613 Apr 29 '13 at 20:30
  • @Ian `Object.prototype.toString.call === "[object Array]"` returned true in my example. How would your for loop look for this example? – user2180613 Apr 29 '13 at 20:32
  • @user2180613: There are answers to that question that give lots of information. It "works" because an Array is an Object, but the `for-in` doesn't guarantee the order of iteration, and it also gives you properties like your `"baz"` that one normally doesn't want when doing an indexed iteration. If you're putting `"baz"` on the Array, I'd wonder why you're using an Array to begin with. –  Apr 29 '13 at 20:33
  • @squint Simply because it is possible to have it in your array and I would like to iterate over every element in it. – user2180613 Apr 29 '13 at 20:36
  • @user2180613 I wouldn't use an array in this case. I'd use `var arr = {};` and use your loop. – Ian Apr 29 '13 at 20:41
  • In php, `&$` means ByRef, right? You realise JavaScript doesn't let you use a true ByRef (you can only achieve something similar via _Object_ properties). Further, `.map` and `.forEach` only loop over defined indices, [according to the es5 spec](http://es5.github.io/#x15.4.4.18), so `"baz"` shouldn't be looped over by these functions. – Paul S. Apr 29 '13 at 20:45
  • @user2180613 By the way, you say "Neither the forEach nor the map method is cross browser compatible" - you know you can add polyfills that fix that for old browsers, right? MDN's docs have one for each, so that you can include them on your page and use `map` and `forEach` like normal without worrying about compatibility – Ian Apr 29 '13 at 20:52
  • http://stackoverflow.com/about – Xotic750 May 11 '13 at 09:27

5 Answers5

1

Just check the hasOwnProperty when using for..in that'll resolve most danger issue:

for (var name in buz) {
    if (buz.hasOwnProperty(name)) {
        // do stuff
    }
}
Simon Boudrias
  • 38,416
  • 12
  • 90
  • 126
0
var arr = [];
arr[3] = "foo";
arr[0] = "bar";
arr["baz"] = "baz";

for (var i in arr) {
    alert(arr[i]);
}

Cheers!

Luke
  • 1,893
  • 1
  • 16
  • 25
  • Your method will loop over EVERY property belong to the array instead of just the properties unique to that array. – kinsho Apr 29 '13 at 20:23
  • An array is also an object in JavaScript, so in addition to looping over all the array properties, you'll also be looping over all the generic object properties, like 'toString' for example. – kinsho Apr 29 '13 at 20:26
  • Oh yeah, i forgot that's how a for loop works. When you loop over an instance of an array, you're actually looping over all the methods that belong to the array class. You're a genius. http://jsfiddle.net/mqc4B/ – Luke Apr 29 '13 at 20:31
  • Well, congrats kid, I stand corrected. A for...in loop only iterates over enumerable properties. – kinsho Apr 29 '13 at 20:44
0

You may be interested in a utility library like underscore.js. It provides many useful utility functions like each and map. If available, it uses the native implementation, otherwise it falls back to its own custom implementation. The library is also relatively small at 4kb for the production version.

If, however, you are really motivated to do this on your own, then the underscore.js source at the very least could illuminate exactly how this is done. Their each is near the top. The line starts with 'var each = _.each = ...'

You could rest assured the code is high quality as it's likely to have been scrutinized and optimized multiple times.

robmisio
  • 826
  • 9
  • 19
  • 1
    I can testify that this is an excellent library. Still, what the questioner is asking for is pretty simply and can be achieved by native JavaScript. – kinsho Apr 29 '13 at 20:24
  • @kinsho - I hear you, but he's also asking about 'map'. If he starts to add any more then he might as well go with a library. Unless, of course, he's doing this as a coding exercise. – robmisio Apr 29 '13 at 20:26
  • @kinsho: Is Underscore not written in plain JS? OP asked for a loop function, and Underscore offers a good one (which you can use without the lib, just copying its source). – Bergi Apr 29 '13 at 21:40
  • Every JS library is written in native JavaScript, bro. Doesn't make it more efficient than a block of native JavaScript that was tailor-written for situations like this. – kinsho Apr 29 '13 at 22:09
  • 1
    Besides, I'd rather people learn how to write these functions in native JavaScript before they actually go ahead and rely on libraries. It helps to build up your fundamentals in a language as deep and Javascript – kinsho Apr 29 '13 at 22:10
0

This should work for array and object, the attribute checks are not necessary, they are just there for demonstration purposes.

function forEach(object, callBack) {
    var tObject = Object.prototype.toString.call(object),
        i,
        l;

    if (tObject !== "[object Array]" && tObject !== "[object Object]") {
        throw new TypeError("'object' must be an array or object");
    }

    if (Object.prototype.toString.call(callBack) !== "[object Function]") {
        throw new TypeError("'callBack' must be a function");
    }

    if (tObject === "[object Array]") {
        i = 0;
        l = object.length;

        while (i < l) {
            callBack(object[i], i);

            i += 1;
        }

        return;
    }

    for (i in object) {
        if (object.hasOwnProperty(i)) {
            callBack(object[i], i);
        }
    }

    return;
}

var test1 = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];
var test2 = {
    "a": 10,
    "b": 11,
    "c": 12,
    "d": 13,
    "e": 14,
    "f": 15,
    "g": 16,
    "h": 17,
    "i": 18
};

forEach(test1, function (element, index) {
    console.log(index, element);
});

forEach(test2, function (element, index) {
    console.log(index, element);
});

On jsfiddle

I've also setup a performance test for you to have a look at on jsperf

Xotic750
  • 20,394
  • 8
  • 50
  • 71
  • that does not iterate over object properties – user2180613 Apr 29 '13 at 20:29
  • Yes, hence my sorry at the bottom, though it is unusual for forEach to work on objects – Xotic750 Apr 29 '13 at 20:29
  • I believe the jquery each does it, but I'm having a hard time to understand how they make their distinction between an array and an object. – user2180613 Apr 29 '13 at 20:34
  • @user2180613 `Object.prototype.toString.call(array)` will return "[object Object]" if it's truly an `Object` – Ian Apr 29 '13 at 20:36
  • @Ian I understand and I know you can safely iterate over an object using the `for-in` loop in combination with the hasOwnProperty method, but how would you iterate over an associative array, if this associative array apparently returns `"[object Array]"`. – user2180613 Apr 29 '13 at 20:39
  • 1
    @user2180613 It's not an associative array - there's no such thing in Javascript. It's an Array with values and properties. – Ian Apr 29 '13 at 20:43
  • @user2180613 Extending from Ian's comment, in _JavaScript_ and _Array_ inherits from _Object_, i.e. an _Array_ is a special type of _Object_, but still an _Object_. `Object.prototype.foo = 'bar'; [].foo; // "bar"` – Paul S. Apr 29 '13 at 20:55
-1
for (var i in arr) {
    if (arr.hasOwnProperty(i)) {
        alert(i + '--->' + arr[i]);
    }
}
kinsho
  • 476
  • 3
  • 11