Javascript: z = z || [] throws an error when not using VAR - why?
Asked Answered
W

3

7

Out of just intellectual curiosity, why does javascript accept

var z = z || [];

to initialize z (as z may defined initially)

but without var, it throws an error (in global space)

z = z || [];

(if z is previously undefined)

In the global space you are not required to use VAR though I get it might be bad practice.

Before you say this is a duplicate of questions like

What is the purpose of the var keyword and when to use it (or omit it)?

Note the declaration that "If you're in the global scope then there's no difference."

Obviously that's not 100% true, given my working example.

Is this a quirk or is there legitimate logic?


adding a summary of the answer as I've learned it:

Thanks to Tim (see below) the key to my misunderstanding was not realizing this (fundamental of javascript)

var z; does absolutely nothing if z already exists

That's how this expression seems to have it both ways, if you incorrect assume that "var z" always initializes.

Starting from the left, "var z" simply makes sure z is defined but does not actually affect any existing value if it already exists. Then on the right, if z already exists, it is used, if not, the variable was just declared (but empty) so it won't be used but will not throw an error.

This is an excellent article on this kind of scoping and hoisting issue in Javascript: http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting

Many thanks to minitech and everyone else who contributed too!

Westphal answered 30/1, 2012 at 16:0 Comment(1)
As a general rule: copying the actual error message is normally more helpful than just writing "throws an error".Papist
C
6

z = z || [] will throw in any scope (global or not) where there is no z in the scope chain. The reason for this is that the expression first attempts to retrieve the value of an existing variable called z on the right hand side, which is an error when none exists.

The reason why var z = z || [] does not throw an error is that the variable z is created (if it does not already exist) before the expression is executed, an effect commonly known as hoisting.

On the other hand, assigning a value to an unresolved identifier (e.g. z = 2) will work without error in any scope (except in ECMAScript 5 strict mode, which forbids it and throws). If the identifier cannot be resolved, it will be added as a property of the final object in the scope chain, which is the global object, hence giving the appearance of creating a global variable.

Chesterfield answered 30/1, 2012 at 16:34 Comment(4)
Ah I think I get it. So "var z" is done first on the left side, like instructing "create a new z" but then it looks for an "old z" on the right side. In that order, "z" has just been initialized on the left, so the "z" on the right side is not undefined, though it's empty. Without the var, "z" on the right is not just empty, it's undefined. Hmm okay - if "var z" defines it as new on the left, how does this code preserve existing values then? It seems like it has it both ways, initializes on the left for the right, but also can keep the value on the right.Westphal
@ck_: Yes, I think you've got it, although the terminology needs some care because undefined is an actual value in JavaScript and is in fact the default value assigned to a variable (for example, var x; creates a variable called x with an initial value of undefined, which is distinct from a non-existent x that has never been declared). I'd go for "undeclared" or "non-existent".Chesterfield
@ck_: Regarding preserving existing values, var z; does absolutely nothing if z already exists, whatever its value.Chesterfield
var z; does absolutely nothing if z already exists - that explains my lack of understanding this problem in a nutshell and why I was confused. I always assumed var z; would wipe out an existing variable and initialize it to empty or null or similar. Now I get this 100% and in hindsight seems like a dumb question!Westphal
B
10

The effect is correct. var will always declare its "operands" right away, whereas when you don't declare it, your script attempts to use an undefined variable and throws an error.

If you're in global scope, you can assign to a nonexistent variable and it will have the same effect as declaring it, bad practice as that may be. Of course, in your case, it's undefined. This being said, although it may be out of intellectual curiosity, you would never write

var z = z || [];

because it makes no sense to do so. Rather, you might do:

if(!window.z) {
    window.z = [];
}

. In fact, when I declare things in the global scope (which is never ;)) I use window.something instead because it makes my intent more clear.

Burnout answered 30/1, 2012 at 16:4 Comment(2)
when you don't declare it, your script attempts to use an undefined variable - wait you are saying z=z accesses z but var z=z doesn't? The second z is still in global scope as is the first if it's outside a function. (puzzled)Westphal
@ck_: The difference is that using var causes the z variable to be created before any statements are executed.Chesterfield
C
6

z = z || [] will throw in any scope (global or not) where there is no z in the scope chain. The reason for this is that the expression first attempts to retrieve the value of an existing variable called z on the right hand side, which is an error when none exists.

The reason why var z = z || [] does not throw an error is that the variable z is created (if it does not already exist) before the expression is executed, an effect commonly known as hoisting.

On the other hand, assigning a value to an unresolved identifier (e.g. z = 2) will work without error in any scope (except in ECMAScript 5 strict mode, which forbids it and throws). If the identifier cannot be resolved, it will be added as a property of the final object in the scope chain, which is the global object, hence giving the appearance of creating a global variable.

Chesterfield answered 30/1, 2012 at 16:34 Comment(4)
Ah I think I get it. So "var z" is done first on the left side, like instructing "create a new z" but then it looks for an "old z" on the right side. In that order, "z" has just been initialized on the left, so the "z" on the right side is not undefined, though it's empty. Without the var, "z" on the right is not just empty, it's undefined. Hmm okay - if "var z" defines it as new on the left, how does this code preserve existing values then? It seems like it has it both ways, initializes on the left for the right, but also can keep the value on the right.Westphal
@ck_: Yes, I think you've got it, although the terminology needs some care because undefined is an actual value in JavaScript and is in fact the default value assigned to a variable (for example, var x; creates a variable called x with an initial value of undefined, which is distinct from a non-existent x that has never been declared). I'd go for "undeclared" or "non-existent".Chesterfield
@ck_: Regarding preserving existing values, var z; does absolutely nothing if z already exists, whatever its value.Chesterfield
var z; does absolutely nothing if z already exists - that explains my lack of understanding this problem in a nutshell and why I was confused. I always assumed var z; would wipe out an existing variable and initialize it to empty or null or similar. Now I get this 100% and in hindsight seems like a dumb question!Westphal
L
4

You may assign to an undeclared variable z = 123; however you may not attempt to read one which is what z = z || [] does.

Librettist answered 30/1, 2012 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.