📖 Deeper dive reading:
Understanding JavaScript scope is essential for making your programs run as you expect. Scope is defined as the variables that are visible in the current context of execution. JavaScript has four different types of scope:
- Global - Visible to all code
- Module - Visible to all code running in a module
- Function - Visible within a function
- Block - Visible within a block of code delimited by curly braces
Initially JavaScript used the keyword var to declare a variable. The problem with var, unlike const or let, is that it ignores block scope. Variables declared with var are always logically hoisted to the top of the function. For example, the following code shows the same variable name being used within different enclosing scopes. However, because var ignores block scope the for loop is simply assigning a new value to x rather than declaring a new variable that has the same name.
var x = 10;
console.log('start', x);
for (var x = 0; x < 1; x++) {
console.log('middle', x);
}
console.log('end', x);
// OUTPUT: start 10
// middle 0
// end 1Important
It rarely makes sense to use var. It is strongly suggested that you only use const and let unless you fully understand why you are using var.
The keyword this represents a variable that points to an object that contains the context within the scope of the currently executing line of code. The this variable is automatically declared and you can reference this anywhere in a JavaScript program. Because the value of this depends upon the context in which it is referenced, there are three different contexts to which this can refer:
- Global - When
thisis referenced outside a function or object it refers to theglobalThisobject. The globalThis object represents the context for runtime environment. For example, when running in a browser, globalThis refers to the browser's window object. - Function - When
thisis referenced in a function it refers to the object that owns the function. That is either an object you defined or globalThis if the function is defined outside of an object. Note that when running in JavaScript strict mode, a global function's this variable is undefined instead of globalThis. - Object - When
thisis referenced in an object it refers to the object.
'use strict';
// global scope
console.log('global:', this);
console.log('globalThis:', globalThis);
// function scope for a global function
function globalFunc() {
console.log('globalFunctionThis:', this);
}
globalFunc();
// object scope
class ScopeTest {
constructor() {
console.log('objectThis:', this);
}
// function scope for an object function
objectFunc() {
console.log('objectFunctionThis:', this);
}
}
new ScopeTest().objectFunc();Running the above code in a browser results in the following.
global: Window
globalThis: Window
globalFunctionThis: undefined
objectThis: ScopeTest
objectFunctionThis: ScopeTest
Note that if we were not using JavaScript strict mode then globalFunctionThis would refer to Window.
A closure is defined as a function and its surrounding state. That means whatever variables are accessible when a function is created are available inside that function. This holds true even if you pass the function outside of the scope of its original creation.
Here is an example of a function that is created as part of an object. That means that function has access to the object's this pointer.
globalThis.x = 'global';
const obj = {
x: 'object',
f: function () {
console.log(this.x);
},
};
obj.f();
// OUTPUT: objectArrow functions are a bit different because they inherit the this pointer of their creation context. So if we change our previous example to return an arrow function, then the this pointer at the time of creation will be globalThis.
globalThis.x = 'global';
const obj = {
x: 'object',
f: () => console.log(this.x),
};
obj.f();
// OUTPUT: globalHowever, if we make function in our object that returns an arrow function, then the this pointer will be the object's this pointer since that was the active context at the time the arrow function was created.
globalThis.x = 'global';
const obj = {
x: 'object',
make: function () {
return () => console.log(this.x);
},
};
const f = obj.make();
f();
// OUTPUT: object