All about scope
Scope
Scope defines the lifetime and visibility of a variable. Variables are not visible outside the scope in which they are declared.
Type of Scope JavaScript has
JavaScript has module scope, function scope, block scope, lexical scope and global scope.
- Global Scope
Variables defined outside any function, block, or module scope have global scope. Variables in global scope can be accessed from everywhere in the application.
When a module system is enabled it’s harder to make global variables, but one can still do it. By defining a variable in HTML, outside any function, a global variable can be created:
<script>
let GLOBAL_DATA = { value : 1};
</script>
console.log(GLOBAL_DATA);
2.Function Scope
Function scope means that parameters and variables defined in a function are visible everywhere within the function, but are not visible outside of the function.
Consider the next function that auto-executes, Immediately Invoked Function Expression.
Invoked Function Expression and is a function that runs immediately after its definition.
Variables declared with var have only function scope. Even more, variables declared with var are hoisted to the top of their scope. This way they can be accessed before being declared. Take a look at the code below:
function doThing(){
console.log(x);
var x = 1;
}
doThing(); //undefined
This does not happen for let. A variable declared with let can be accessed only after its definition.
3.Block Scope
Block scope is defined with curly braces. It is separated by { and }.
Variables declared with let and const can have block scope. They can only be accessed in the block in which they are defined.
Consider the next code that emphasizes let block scope:
let x = 1;
{
let x = 2;
}
console.log(x); //1
In contrast, the var declaration has no block scope:
var x = 1;
{
var x = 2;
}
console.log(x); //2
Another common problem with not having block scope is the use of an asynchronous operation like setTimeout() in a loop. The flowing loop code displays the number 5, five times.
(function run(){
for(var i=0; i<5; i++){
setTimeout(function logValue(){
console.log(i); //5
}, 100);
}
})();
The for loop statement, with the let declaration, creates a new variable locale to the block scope, for each iteration. The next loop code shows 0 1 2 3 4 5.
(function run(){
for(let i=0; i<5; i++){
setTimeout(function log(){
console.log(i); //0 1 2 3 4
}, 100);
}
})();
- Lexical Scope
Lexical scope is the ability of the inner function to access the outer scope in which it is defined.
(function autorun(){
let x = 1;
function log(){
console.log(x);
};
function run(fn){
let x = 100;
fn();
}
run(log);//1
})();
The log function is a closure. It refers the x variable from its parent function autorun(), not the one from the run() function.
The closure function has access to the scope in which it was created, not the scope in which it was executed.
The local function scope of autorun() is the lexical scope of the log() function.
- Scope chain
Every scope has a link to the parent scope. When a variable is used, JavaScript looks down the scope chain until it either finds the requested variable or until it reaches the global scope, which is the end of the scope chain.
let x0 = 0;
(function autorun1(){
let x1 = 1;
(function autorun2(){
let x2 = 2;
(function autorun3(){
let x3 = 3;
console.log(x0 + " " + x1 + " " + x2 + " " + x3);//0 1 2 3
})();
})();
})();
The autorun3() inner function has access to the local x3 variable. It has also access to the x1 and x2 variables from the outer functions and the x0 global variable.
Conclusion
Variables defined in global scope are available everywhere in the application.
In a module, a variable declared outside any function is hidden and not available to other modules unless it is explicitly exported.
Function scope means that parameters and variables defined in a function are visible everywhere within the function
Variables declared with let and const have block scope. var doesn’t have block scope.
Javascript Is Single Threaded, but can be used as a Asynchronous?
Javascript is a single threaded language. This means it has one call stack and one memory heap. As expected, it executes code in order and must finish executing a piece code before moving onto the next. It's synchronous, but at times that can be harmful. For example, if a function takes awhile to execute or has to wait on something, it freezes everything up in the meanwhile.
A good example of this happening is the window alert function. alert("Hello World")
You can't interact with the webpage at all until you hit OK and dismiss the alert. You're stuck.
So how do we get asynchronous code with Javascript then?
Well, we can thank the Javascript engine (V8, Spidermonkey, JavaScriptCore, etc...) for that, which has Web API that handle these tasks in the background. The call stack recognizes functions of the Web API and hands them off to be handled by the browser. Once those tasks are finished by the browser, they return and are pushed onto the stack as a callback.
Open your console and type window then press enter. You'll see most everything the Web API has to offer. This includes things like ajax calls, event listeners, the fetch API, and setTimeout. Javascript uses low level programming languages like C++ to perform these behind the scenes.
Let's look at a simple example, run this code your console:
console.log("first")
setTimeout(() => {
console.log("second")
}, 1000)
console.log("third")
//.................. expected output......................//
// first
// third
// undefined
// second
console.log("first") is on the stack first, so it gets printed. Next, the engine notices setTimeout, which isn't handled by Javascript and pushes it off to the WebAPI to be done asynchronously. The call stack moves on without caring about the code handed off to the Web APIs and console.log("three") is printed.
Next, the Javascript engine's event loop kicks in, like a little kid asking "Are we there yet?" on a road trip. It starts firing, waiting for events to be pushed into it. Since the setTimeout isn't finished, it returns undefined, as the default, well because it hasn't been given the value yet. Once the callback finally does hits we get console.log("second") printed.
Call stack
A call stack is a mechanism for an interpreter (like the JavaScript interpreter in a web browser) to keep track of its place in a script that calls multiple functions — what function is currently being run and what functions are called from within that function, etc.
When a script calls a function, the interpreter adds it to the call stack and then starts carrying out the function.
Any functions that are called by that function are added to the call stack further up, and run where their calls are reached.
When the current function is finished, the interpreter takes it off the stack and resumes execution where it left off in the last code listing.
If the stack takes up more space than it had assigned to it, it results in a "stack overflow" error.
In summary, then, we start with an empty Call Stack. Whenever we invoke a function, it is automatically added to the Call Stack. Once the function has executed all of its code, it is automatically removed from the Call Stack. Ultimately, the Stack is empty again.