Javascript
Difference between variable declaration syntaxes in Javascript including global variables
JavaScript, the dynamic language powering the web, offers several ways to declare variables. Understanding these nuances, particularly the differences between var, let, and const, and how they interact with global scope, is crucial for writing clean, efficient, and predictable JavaScript code. Choosing the right declaration method significantly impacts a variable’s behavior, affecting its accessibility and modifiability throughout your script. This post delves into the core differences between these declaration types, exploring their scope, hoisting behavior, and best-use cases. Mastering these distinctions will elevate your JavaScript programming skills and help you avoid common pitfalls.
The Reign of var
var was the traditional way to declare variables in JavaScript for many years. Its scope is either function-scoped or globally-scoped, meaning a variable declared with var inside a function is accessible throughout that function, while a var declared outside any function has global scope. This can sometimes lead to unexpected behavior, especially in larger projects.
One key characteristic of var is hoisting. This means the declaration is moved to the top of its scope during compilation. While the declaration is hoisted, the initialization isn’t. This can lead to accessing a var variable before its assignment, resulting in an undefined value rather than an error. This can make debugging tricky.
Example: javascript console.log(x); // Outputs undefined var x = 10;
Embracing Modern JavaScript with let
Introduced in ES6 (ECMAScript 2015), let offers a more controlled approach to variable declaration. Unlike var, let has block scope. This means variables declared with let are only accessible within the block of code (defined by curly braces {}) they are declared in. This promotes better code organization and reduces the risk of unintended variable overwriting.
let variables are also hoisted, but unlike var, accessing a let variable before its initialization results in a ReferenceError. This stricter behavior helps catch errors early in the development process.
Example: javascript console.log(y); // Throws ReferenceError let y = 20;
The Constant Power of const
Also introduced in ES6, const is used to declare variables with values that should not be reassigned. Similar to let, const has block scope and is hoisted, but accessing it before initialization throws a ReferenceError. The key difference is that once a const variable is assigned a value, it cannot be changed. This is particularly useful for declaring constants or values that shouldn’t be modified accidentally.
It’s important to note that const does not create immutable objects. While the variable itself cannot be reassigned, the properties of an object declared with const can still be modified.
Example: javascript const z = 30; z = 40; // Throws TypeError
Navigating Global Variables
Variables declared outside any function or block have global scope. They can be accessed from anywhere within your JavaScript code. While sometimes necessary, excessive use of global variables can make code harder to maintain and debug due to potential naming conflicts and unintended side effects. It’s generally recommended to minimize the use of global variables whenever possible.
In browsers, global variables become properties of the window object. You can explicitly create a global variable by assigning a value to a property of the window object. However, it’s generally best practice to avoid this unless absolutely necessary.
Example: javascript window.myGlobal = “Hello, world!”; console.log(myGlobal); // Outputs “Hello, world!”
- Use const for values that should not change.
- Prefer let over var for block-scoped variables.
- Identify the purpose of your variable.
- Choose the appropriate declaration type (const, let, or var).
- Initialize the variable.
According to MDN Web Docs, “Global variables are generally discouraged because they can easily lead to naming collisions and make it harder to reason about code.” Learn more about JavaScript declarations.
Choosing the right variable declaration type (var, let, or const) is essential for writing clean, maintainable, and predictable JavaScript code. Consider the scope and mutability requirements of your variables to make informed decisions. Prioritize const for constants, let for block-scoped variables, and minimize the use of global variables.
[Infographic Placeholder]
Learn More about ES6 featuresFAQ
Q: What is the primary difference between let and const?
A: Both let and const have block scope. The key difference is that const variables cannot be reassigned after initialization, whereas let variables can.
By understanding the distinctions between var, let, const, and global variables, you can write more robust and efficient JavaScript. Leveraging the strengths of each declaration method allows for better code structure, improved readability, and fewer unexpected behaviors. Continue exploring JavaScript’s intricacies and stay up-to-date with best practices to further refine your coding skills. Explore related topics such as scope, closures, and the evolution of JavaScript to gain a deeper understanding of the language. Take the time to experiment with different variable declarations in your own projects to solidify your knowledge and enhance your coding style. Learn more about scope. See a comparison of var, let, and const.
Question & Answer :
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?
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
Those statements explained
1. 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).
1.1 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.)
1.2 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:
- Makes it a runtime error if you try to assign to the constant (and most IDEs will flag it up for you more proactively than that).
- Documents its unchanging nature for other programmers.
- Lets the JavaScript engine optimize on the basis that the
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):
2 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).
3 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).
4 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.
Deleting properties
What do I mean by “deleting” or “removing” a? Exactly that: Removing the property (entirely) via the delete keyword:
(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.)
When var happens
Preface: 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:
When let and const happen
let 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:
varalways applies to the entire execution context (throughout global code, or throughout function code in the function where it appears; it jumps out of blocks), butletandconstapply only within the block where they appear. That is,varhas function (or global) scope, butletandconsthave block scope.- Repeating
var ain the same context is harmless, but if you havelet a(orconst a), having anotherlet aor aconst aor avar ais 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:
Avoid cluttering global scope - use modules
Global scope is very, very cluttered. It has (at least):
- Lots of global variables created via the spec (like
undefinedandNaNwhich, oddly, are globals rather than keywords; miscellanous global functions) - (On browsers) Variables for all DOM elements with an
idand many with aname(provided theid/namevalue is a valid identifier; otherwise, they’re just properties onwindowbut not global variables) - (On browsers) Variables for
window-specific things, likename,location,self… - Variables for all global-scope
varstatements - Variables for all global-scope
let,const, andclassstatements
All of those globals are ripe with opportunities for conflicts with your code, such as this classic example on browsers:
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: