As you said, JavaScript only has function scope. Variable declarations are hoisted to the top of the scope in which they are declared. Your example is interpreted like this:
var i, j, len; //Declarations are hoisted...
for (i = 0, len = x.length; i < len; i++) { //Assignments happen in place
for (j = 0, len = y.length; j < len; j++) {
}
}
As for this part:
if I change j to i, the outer for loop terminates after one iteration
If you replace j
with i
, then after the first iteration of the inner loop, i
will be y.length - 1
, and the outer loop will either continue or stop, depending on the difference between x.length
and y.length
.
If you're interested in the real explanation of the internal workings, the ECMAScript spec (Declaration Binding Instantiation) covers it in detail. To summarise, every time control enters a new execution context, the following happens (a lot more than this happens, but this is part of it):
For each VariableDeclaration and VariableDeclarationNoIn d in code, in
source text order do
- Let dn be the Identifier in d.
- Let varAlreadyDeclared be the result of calling env’s HasBinding concrete
method passing dn as the argument.
- If varAlreadyDeclared is false, then
- Call env’s CreateMutableBinding concrete method passing dn and
configurableBindings as the arguments.
- Call env’s SetMutableBinding
concrete method passing dn, undefined, and strict as the arguments.
This means that if you declare a variable more than once per execution context, it will effectively be ignored.