Reputation: 57891
Is there any difference between declaring a variable:
var a=0; //1
...this way:
a=0; //2
...or:
window.a=0; //3
in global scope?
Upvotes: 305
Views: 272606
Reputation: 1074495
Yes, there are a couple of differences, though in practical terms they're not usually big ones (except for your #2 — a = 0;
— which A) I strongly recommend not doing, and B) is an error in strict mode).
There's a fourth way, and as of ES2015 (ES6) there's two more. I've added the fourth way at the end, but inserted the ES2015 ways after #1 (you'll see why), so we have:
var a = 0; // 1
let a = 0; // 1.1 (new with ES2015)
const a = 0; // 1.2 (new with ES2015)
a = 0; // 2
window.a = 0; /*or*/ globalThis.a = 0; // 3
this.a = 0; // 4
var a = 0;
This creates a global variable which is also a property of the global object, which we access as window
on browsers (or via the globalThis
global added in ES2020, or via this
at global scope). Unlike some other properties, the property cannot be removed via delete
.
In specification terms, it creates an identifier binding on the Object Environment Record for the global environment. That makes it a property of the global object because the global object is where identifier bindings for the global environment's Object Environment Record are held. This is why the property is non-deletable: It's not just a simple property, it's an identifier binding, and identifiers can't be removed.
The binding (variable) is defined before the first line of code runs (see "When var
happens" below).
The property this creates is enumerable (except on the very obsolete IE8 and earlier).
let a = 0;
This creates a global variable which is not a property of the global object. This is a new thing as of ES2015.
In specification terms, it creates an identifier binding on the Declarative Environment Record for the global environment rather than the Object Environment Record. The global environment is unique in having a split Environment Record, one for all the old stuff that goes on the global object (the Object Environment Record) and another for all the new stuff (let
, const
, and the functions created by class
) that don't go on the global object, but go in the global environment's Declarative Environment Record instead.
The binding is created before any step-by-step code in its enclosing block is executed (in this case, before any global code runs), but it's not accessible in any way until the step-by-step execution reaches the let
statement. Once execution reaches the let
statement, the variable is accessible. (See "When let
and const
happen" below.) The time between the binding being created (on entry to the scope) and becoming accessible (code execution reaching the let
) is called the Temporal Dead Zone [TMZ]. While the binding is in that state, any attempt to read from it or write to it is a runtime error.
(The specification's terminology for whether the binding is accessible is whether it's "initialized," but don't confuse that use of "initialized" with having an initializer on the let
statement [let a = 10;
vs. just let a;
]; they're unrelated. The variable defined by let a;
is initialized with undefined
once the let
is reached.)
const a = 0;
Creates a global constant, which is not a property of the global object.
A const
binding is exactly like a let
binding (including the TMZ and such) except it has a flag saying its value cannot be changed. One implication of that is you must provide an initializer (the = value
part) to provide the initial (and never-changing) value for the const
.
Using const
does three things for you:
const
's value won't change (without having to track whether it's written to later or not — e.g., doesn't have to check if it's effectively constant).It's important to understand that the const
's value never changing doesn't mean that an object the const
refers to is immutable. It isn't. It just means that the value of the const
can't be changed so it refers to a different object (or contains a primitive):
// This is fine:
const x1 = {a: 1};
console.log(x1.a); // 1
x1.a = 2;
//^^^^^^−−− No problem, just changing the object's state, not the value in the `const` (the object reference)
console.log(x1.a); // 2
// This is not:
const x2 = {a: 1};
console.log(x2.a); // 1
x2 = {a: 2};
// ^−−−−−−− Error here ("TypeError: Assignment to constant variable"),
// you can't change the value of a `const`
console.log(x2.a);
a = 0;
Don't do this. 😊 It's assigning to a completely undeclared identifier. In loose mode (the only mode before ES5), it creates a property on the global object implicitly. On my old blog, I call this The Horror of Implicit Globals. Thankfully, they fixed it with strict mode, added in ES5 and the default in new kinds of scopes (inside modules, inside class
constructs, etc.). Strict mode makes assigning to an undeclared identifier the error it always should have been. It's one of several reasons to use strict mode.
Since it creates a normal property, you can delete
it.
The property this creates is enumerable (except on the very obsolete IE8 and earlier).
window.a = 0;
or globalThis.a = 0;
This creates a property on the global object explicitly, using the window
global (on browsers) or the globalThis
global that refers to the global object. As it's a normal property, you can delete it.
This property is enumerable (even on the very obsolete IE8 and earlier).
this.a = 0;
Exactly like #3, except we're referencing the global object through this
instead of the globals window
or globalThis
. This works because this
at global scope is the "global" this
value. This is true even in strict mode. (Strict mode changes the this
used when you call a function without supplying this
, such as when you do fn()
, but not what this
is at global scope.) Note that it has to really be global scope. The top-level scope of modules is not global scope (it's module scope), and at module scope this
is undefined
.
What do I mean by "deleting" or "removing" a
? Exactly that: Removing the property (entirely) via the delete
keyword:
window.a = 0;
console.log(`"a" in window? ${"a" in window}`); // "a" in window? true
delete window.a;
console.log(`"a" in window? ${"a" in window}`); // "a" in window? false
delete
completely removes a property from an object. You can't do that with properties added to window
indirectly via var
, the delete
is either silently ignored or throws an exception (depending on whether you're in strict mode).
(Minor note: The very obsolete IE8 and earlier, and the obsolete IE9-IE11 in their broken "compatibility" mode, wouldn't let you delete window
properties even if you should have been allowed to.)
var
happensPreface: var
has no place in new code. Use let
or const
instead. But it's useful to understand var
for the purposes of understanding old code you run across.
The variables defined via the var
statement are created before any step-by-step code in the execution context is run, and so the variable (and its property on the global object) exists well before the var
statement.
This can be confusing, so let's take a look. Here we have code trying to access a
and b
, followed by code in the middle creating them, and then code trying to access them again:
try {
console.log(a); // undefined
console.log(b); // ReferenceError: b is not defined
} catch (e) {
console.error(e);
}
var a = "ayy";
b = "bee"; // Don't do this, but I didn't want to use `let` or `const` in this example
try {
console.log(a); // "ayy"
console.log(b); // "bee"
} catch (e) {
console.error(e);
}
As you can see, the identifier a
is defined (with the value undefined
) before the first line runs, but the identifier b
isn't, so trying to read its value is a ReferenceError
. The statement var a = "ayy";
really deos two different things, at different times: On entry to the scope, it defines the identifier with the initial value undefined
(the var a
part), and later when it's reached in the execution of the code, it sets the value of a
(the a = "ayy"
part). Since a
is defined before the first line of code runs, we can use it (and see its undefined
value). This is known as "var
hoisting" because the var a
part is moved ("hoisted") to the top of the global scope or function scope where it appears, but the a = "ayy"
part is left in its original location. (See Poor misunderstood var
on my anemic old blog.)
let
and const
happenlet
and const
are different from var
in a couple of useful ways. The ways that are relevant to the question are A) that although the binding they define is created before any step-by-step code runs, it's not accessible until the let
or const
statement is reached; and B) as we've seen above, at global scope they don't create properties on the global object.
Re (A), while this using var
runs:
console.log(a); // undefined
var a = 0;
console.log(a); // 0
This using let
throws an error:
console.log(a); // ReferenceError: a is not defined
let a = 0;
console.log(a);
The other two ways that let
and const
differ from var
, which aren't really relevant to the question, are:
var
always applies to the entire execution context (throughout global code, or throughout function code in the function where it appears; it jumps out of blocks), but let
and const
apply only within the block where they appear. That is, var
has function (or global) scope, but let
and const
have block scope.
Repeating var a
in the same context is harmless, but if you have let a
(or const a
), having another let a
or a const a
or a var a
is a syntax error.
Here's an example demonstrating that let
and const
take effect immediately in their block before any code within that block runs, but aren't accessible until the let
or const
statement:
let a = 0; // (Doesn't matter whether this is `let`, `const`, or `var` [or even `class` or `function`])
console.log(a); // 0
if (true) {
console.log(a); // ReferenceError: a is not defined
let a = 1;
console.log(a);
}
Note that the second console.log
fails, instead of accessing the a
from outside the block, because within that block a
refers to the a
declared later in the block. But the console.log
statement occurs within the Temporal Dead Zone for that inner a
, so it causes an error.
Global scope is very, very cluttered. It has (at least):
undefined
and NaN
which, oddly, are globals rather than keywords; miscellanous global functions)id
and many with a name
(provided the id
/name
value is a valid identifier; otherwise, they're just properties on window
but not global variables)window
-specific things, like name
, location
, self
...var
statementslet
, const
, and class
statementsAll of those globals are ripe with opportunities for conflicts with your code, such as this classic example on browsers:
var name = 42;
console.log(name); // 42 - seems okay, but...
console.log(typeof name); // ...string?!?!!
Why is name
a string? Because it's an accessor property on window
for the name of the window object, which is always a string. (The equivalent with let
would work as expected, since the declarative environment record is conceptually nested within the object environment record, so the name
binding created with let
shadows the name
binding for the accessor property.)
Whenever possible, don't add to the mess. Use modules instead. Top-level scope in modules is module scope, not global scope, so only other code in your module sees those top-level declarations. You can share information between modules via export
and import
.
Before modules, we used "scoping" functions wrapped around our code:
// From the ES5 era
(function () {
var a = 0; // `a` is NOT a property of `window` now
function example() {
console.log(a); // Shows "0", because `example` can access `a`
}
example();
})();
Modules make that obsolete.
Upvotes: 581
Reputation: 10015
<title>Index.html</title>
<script>
var varDeclaration = true;
noVarDeclaration = true;
window.hungOnWindow = true;
document.hungOnDocument = true;
</script>
<script src="external.js"></script>
/* external.js */
console.info(varDeclaration == true); // could be .log, alert etc
// returns false in IE8
console.info(noVarDeclaration == true); // could be .log, alert etc
// returns false in IE8
console.info(window.hungOnWindow == true); // could be .log, alert etc
// returns true in IE8
console.info(document.hungOnDocument == true); // could be .log, alert etc
// returns ??? in IE8 (untested!) *I personally find this more clugy than hanging off window obj
Is there a global object that all vars are hung off of by default? eg: 'globals.noVar declaration'
Upvotes: 10
Reputation: 3927
Bassed on the excellent answer of T.J. Crowder: (Off-topic: Avoid cluttering window
)
This is an example of his idea:
Html
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="init.js"></script>
<script type="text/javascript">
MYLIBRARY.init(["firstValue", 2, "thirdValue"]);
</script>
<script src="script.js"></script>
</head>
<body>
<h1>Hello !</h1>
</body>
</html>
init.js (Based on this answer)
var MYLIBRARY = MYLIBRARY || (function(){
var _args = {}; // private
return {
init : function(Args) {
_args = Args;
// some other initialising
},
helloWorld : function(i) {
return _args[i];
}
};
}());
script.js
// Here you can use the values defined in the html as if it were a global variable
var a = "Hello World " + MYLIBRARY.helloWorld(2);
alert(a);
Here's the plnkr. Hope it help !
Upvotes: 8
Reputation: 3666
Keeping it simple :
a = 0
The code above gives a global scope variable
var a = 0;
This code will give a variable to be used in the current scope, and under it
window.a = 0;
This generally is same as the global variable.
Upvotes: 40
Reputation: 169411
In global scope there is no semantic difference.
But you really should avoid a=0
since your setting a value to an undeclared variable.
Also use closures to avoid editing global scope at all
(function() {
// do stuff locally
// Hoist something to global scope
window.someGlobal = someLocal
}());
Always use closures and always hoist to global scope when its absolutely neccesary. You should be using asynchronous event handling for most of your communication anyway.
As @AvianMoncellor mentioned there is an IE bug with var a = foo
only declaring a global for file scope. This is an issue with IE's notorious broken interpreter. This bug does sound familiar so it's probably true.
So stick to window.globalName = someLocalpointer
Upvotes: 5