foreach

Topics: General, Language Specification
Oct 3, 2012 at 6:39 PM

With arrays being an important part of JavaScript, it's always been frustrating that iterating one requires a for loop. Would it be possible to add a foreach construct which would essentially transform into a for loop? or into an ES5 function call? depending on compiler option.

Coordinator
Oct 3, 2012 at 9:28 PM

There is support for a foreach style for loop in ES5 (which TS supports):

var x = [1, 2, 3];

for (var i in x) {
 document.write(x[i]);
}

Oct 3, 2012 at 9:50 PM

jon, the ES5 foreach loop works like this:

var x = [1, 2, 3];

x.forEach(function(v) {
  document.write(v);
});

Oct 3, 2012 at 10:00 PM

Does TypeScript require ES5? I thought it required only ES3.

Coordinator
Oct 3, 2012 at 10:16 PM

@mateo2: I believe the for-in statement has been part of ECMAScript at least since v3, looking at the spec. 

@EisenbergEffect: TypeScript compiles to ES3 except when you ask for a feature that's supported only in ES5 mode (like using property accessors).

 

Oct 3, 2012 at 10:25 PM

Ok. That's good to know about compilation. But, I don't think it's a recommended good practice to use for/in over arrays.

Oct 4, 2012 at 6:53 AM

The downside to using the syntax

var x = [1, 2, 3];

for(var v in x) {
    document.write(v);
}

is that in TypeScript doing so vs. accessing the index property means you lose any type inference for value of variable 'v'. It would be great if TypeScript could deal with this in one of two ways:

  1. Expanding for-in statements on arrays into .forEach or alternatively optimized for-loop statements with type safety on the current value.
  2. Introducing a dedicated foreach keyword (so as to not replace JS syntax) that expands to .forEach or optimized for-loop with type safety on the current value.
Oct 4, 2012 at 7:01 AM
EisenbergEffect wrote:

Ok. That's good to know about compilation. But, I don't think it's a recommended good practice to use for/in over arrays.


No... It'll bite you in IE8 and below.

Oct 4, 2012 at 7:02 AM

As @mateo2 said, [].forEach(iterator : (element: any, i: number, array: Array) is the native ES5 way to iterate over elements in an array.

Using `in` for arrays should be avoided. See http://jsfiddle.net/tMhwR/

for...in iterates over enumerable keys of an object, rather than elements of an array.

 

@EisenbergEffect specifically, the TypeScript compiler includes ES3 polyfills for ES5 array methods when targeting ES3.

Oct 4, 2012 at 7:15 AM

@Tharaxis: Why can type inference not handle this case? Currently, type inference makes strings out of all elements, but I think this looks like a bug.

@EisenbergEffect: Why it's not a recommended good practice? I'd rather have one construct to enumerate over collection-like objects. As Tharaxis said, the compiler is able to choose the more efficient implementation.

Best Regards,
Stefan

Oct 4, 2012 at 7:22 AM
Edited Oct 4, 2012 at 7:30 AM

Hi Stefan,

I think the reason for that is likely the way for-in works vs. a standard for loop. With arrays in JS, doing a for-in will return all the values but also any additional properties you have added to that array, so for example:

var x = [1, 2, 3];
x.foo = "this";

for(var v in x) {
    document.write(v);
}

will produce the output 1, 2, 3, "foo" instead of just 1, 2, 3. Since the value of a for-in array can return any combination of whatever values + strings that exist on that object, type inference will probably always break down in some way, hence my recommendation that arrays be treated differently (and monotyped preferably since JS arrays can also be [1, 2, "foo", "bar", 4] and so on).

EDIT:

Actually, thinking about it now, I suspect the dynamic behaviour of arrays (any indexed value can have any type) is the primary reason it can't really work.

There is no direct way of knowing what exact value you're accessing within the for-in closure (is the v in x the first element which is a number or the 10th element which is a string?) however, accessing via index (such as in a standard for-loop) gives the compiler/parser a direct line to what value you're trying to retrieve and can therefore light up with valid type information.

Oct 4, 2012 at 7:31 AM
Edited Oct 4, 2012 at 7:36 AM

Thanks for your explanation, Tharaxis; I appreciate it.

The code does not work with the compiler used by the playground; it is not allowed to set "foo" on an array.

Update: Hm, hinting about a type when an array is indexed? I think this won't work for many cases. What about an array passed as function parameter?

Oct 4, 2012 at 8:01 AM
Edited Oct 4, 2012 at 8:04 AM

Hi Stefan,

At least TypeScript catches that issue then, although doing a x["foo"] = "bar"; still works and will then result in the same output on my example.

Also my example is actually wrong (prime example of bad assumptions when using JS), what the for-in loop will produce is the list of keys, so if you have an array:

var x = [1, 4, 5, 6, 1];
x["foo"] = "bar";

for(var v in x) {
    document.write(v);
}

you will end up with the output 0, 1, 2, 3, 4, "foo" instead of the actual values (which would be expected when using a for-in on an array).

On the other hand, doing:

var x = [1, 4, 5, 6, 1];
x["foo"] = "bar";

for(var v in x) {
    document.write(x[v]);
}

will result in the output 1, 4, 5, 6, 1, "bar". Again, not the expected output.

Would love for TypeScript to have some kind of elegant solution in place someday :).

Oct 4, 2012 at 11:11 AM

It's nice to know that a polyfill is included when using an ES5 method and targeting an ES3 browser. However, I'd still like to have a dedicated foreach that is compiled into a for loop since that is more performant than the forEach method. A lot of the code I'm writing these days is related to game programming or at least graphics/canvas rendering, so loops are common and performance is important.

Coordinator
Oct 4, 2012 at 11:49 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.