Scope in JavaScript
What is Scope?
Scope is the current context of execution. It determines the accessibility of variables and functions in a particular part of code. Scope is controlled entirely by the placement of functions, blocks, and variable declarations, in relation to one another.
Scope in JavaScript is determined at compile time and should not be affected by runtime (unless you are using eval, which is a bad practice). During “lexing” stage of compilation, lexical scope is created. Also, a map is created of all the lexical scopes that lays out what the program will need at run time.
Consider the below code:
// outer/global scope: 1
var persons = [
{ id: 1, name: "Max" },
{ id: 2, name: "Sara" },
{ id: 3, name: "Lincy" },
{ id: 4, name: "Alicia" }
];
function getName(id) {
// function scope: 2
for (let person of persons) {
// loop scope: 3
if (person.id == id) {
return person.name;
}
}
}
var nextPerson = getName(3);
console.log(nextPerson); // Lincy
Here, we have 3 scopes.
- Global scope which has three identifiers: persons, getName, nextPerson.
- function scope getName(…) which has only one identifier id
- for loop scope which has one identifier person. persons in for expression belongs to global scope.
Let’s see how this program executes. persons is defined at line1, getName is defined after that. getName is called. It creates id varibale which has scope 2. Then a for loop starts and it creates a variable person in scope 3. It also references persons which was not declared in scope 3. So, the lookup for persons starts moving outwards. In scope 2 as well, the declaration for persons can not be found. It goes one more step out and reaches the global scope where it was actually declared. So this is how the scope lookup works. It moves outwards.
To summarize this, when the JS program runs, the reference of a variable must be resolved. If the variable is not declared in the current scope, the next outer scope will be consulted. This process of stepping out one level of scope nesting continues until either a matching variable declaration can be found, or the global scope is reached and there’s nowhere else to go.
What would happen if the variable which is being used as a source cannot be resolved even till the global scope? A Reference error will be thrown. And there is a common misunderstanding between reference error and a variable being undefined. Let’s see it in detail in the next section.
Reference error vs undefined
This is what a declaration looks like:
var a;let b;
To proceed further let’s first understand what is source and target in an expression.
A variable is a source if its value is being assigned to something or is being used in some operation.
A variable is a target if a value is getting assigned to it.
If declaration of a source variable cannot be found in the current or the parent scope(s), it results in reference error. The variable is undeclared, unknown or missing.
var a = b;
/* Reference Error since b is a source variable and it was never declared and is unknown. */
If the undeclared variable is target and the code is running in strict mode, then again reference error is thrown.
'use strict';var name = "Suzy";anotherName = name;
/* Reference Error since in strict mode before using anotherName as target, we first need to declare it */
If the above code was not running in strict mode, the outcome would have been completely different and it may lead to memory leak. So what would happen in non strict mode? A global variable will get created to fulfill the target assignment! This is the cause of accidental global variable creation. So, always declare your variables before using them or better run your code in strict mode.
var name = "Suzy";anotherName = name;
// No error here, anotherName got created as global variableconsole.log(anotherName); // Suzy
So now what does undefined mean? ‘undefined’ means a variable was found(it was declared) but the variable has no other value in it at the moment so it defaults to undefined value.
var a;
console.log(a); // undefined
Here, a does not have any value and hence it defaults to undefined. There is no error but the value itself is undefined.
Global Scope
Global scope is located in the outermost portion of a file; that is, not inside any function or other block. Global scope is different in different JS environments. In browser, it is window object, in web worker it is self object and in node it is global object.
window
var name = "Kim";
function hello() {
console.log(`Hello, ${ window.name }!`);
}
window.hello();
// Hello, Kim!
Both name and hello identifiers are declared in the global scope. If you access window object in browser environment, you will find both of them as properties of window. There is one behavior to note here. If the variable is declared using var, then it gets added to window object’s property as well. On the other hand, if the global variable is created using let or const, then it does become a global variable but it does not get added in the window object.
window.something = 42;
let something = "Kyle";
console.log(something);
// Kyle
console.log(window.something);
// 42
something in line 2 shadows something global object property.
A simple way to avoid this gotcha with global declarations: always use var
for globals. Reserve let
and const
for block scopes.
DOM Globals
DOM element with an id attribute automatically creates a global variable that references it.
Consider this html:
<div id="my-todo-list"></div>
<div id="first">Write a book</div>
And the JS for that page could include:
first;
// <div id="first">Write a book</div>window["my-todo-list"];
// <div id="my-todo-list"></div>
This is old legacy browser behavior. Never use these global variables even though they will always be silently created.
Global name variable
var name= 42;console.log(name, typeof name); // "42" string
window.name is a pre-defined “global” in a browser context.
But why does the type of name is string when we had assigned a number to it? name is actually a pre defined getter/setter on the window object, which insists on its value being a string value.
We used var which does not shadow the pre-defined name global property. Had we used let name, we would have shadowed window.name with a separate global name variable .
let name = 42;
console.log(name, typeof name); // 42 number
console.log(window.name, typeof window.name); // "" string
That’s all folks for this article. For me, DOM globals and window name was something new. Hope you also learnt something new today.