I understand the issues with global scope and javascript variables and their general undesirability; and that you find them everywhere. The following (in a browser) is equivalent:
var foo = 3; // foo === 3, window.foo === 3
bazz = 10; // bazz === 10, window.bazz === 10
Declaring a variable with the var keyword in the global scope is the same as declaring it without a var anywhere in the code: your variable is assigned to the root (window) object.
One technique I see a lot (e.g. setting up google analytics) is this:
var _gaq = _gaq || [];
... and I follow the reasoning that if _gaq has been declared use that, if not create it as an array. It allows careless coding not to overwrite any values already assigned to the global variable _gaq.
What I don't understand is why this throws an error:
_gaq = _gaq || [];
They look equivalent to me: _gaq should take the value of _gaq or be initialised as an array. But it throws a reference error - my question is: why are they different?
I understand the issues with global scope and javascript variables and their general undesirability; and that you find them everywhere. The following (in a browser) is equivalent:
var foo = 3; // foo === 3, window.foo === 3
bazz = 10; // bazz === 10, window.bazz === 10
Declaring a variable with the var keyword in the global scope is the same as declaring it without a var anywhere in the code: your variable is assigned to the root (window) object.
One technique I see a lot (e.g. setting up google analytics) is this:
var _gaq = _gaq || [];
... and I follow the reasoning that if _gaq has been declared use that, if not create it as an array. It allows careless coding not to overwrite any values already assigned to the global variable _gaq.
What I don't understand is why this throws an error:
_gaq = _gaq || [];
They look equivalent to me: _gaq should take the value of _gaq or be initialised as an array. But it throws a reference error - my question is: why are they different?
Share Improve this question asked Mar 13, 2013 at 14:52 Party ArkParty Ark 1,1599 silver badges23 bronze badges 2-
1
They're not exactly equivalent, they differ in behavior when you try to
delete
them. – Bergi Commented Mar 13, 2013 at 14:59 -
The best explanation of 'undeclared assignments' and why they're different to global assignments (
foo=0
!=var foo=0
in the global scope) I can find is on this blog which has helped me understand the intrinsic difference. – Party Ark Commented Mar 13, 2013 at 16:04
4 Answers
Reset to default 5You can never read variables which have not been declared, and that's what you are trying with the expression _gaq || []
in the last case.
In this case
_gaq = _gaq || [];
_qaq
has not been declared before and when the right hand side (_gaq || []
) is evaluated, it throws the error.
Here is step by step explanation of what is going on in this case:
The assignment operator is described in section 11.13.1 of the specification:
The production
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
is evaluated as follows:1. Let
lref
be the result of evaluatingLeftHandSideExpression
.
2. Letrref
be the result of evaluatingAssignmentExpression
.
...
LeftHandSideExpression
is _gaq
, the AssignmentExpression
is _gqa || []
.
So first _qaq
is evaluated, which results in an unresolvable reference, since the variable _gaq
is not declared. This evaluation does not throw an error.
Then _gqa || []
is evaluated. This is a LogicalORExpression
and is described in section 11.11 as LogicalORExpression || LogicalANDExpression
. In this case, LogicalORExpression
, the left hand side, is _gaq
and LogicalANDExpression
, the right hand side, is []
.
The expression is evaluated as follows:
1. Let
lref
be the result of evaluatingLogicalORExpression
.
2. Letlval
beGetValue(lref)
.
...
We already know that lref
will be an unsolvable reference because _gaq
was not declared. So lets have a look what GetValue
is doing (defined in section 8.7.1, V
is the value passed to GetValue
):
1. If
Type(V)
is notReference
, returnV
.
2. Letbase
be the result of callingGetBase(V)
.
3. IfIsUnresolvableReference(V)
, throw aReferenceError
exception.
...
As you can see, a ReferenceError
error is thrown in the third step of this procedure, which in turn gets executed by evaluating the right hand side of the assignment, and this is where the error is thrown.
So, why does this not happen with var _gaq = _gaq || [];
?
This line:
var _gaq = _gaq || [];
is actually
var _gaq;
_gaq = _gaq || [];
because of something called hoisting [MDN]. That means when _gaq
is evaluated, it will not result in an unresolvable reference, but a reference with value undefined
.
(If the variable _gaq
is already declared (and potentially has a value), then var _gaq
won't have any effect.)
If you want to create _gaq
globally from inside a function, do it explicitly by referring to window
:
window._gaq = window._gaq || [];
If the _gaq
to the right of =
was not previous declared with var
, it will throw a reference error. You're trying to reference a variable that does not exist. The "magic" just works for assigning to non-existing variables.
It's just like saying x = y + 1
; the problem is not the non-existing x
, but the non-existing y
.
This will throw an error because the variable is not found in the context-chain of the current execution context. Accessing a variable that cannot be resolved will result in an error.
_gaq = _gaq || [];
This, on the other hand, will try to resolve _gac trying to look for it as a member of the window object, wich turns out to be the global context 'holder' object. The difference in this case is that it will not throw an error, but the the window._gaq
will return undefined, because the property is not found in the window object.
_gaq = window._gaq || [];
So, since the global context object is the window (when speaking about browsers), if _gaq is defined, this two statements will have the same effect. The difference will be noticed when _gaq is not defined, and accessing to it using the window object can have the advantage of not getting an error.
There underlying concept here is hoisting and it is often tricky in practice. Variables are defined at the top of a function's scope while the assignment still occurs where it is defined.
With this var _gaq = _gaq
the variable is actually defined before the actual line of code for the assignment is executed. This means that when the assignment occurs the variable is already on the window scope. Without the var in front of _gaq no hoisting occurs and thus _gaq does not yet exists when the assignment runs causing the reference error.
If you want to see this in action you can check when the _gaq variable is added to the window object with the following:
function printIsPropOnWindow(propToCheck)
{
for (prop in window)
{
if (prop == propToCheck)
{
console.warn('YES, prop ' + prop + ' was on the window object');
return;
}
}
console.warn('NO, prop ' + propToCheck + ' was NOT on the window object');
}
try {
var _gaq = function() {
printIsPropOnWindow("_gaq");
return a;
}();
} catch (ex) {
printIsPropOnWindow("_gaq");
}
_gaq = "1";
printIsPropOnWindow("_gaq");
If you try this once as is and once with the var before _gaq removed you will see very different results because one has the _gaq hoisted and the other does not.