You have more context (in that first example) and so the foo and bar indexes make great sense.
i and j really make more sense (as i and j) when used in their more mathematical connotation: indexes into a multidimensional array/object.
for (i = 0; i < thing.length; i++) {
for (j = 0; j < thing[i].length; j++) {
thing[i][j]; // this is very close to the mathematical use of indexes
}
}
Makes perfect sense because i and j are being used to index into the same thing.i and j would be poor fits for your first example because they don't index into the same thing anymore. You've introduced additional levels of indirection, to rewrite your first example using i and j (and illustrate why this is an awkward construct):
for (i = 0; i < foos.length; i++) {
for(j = 0; j < foos[i].bars.length; j++) {
foos[i].bars[j];
}
}
It's not wrong, but it is awkward and unclear as the code grows larger it becomes easier to misuse i, j, and subsequent index variables.=====
And of course, you're absolutely correct: foreach or equivalent is a better construct. It states the intent of the code, rather than the how of the code. This keeps your code closer to the semantics of your problem specification.