- Scope refers to the current context of the code.
- Scope determines the lifespan, access, and visibility of variables, functions, and objects throughout the code.
- Security: Variables and functions are only accessible where they are needed.
- Reducing Namespace Collisions: Namespace collisions occur when two or more variables share a common name.
- If you declared your variables with
const
orlet
, you would receive an error whenever you a name collision happens - If you declare your variables with
var
, your second variable overwrites the first one after it is declared (hard to debug)
- If you declared your variables with
- Code Reusability: Correctly utilizing local scopes means more reusable code with fewer potential side-effects.
- In Static scoping, an identifier refers to its nearest (top-level) lexical environment.
- This identifier become a property of the program text (lexical environment) and unrelated to the run time call stack.
- In Static scoping, the compiler first searches in the current block, then in the surrounding blocks successively and finally in the global variables.
- The scope is determined when the code is compiled (
compile-time
) - The word "static" relates to ability to determine the scope of an identifier during the parsing stage of a program (
compile-time
) - If only by looking at the source code one can determine in which environment a binding is resolved, we are in static scope.
- Technically static scope is implemented by the concept of
closures
. And vice-versa,closures
can naturally be implemented in languages with static scope, through the mechanism of capturing free-variables in lexical environments.
var x = 10; // lexically defined in the global scope (that means at runtime it is resolved also in the global scope)
var y = 20; // lexically defined y
function foo() {
console.log(x, y);
}
foo(); // 10, 20
function bar() {
var y = 30; // shadows (changes within this scope) global y as defined with same name
console.log(x, y); // 10, 30
foo(); // 10, 20 (at the moment of foo function definition - compile time, the nearest lexical context with the y name — was the global context)
}
bar();
- We cannot determine at parsing stage (compile-time), to which value (in which environment) a variable will be resolved
- Variable is resolved not in the lexical environment, but rather in the dynamically formed global stack of variables
- After the scope (lifetime) of the variable is finished, the variable is removed (popped) from the stack
- That means, that for a single function we may have infinite resolution ways of the same variable name — depending on the context from which the function is called
- Each time a new function is executed, a new scope is pushed onto the stack. (
run-time
) - A dynamically-scoped variable is resolved in the
environment of execution (run-time)
, rather than theenvironment of definition (compile-time)
as we have in the static scope. - Most of the modern languages do not use dynamic scope (Perl support both).
- One of the dynamic scope benefits is ability to apply the same code for different (mutable over the time) states of a system.
- If a
caller
defines an activation environment of acallee
, we are in dynamic scope. - in JavaScript
this
value is dynamically scoped, unless used in an arrow function. IMPORTANT
- Variables defined outside of a function or curly braces have Global scope.
- When you start writing JavaScript in a document, you are already in the Global scope.
- Variables inside the Global scope can be accessed and altered in any other scope.
- There is typically one global scope.
- Variables defined inside a function have Local scope.
- Any function defined within another function has a local scope which is linked to the outer function.
- Each function when defined creates its own (nested) local scope.
- Each function when invoked creates a new scope (different scope for every call of that function)
- Any locally scoped items are not visible in the global scope (unless exposed)
- Function Scope
- When you declare a variable in a function, you can access this variable only within the function. You can't get this variable once you get out of it.
- Block Scope
if
,switch
, conditions orfor
andwhile
loops- When you declare a variable with const or let within a curly brace ({}), you can access this variable only within that curly brace.
- The block scope is a subset of a function scope since functions need to be declared with curly braces (unless you're using arrow functions with an implicit return)
- Block statements, unlike functions, don't create a new scope
let
andconst
keywords support the declaration of local scope inside block statements
const logName = function() { return this.__proto__.constructor.name; };
class Object {
logName() { return this.__proto__.constructor.name; }
}
console.log("logName Scope:", logName()); // logName Scope: Window
console.log("object.logName Scope:", new Object().logName()); // object.logName Scope: Object
Note:
- Global scope lives as long as your application lives. Local Scope lives as long as your functions are called and executed.
- When a function is called in Strict Mode, the context will default to undefined.
Execution Context
refers toscope
and not context- when the JavaScript interpreter starts to execute the code, the context (scope) is by default set to be global. After that, each function call (invocation) would append its context to the execution context.
- Each function creates its own execution context.
- There can only be one global context but any number of function contexts.
- Creation Phase : when a function is called but its code is not yet executed
-
Creation of the Variable (Activation) Object
- When a function is called, the interpreter scans it for all resources including function arguments, variables, and other declarations.
- Everything, when packed into a single object, becomes the the Variable Object
-
Creation of the Scope Chain
- When asked to resolve a variable, JavaScript always starts at the innermost level of the code nest and keeps jumping back to the parent scope until it finds the variable or any other resource it is looking for.
-
Setting of the value of context (this)
executionContextObject = { 'scopeChain': {}, // contains its own variableObject and other variableObject of the parent execution contexts 'variableObject': {}, // contains function arguments, inner variable and function declarations 'this': valueOfThis }
- Execution Phase : when values are assigned and the code is finally executed
-
function declaration :
function funcName() {};
-
function expression :
funcName = function () {};
-
Functions, when declared with a
function declaration
, are always hoisted to the top of the current scope.
welcome();
function welcome() { console.log('Welcome!') };
// is equivalent to
function welcome() { console.log('Welcome!') };
welcome();
- Functions, when declared with a
function expression
, are not hoisted to the top of the current scope.
welcome();
var welcome = function() { console.log('Welcome!') };
// is NOT equivalent to
function welcome() { console.log('Welcome!') };
welcome();
-
Functions do not have access to each other's scopes when you define them separately, even though one function may be used in another. (we must return the variable and call the function that returns it to use the variable)
-
When a function is defined in another function, the inner function has access to the outer function's variables. This behavior is called
lexical scoping
. (outer function does not have access to the inner function's variables) like one-way glass.- Lexical Scope means that in a nested group of functions, the inner functions have access to the variables and other resources of their parent scope.
- This means that the child functions are lexically bound to the execution context of their parents.
function grandfather() { var name = 'BigDaddy'; // likes is not accessible here function parent() { // name is accessible here // likes is not accessible here function child() { // Innermost level of the scope chain // name is also accessible here var likes = 'Growing'; } } }
-
The concept of closures is closely related to Lexical Scope
-
A Closure is created when an inner function tries to access the scope chain of its outer function meaning the variables outside of the immediate lexical scope. (Whenever you create a function within another function, you have created a closure)
-
The inner function is the closure. This closure is usually returned so you can use the outer function's variables at a later time.
-
Closures contain their own scope chain, the scope chain of their parents and the global scope
-
A closure can also access the variables of its outer function even after the function has returned
-
When you return an inner function from a function, that returned function will not be called when you try to call the outer function. You must first save the invocation of the outer function in a separate variable and then call the variable as a function.
const outerFunction = (outerVariable) => { const innerFunction = (innerVariable) => { console.log('outerVariable: ', outerVariable); console.log('innerVariable: ', innerVariable); }; return innerFunction; }; const myFunction = outerFunction('Outer Variable as Closure'); myFunction('Inner Variable');
- Control Side Effects
you create a function that activates the inner closure at your whim
const cookPizza = (type) => {
console.log(`Start Cooking ${type} Pizza.`);
setTimeout(() => console.log(`${type} Pizza is Serving ...`), 1000);
};
cookPizza('Pepperoni'); // Start cooking immediately and serving after 1 sec
// VS.
const cookPizza = (type) => {
console.log(`Start Cooking ${type} Pizza.`);
return () => {
setTimeout(() => console.log(`${type} Pizza is Serving ...`), 1000);
};
};
const servePizza = cookPizza('Pepperoni'); // Start cooking immediately but serving once needed
servePizza(); // Start serving while needed!
- Create Private Variables
variables created in a function cannot be accessed outside the function. Since they can't be accessed, they are private variables.
const Secret = (publicA= 'Public A') => {
const secretA = 'Secret B';
const getSecrets = (secretB = 'Secret B') => {
const secretC = 'Secret C';
return {
secretA,
secretB,
secretC
}
};
return {
publicA,
getSecrets
}
};
const secret = new Secret();
console.log(secret.publicA, secret.secretA, secret.secretB, secret.secretC); // Public A undefined undefined undefined
console.log(secret.getSecrets()); // {secretA: "Secret B", secretB: "Secret B", secretC: "Secret C"}
- Encapsulating functions
Encapsulating functions from the public (global) scope to private or protected saves them from vulnerable attacks. But in JavaScript, there is no such thing as public or private scope. However, we can emulate this feature using closures.
const User = (() => {
const credentials = {
key: 'USER_KEY',
secret: 'ABC123'
};
const api = 'https://jsonplaceholder.typicode.com/users';
const get = async (userId) => {
const response = await fetch(`${api}/${userId}`);
return response.json()
};
return {
get
};
})();
const user = User.get(1);
console.log(User); // {get: ƒ}
console.log(user); // Promise {<pending>}
user.then(console.log); // {id: 1, …}
- Module Pattern
Module Pattern__ allows us to scope our functions using both public and private scopes in an object (One convention is to begin private functions with an underscore)
const Module = function(name) {
this.name = name;
const getModuleName = () => this.name;
const setModuleName = (name) => this.name = name;
return {
getModuleName,
setModuleName
}
};
const module = new Module('Module Name');
console.log(module.name); // undefined
console.log(module.getModuleName()); // Module Name
module.setModuleName('New Module Name');
console.log(module.getModuleName()); // New Module Name
const Module = (() => {
const name = 'Nice Module';
const _privateMethod = () => {
console.log("Module._privateMethod Invoked Privately");
return `Module._privateMethod returned "${name}"`;
};
const publicMethod = () => {
console.log("Module.publicMethod Invoked Publicly");
console.log(_privateMethod());
console.log("Module.publicMethod Done.");
};
return {
publicMethod
}
})();
try {
Module._privateMethod();
} catch(e) {
console.error(`ERROR ACCESS PRIVATE: ${e.message}`);
} finally {
Module.publicMethod();
}
- Immediately-Invoked Function Expression (IIFE)
This is a self-invoked anonymous function called in the context of window, meaning that the value of this is set window. This exposes a single global interface to interact with.
(function(window) {
// do anything
})(this);
((window) => {
// do anything
})(this);
An IIFE encapsulates and secures your variables
(function() {
// hoisted up but still inside the IIFE
var button = '<button class="red-button">Close</button>';
})();
var button = $('button'); // sent to the global scope
- Call is slightly faster in performance than Apply.
- Call allows you pass the rest of the arguments one by one separated by a comma
- Apply allows you pass the the arguments as an array
- Bind allows you pass the rest of the arguments one by one separated by a comma BUT doesn't invoke the method by itself.
function born(gender, name) {
console.log(`${name} is a ${gender}`);
console.log('ES : ', this, "\n\n");
};
const bornES6 = (gender, name) => {
console.log(`${name} is a ${gender}`);
console.log('ES6 : ', this, "\n\n");
};
const family = [
{
name: 'Jane',
gender: 'Girl'
},
{
name: 'John',
gender: 'Boy'
}
];
family.map((member) => {
console.log('-----------------------------------------------\n\n');
born.call( member, member.gender ,member.name );
bornES6.call( member, member.gender ,member.name );
console.log('===============================================\n\n');
born.apply( member, [member.gender ,member.name] );
bornES6.apply( member, [member.gender ,member.name] );
console.log('-----------------------------------------------\n\n');
});
function Person(name, age) {
this.name = name;
this.age = age;
return !(this instanceof Person) && new Person(name, age);
}
const p = Person("myName", 35); // Accidentally missed the "new" keyword
p instanceof Person // true
- add the
debugger
keyword in your code
/* ES5 Normal Function */
var getScope = function () {
console.log(this.constructor.name); // this = global, [object Window]
};
getScope();
var myObject = {};
myObject.getScope = function () {
console.log(this.constructor.name); // this = Object { myObject }
};
myObject.getScope();
console.log('* * * * *');
/* ES6 Fat Arrow Function */
const getScopeES6 = () => {
console.log(this.constructor.name); // this = global, [object Window]
};
getScopeES6();
const myObjectES6 = {};
myObjectES6.getScopeES6 = () => {
console.log(this.constructor.name); // this = global, [object Window]
};
myObjectES6.getScopeES6();
console.log('* * * * *');
/* Changing Context */
getScope.apply(this); // this = global, [object Window]
getScope.call(this); // this = global, [object Window]
getScope.bind(this)(); // this = global, [object Window]
getScope.apply(myObjectES6); // this = Object { myObject }
getScope.call(myObjectES6); // this = Object { myObject }
getScope.bind(myObjectES6)(); // this = Object { myObject }