- INDEX
- JavaScript Engine
- Types
- Execution context
- Functional Programming
- Generators
- JS : The weird parts
- Miscellaneous
- Notes
The JavaScript engine is a program or an interpreter that reads and executes the JavaScript code. The most popular JavaScript engines is V8 which is used in Google Chrome and Node.js.
V8 is not the only javascript engine, there are a lot of other engines like SpiderMonkey (used in Firefox), Chakra (used in Microsoft Edge), JavaScriptCore (used in Safari), and Nashorn (used in Java), ... etc.
These are called "ECMAScript engines" because they implement the ECMAScript standard, which JavaScript is based on.
Google created V8 to power its Chrome browser, and open-sourced it in 2008. before it, most engines were weak and slow, but V8 was a game-changer.
-
Compiled Language
- The compiler works ahead of time to convert instructions into a machine-code or lower-level form so that they can be read and executed by a computer. It runs all of the code and tries to figure out what the code does and then compiles it down into another language that is easier for the computer to read.
- Language is written and compiled to machine code inside of an application (before it's run).
- Errors are detected during compiling
- The code won’t compile until it’s error-free
- it
optimizesthe code as it cashes any repeated function.- if it say
add(2,3)function it caches its result (5) and uses it if it found the same function-call again --> improves performance
- if it say
- Examples: C, C++, Erlang, Go
- Examples used with javascript: Babel (transpiles
ES6toES5), TypeScript (transpilesTStoJS)- Both of these do exactly what compilers do. Take one language and convert into a different one!
-
Interpreted Language
- An interpreter directly executes each line of code line by line, without requiring them to be compiled into a machine language program.
- The interpreter translates and runs code one statement at a time (line-by-line).
- each line of code is translated to machine-code one-by-one as the script is run.
- the interpreter always looks for variables and function declarations before going through each section of a script, line-by-line.
- Interpreters can use different strategies to increase performance. They can parse the source code and execute it immediately, translate it into more efficient machine code, execute precompiled code made by a compiler, or some combination of these.
- In the V8 engine, the interpreter outputs bytecode.
- Errors found when the code is run
- Interpreter starts running the code quickly
- Interpreted code runs more slowly
- it's more fit to
javascriptas JS runs on the browser
Node.js Is an Interpreter
-
Just-In-Time (JIT) Compilation
-
It's a combination of both compilers and interpreters.
-
It compiles the code into machine code at runtime, and then executes it immediately.
-
It's a hybrid approach that combines the best of both worlds.
-
In modern engines:
- the interpreter starts reading the code line by line while the profiler watches for frequently used code and flags then passes is to the compiler to be optimized.
- In the end, the JavaScript engine takes the bytecode the interpreter outputs and mixes in the optimized code the compiler outputs and then gives that to the computer. This is called "Just in Time" or JIT Compiler.
-
For example this code:
function add(a, b) { return a + b; } for (let i = 0; i < 1000; i++) { add(2, 3); }
- Here, the
add(2, 3)function is called 1000 times, so the JIT compiler will optimize this function and cache its result to be used in the next calls.- This is not possible in a pure interpreter because it doesn't know what the code does until it runs it.
- But it's possible in a JIT compiler because it can see the code running and optimize it as it goes (compiling it to machine code).
- Here, the
-
-
javascript uses best of (Interpreted & Compiled) languages ---> Jit Compiler
-
is
javascriptan interpreted language ?- yes initially, but it evolved to use "compilers" as well based on the implementation, as the code is compiled to machine-code first then it's executed by the engine with optimizations to the initial compiled code
- The engine (embedded if it’s a browser) reads (“parses”) the script.
- Then it converts (“compiles”) the script to machine code.
- And then the machine code runs, pretty fast.
the browser's Console is a live interpreter of the javascript engine
-
What is that
ASTstep?- it's the Abstract Syntax Tree which is a tree representation of the abstract syntactic structure of source code written in a programming language.
- It's a tree graph of the source code that does not show every detail of the original syntax, but contains structural or content-related details. Certain things are implicit in the tree and do not need to be shown, hence the title abstract.
- Each node of the tree denotes a construct occurring in the source code.

-
What is the
Profilerstep?- It's a tool that helps to analyze the performance of the code and to identify the parts of the code that are slow and need to be optimized, and pass them to the
compilerto be optimized.- It's used to monitor the code execution and to identify the parts of the code that are executed frequently.
- For example, if a function is called many times, the profiler will flag it and pass it to the compiler to be optimized.
- It's a tool that helps to analyze the performance of the code and to identify the parts of the code that are slow and need to be optimized, and pass them to the
-
Summary: The
Interpreterallows us to run the code quickly, but it's not optimized. TheProfilerwatches the code execution and flags the parts that are executed frequently and sends them to theCompilerto be optimized. TheCompileroptimizes the code and passes it to the `JIT Compiler- All this happens as the code is running, and it's called Just-In-Time Compilation.
Heap: a much larger part of the memory that stores everything allocated dynamically, that allows a faster code execution, and protects it from corruption and makes the execution faster.
-
Memory-heap:
// tell the memory heap to allocate memory for a number const number = 11; // allocate memory for a string const string = 'some text'; // allocate memory for an object and it's values const person = { first: 'Brittney', last: 'Postma' };
- It's a space in memory where the memory allocation (storage) happens (variables, objects, functions, ... etc)
- usually when you have values that has place in memory but not used
- also common in event listeners which are always listening(waiting) for an event
- error->
memory leak
-
call-stack
-
It's a data structure that indicates where the program is in its execution.
-
It keeps track of where the program is in its execution, and what functions are currently
-
The call stack keeps track of where we are in the code, so we can run the program in order.
-
always the first thing in it is the global execution context (Top-level Code / variable declared outside a function)
-
error->
stack overflow: usually from recursion (function calling itself) and it's not stopping
-
Example:
function subtractTwo(num) { return num - 2; } function calculate() { const sumTotal = 4 + 5; return subtractTwo(sumTotal); } debugger; calculate();
-
Things are placed into the call stack on top and removed as they are finished. It runs in a first in last out mode. Each call stack can point to a location inside the memory heap. In the above snippet the call stack looks like this:
anonymous; // file is being ran // CALL STACK // hits debugger and stops the file // step through each line calculate( // steps through calculate() sumTotal = 9 anonymous ); // CALL STACK // steps into subtractTwo(sumTotal) num = 9 subtractTwo; // returns 9 - 2 calculate(anonymous); // CALL STACK // subtractTwo() has finished and has been removed calculate( // returns 7 anonymous )( // CALL STACK // calculate() has finished and has been removed anonymous ); // CALL STACK // and finally the file is finished and is removed // CALL STACK
-
-
global execution context consists of 2 things:
- storing variables / function in memory (global state)
- performing functions execution in the thread of execution (call stack) where each function gets its own mini execution context
- JavaScript is a single threaded language, meaning only one thing can be executed at a time. It only has one call stack and therefore it is a synchronous language.
- other languages like
C++are multi-threaded, meaning they can run multiple things at once. but in javascript, it's was designed to be single-threaded because it was designed to run in the browser, and the browser is single-threaded. and to avoid conflicts between different parts of the code, and Deadlocks which is a situation where two or more threads are waiting for each other to release a resource, or waiting for an event to occur.
- other languages like
-
Types:
-
primitive values : they are the most basic data types in the language. They are immutable (cannot be changed) and copied by value (when assigning or passing).
-
reference values : they are more complex data types. They are mutable (can be changed), copied by reference (when assigning or passing).
reference-typeare stored in theHeapas we don't know how big its size would be so it's like if theHeaphas unlimited storage unlikecall stackwhich has a limited size (4-8MB)
- There are 3 types of objects:
object,function,array.
typeof []; // object typeof {}; // object typeof function () {}; // function 🤯
-
If you don't beleive me, try this:
function fn() { console.log('hello'); } fn.hi = 'hi'; console.log(fn.hi); // hi
-
-
typeofoperator:-
It's used to get the type of a variable or an expression.
-
It returns a string indicating the type of the unevaluated operand.
-
It's useful when you want to check the type of a variable before performing an operation on it.
typeof 5; // number typeof 'hello'; // string typeof true; // boolean typeof undefined; // undefined typeof null; // object typeof {}; // object typeof []; // object typeof function () {}; // function // Ready for the weird part? typeof Object; // function typeof String; // function typeof Boolean; // function typeof new String(); // object // Why? Because these are constructor functions in JavaScript. They are functions that create (objects, strings, booleans) respectively. But if you use the `new` keyword, it will return an object.
-
Sometimes it's not enough when figuring out the type of something specially when it's reference type, so you can these:
// Array.isArray() -> to check if it's an array Array.isArray([]); // true // instanceof -> to check if it's an instance of a specific object [] instanceof Array; // true // Object.prototype.toString.call() -> to get the type of the object Object.prototype.toString.call([]); // [object Array]
-
-
There's a difference in how memory is allocated for
primitiveandreferencetypes. In the case ofprimitivetypes, the value is stored directly in the memory location, while in the case ofreferencetypes, the memory location stores a reference to the actual value in theheap.
-
"Pass by value"
const a = 5; let b = a; a = a + 1; console.log(a); // 6 console.log(b); // 5 -> Didn't change
- means that a copy of the value and store it in a new memory location.
- So when we change the value of the variable, it doesn't affect the original value.
-
"Pass by reference"
const obj1 = { name: 'Ahmed' }; const obj2 = obj1; obj1.name = 'John'; console.log(obj1.name); // John console.log(obj2.name); // John -> Changed
- means that a reference to the value is stored in the memory location and shared between the variables.
- So when we change the value of the variable, it affects the original value.
- Why this might be good: it saves memory space, as it doesn't need to store the same value in multiple locations.
- Why this might be bad: it can lead to unexpected behavior if you're not careful. like if you want to copy the value of the object to another object, you need to do a
deep copynot ashallow copy.
To remember the difference, use the song "I want it that way"

-
null: is you set it to empty intentionally, (a variable with no value - it may have had one at some point, but no longer has a value)- developer sets the value
undefined: is you set it to empty unintentionally, (a variable that should have a value assigned to it, but doesn't yet, , or hasn't been created yet)- javascript can't find value for it
- it's empty because it:
- variable without value:
- has not been set
- Doesn't currently have a value.
- missing function arguments
- missing object properties
- variable without value:
-
or
nullis empty on purpose, whileundefinedis still empty. -
undefinedmeans a variable has been declared but has not yet been assigned a value -
nullis an assignment value. It can be assigned to a variable as a representation of no value. -
Notes:
typeof null; // object (a bug in JS)
- Super weird right? It's a long-standing bug in JS, but it's too late to fix it now. It's a quirk in the language that we have to live with.
The difference is that == will do type coercion before comparing, whereas === will not do any type coercion (it will only return true if both values and types are the same).
-
Because of Type coercion, the strict equality operators
===and!==result in fewer unexpected values than==and!=do. -
if
===would always be equivalent to==in your code, using it everywhere sends a wrong semantic signal : protecting myself since I don't know'trust the types -
How "Double equal" works:
-
If the types differ, try to convert them to the same type by using coercion to:
- convert to previous type (if possible) then compare ->
[] -> "" -> 0 -> false - that's why it's not recommended to use
==withtrue / falseas it will convert it to1 / 0and compare it with the other value
[] == true; // to '' == 1; // to 0 == 1; // to false;
- convert to previous type (if possible) then compare ->
-
Note: It's a myth that
===is slower than==in modern browsers.
Some argue that using
===everywhere implies a lack of understanding or trust in your code's types.Clear and obvious types lead to better code. Use
==when types are known, otherwise use===.
Explicit Type Conversion (Type Casting): is when data type is converted by the programmer using type casting.
-
Here are some examples of explicit type conversion:
Number(),String(),Boolean()parseInt(),parseFloat()toString(),toFixed()JSON.stringify(),JSON.parse()
-
When the conversion fails, the result is
NaN(Not a Number).Number('123'); // 123 Number('hello'); // NaN
-
Truthy & Falsy values:
- Falsy values:
false,0,"",null,undefined,NaN - Truthy values:
true,1,"hello",[],{},function(){}
Boolean(0); // false Boolean(''); // false Boolean('0'); // true -> because it's a string (any string that is not empty is true) Boolean(1); // true Boolean({}); // true
- Falsy values:
Type coercion is the process of implicitly converting value from one type to another by the JavaScript engine.
Implicit vs Explicit
- type coercion is implicit whereas type conversion can be either implicit or explicit
-
Truthy & Falsy values:
if (0) { // this code will not run }
-
Operator overloading:
+vs-:-: if one of the operands is not a number, it tries to convert it to a number and subtracts it+: if one of the operands is a string, it converts the other operand to a string and concatenates them
-
convert string to number using :
unaryoperator- it's a
+or-before the string number =>console.log(+"565") - it's a
!before something =>console.log(! (x > 4) )
- it's a
parseint()orparsefloat()=>parseint(100Ahmed)= 100
-
Boolean("0");--> true
-
Coercion Corner Cases
1 < 2; // true 2 < 3; // true 1 < 2 < 3; // true (1 < 2 = true => true < 3 => 1 < 3 = true) 3 > 2; // true 2 > 1; // true 3 > 2 > 1; // false (3 > 2 = true => true > 1 => 1 > 1 = false) { x: 1 } == { x: 1 }; // false { x: 1 } === { x: 1 }; // false
It's the environment in which javascript code is executed, it stores all the necessary info for some code to be executed.

-
Each function has its own Execution context, and it's created when the function is called/invoked. and all execution contexts together make the call stack.

-
There're 2 types of execution contexts:

-
Global Execution Context:
-
It's the default execution context, and it's created when the script is loaded.
-
It has 2 phases:
- Creation phase:
- Creation of the
global object(windowobject in the browser) - Creation of the
thiskeyword (points to theglobal object) ->thiskeyword will be automatically defined with valuewindowin the global execution context. - Memory allocation for
variablesandfunctions(hoisting)- This is super important when understanding hoisting in JavaScript.
- Creation of the
- Execution phase:
- where the code is executed line by line.
- Creation phase:
-
- Local Thread of execution -> code that is being executed
- Local Memory ->
local variables,arguments passed to the function - Scope chain -> where the function was written -> Lexical / Variable Environment
- This is slightly different from
closure, the scope chain is where the function was written, while the closure is what allows a function to access variables from an outer function, even after the outer function has finished executing. it's stored in the_proto_property of the function.
- This is slightly different from
thiskeyword -> how the function was called (not inarrow functions)
-
-
Functional Execution Context:
-
Created when a function is called.
-
-
Local Thread of execution -> code that is being executed
-
Local Memory ->
local variables,arguments passed to the function -
Scope chain -> where the function was written -> Lexical / Variable Environment

- This is slightly different from
closure, the scope chain is where the function was written, while the closure is what allows a function to access variables from an outer function, even after the outer function has finished executing. it's stored in the_proto_property of the function.
- This is slightly different from
-
thiskeyword -> how the function was called (not inarrow functions) -
argumentsobject -> contains all the arguments passed to the function- It's an array-like object that contains all the arguments passed to the function. the default value of
argumentsis an empty array[]if no arguments are passed to the function.
- It's an array-like object that contains all the arguments passed to the function. the default value of
-
-
-
It's the environment in which a variable is declared and accessible.
Javascript doesn't have dynamic scope (where the scope is determined by the calling context), it has lexical (static) scope (where the scope is determined by the location of the variable declaration).
-
Types of scopes:
- Global scope: Variables declared outside functions, accessible anywhere.
- Function scope: Variables declared inside a function, accessible only within.
- Block scope (ES6): Variables declared inside a block, accessible only within.
letorconstinside a block, accessible only within. Butvaris accessible outside the block.functionsare alsoblock-scopedinstrict mode, otherwise they arefunction-scoped.
-
Types of variables based on scope:
- Local variables: Created and removed within a function, no naming conflicts between functions.
- Global variables: Persist in memory while the page is loaded, higher memory usage, risk of naming conflicts.
-
Scope chain: The order the interpreter searches for variables, from innermost to global scope.

-
Scope Variables are properties of a special internal object.
- Accessing a variable means accessing a property of this object.
-
Variables defined outside a function are accessible inside functions defined after them even if they are called before the function definition.

- This is called a Backpack or Persistent Lexical Scope Referenced Data (PLSRD).
- Each function execution gets its own Backpack.
Note: (if not in strict mode) -> if variable is assign a value but never declared in a scope or block -> it automatically gets declared in the global scope
-
if function is declared in a local scope, it can't be accessed outside:
let phrase = 'Hello'; if (true) { let user = 'John'; function sayHi() { alert(`${phrase}, ${user}`); } } sayHi(); // The result is an error. // The function sayHi is declared inside the if, so it only lives inside it. There is no sayHi outside.
-
The global object has a universal name ->
globalThis.- (
windowobject ) -> In the browser - (
globalobject ) -> In Node.js
- (
It's a scope that is defined at the time of writing the code, and it doesn't change at runtime.
- It refers to the scope where a variable is declared, which remains constant regardless of where the variable is called.
- Functions are linked to the object they were defined within.
- Lexical Environment consists of two parts:
- Environment Record: Stores local variables.
- Outer Reference: Points to the outer lexical environment.
It's the javascript engine alocating memory for variables and functions before the code is executed (during the creation phase of the execution context).
It is the process of putting all variable and function declarations into memory during the "compile phase".
-
It's a JavaScript mechanism where (variables and function) declarations are moved to the top of their containing scope before code execution by saving them in memory before the code is executed.
-
It makes some types of variables accessible/usable in the code before they are actually declared by initializing them with
undefined. -
It's to make space in memory for a variable to be able to:
- Call functions before they have been declared
- Assign a value to a variable that has not yet been declared
-
In JavaScript,
functionsare fully hoisted,varvariables are hoisted and initialized toundefined, andletandconstvariables are hoisted but not initialized a value.- Note that the declaration is hoisted, but the initialization/value is not.
console.log(name); // undefined var name = 'John';
-
variables and functions within each execution context are created before they are executed.

Variable are partially hoisted (only the declaration is hoisted, not the initialization), while functions are fully hoisted (both declaration and initialization).
-
varhoisting: usually bad, as withvar, you can access declaration, but not the valueconsole.log(name); // undefined var name = 'John';
Varvariables are given a memory allocation and initialized a value ofundefineduntil they are set to a value in line. So if a var variable is used in the code before it is initialized, then it will returnundefined.
-
functionhoisting: actually pretty useful --> must be a function declarationdisplay(); // "hello world" ✅ function display() { console.log('hello world'); } display2(); // ReferenceError: Cannot access 'display2' before initialization ❌ let display2 = function () { console.log('hello world'); };
-
if you use
function expression(let display2 = function () {}) it will throw an error because it's not a function declaration, it's a variable declaration that holds a function. -
a function can be called from anywhere in the code base because it is fully hoisted. If
letandconstare used before they are declared, then they will throw areference errorbecause they have not yet been initialized// function expression gets hoisted as undefined var sing = function () { console.log('uhhhh la la la'); }; // function declaration gets fully hoisted function sing2() { console.log('ohhhh la la la'); }
-
-
- it gets hoisted but not initialized (it's not assigned a value) -> so you can't access it before the initialization ->
TDZ (Temporal Dead Zone) Error - Javascript knows that the variable is declared but not initialized, so it throws an error, if the variable wasn't declared, it would have thrown a
ReferenceError (not defined)
- it gets hoisted but not initialized (it's not assigned a value) -> so you can't access it before the initialization ->
-
-
How hoisting works ?
-
Before the code is executed, code is scanned for variable declarations, and for each variable, a new property is created in the variable environment object and added to the memory

-
It doen't actually move the code to the top, it alocate memory for them before the code is executed during the "creation phase" of the execution context.
-
It happens in every execution context, not just the global one.
-
-
Why Hoisting is useful ?
- Using functions before actual declaration
varhoisting can be useful in some cases, but it's usually better to useletorconstinstead.
-
Temporal Dead Zone (TDZ)
-
It's the time between the variable creation and initialization, where you can't access the variable.
-
It only happens with
letandconstvariables, not withvarvariables. becausevaris initialized withundefinedby default.
-
Why TDZ is useful ?
- It helps to catch errors in the code, as it throws an error if you try to access a variable before it's initialized.
-
-
Notes
-
What will be the result of this code?
// variable declaration gets hoisted as undefined var favoriteFood = 'grapes'; // function expression gets hoisted as undefined var foodThoughts = function () { // new execution context created favoriteFood = undefined console.log(`Original favorite food: ${favoriteFood}`); // variable declaration gets hoisted as undefined var favoriteFood = 'sushi'; console.log(`New favorite food: ${favoriteFood}`); }; foodThoughts();
- The result will be
undefinedandsushi, I bet that you thought it would begrapesandsushibut here's why:- As told before: hoisting happens in every execution context, so when the
foodThoughtsfunction is called, a new execution context is created, and thefavoriteFoodvariable is hoisted to the top of the function and initialized asundefined. - So
favoriteFoodinside the function gets overwritten by theundefinedvalue, and that's why the result isundefinedandsushi.
- As told before: hoisting happens in every execution context, so when the
- The result will be
-
Avoid hoisting when possible. It can cause memory leaks and hard to catch bugs in your code. Use
letandconstas your go to variables.
-
It's the object that the function is a property of.
It's a special variable that is created for every execution context (every function), which takes the value of (points to) the “owner” of the function in which the this keyword is used.
- It's not static. It depends on how the function is called, and its value is only assigned when the function is called.

The reason
thisis so confusing because javascript doesn't really have a concept ofindividual functionslike other languages, it hasobjectsandmethodsthat are called on objects. So any function can be called on any object, andthisis what allows that to happen (even if the function is not a method of that object which is called using the global object (window))Remember the rule:
thisis determined by the left of the dot at the call time, if there's no dot, it points to the global object (window) assomeFunction()is the same aswindow.someFunction()
-
thisis handled differently based on the type of function:-
Arrow functions:
this: Defined by where it was written, not called (lexical scope).- No
this: Taken from the outer scope (global scope,windowobject). - Object methods: Still point to the global object.
- Avoid using arrow functions as methods inside objects:
thiswill refer to the upper scope, usually the global object.
-
Normal functions:
thisrefers to the caller (dynamic scope).thisisundefinedif called globally if in strict mode, otherwise it points to the globalwindowobject.
-
-
Benefits of
thisin functions:-
gives methods access to their object
-
reusability: execute same code for multiple objects (use
thisto refer to the object that the function is a property of) allowing us to save memory.function sayName() { console.log(`My name is ${this.name}`); } const person1 = { name: 'John', sayName: sayName }; const person2 = { name: 'Jane', sayName: sayName }; // call the function with different objects person1.sayName = sayName; person2.sayName = sayName;
-
-
Notes
-
When chaining multiple methods (functions) that uses
thiskeyword, you can returnthisat the end to make it chainable.let ladder = { step: 0, up() { this.step++; return this; // return the object itself to be able to chain the methods }, down() { this.step--; return this; }, showStep() { alert(this.step); return this; } }; ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0
-
When you want to write a function inside object method, (use
arrow functionor useselfreference variable ) to avoidthisconfusion.let user = { name: 'John', sayHi_1() { let func = () => alert(this.name); ✅ // this -> user object func(); }, sayHi_2() { function func() { alert(this.name); // Error ❌: Cannot read property 'name' of undefined // this -> window object } func(); }, sayHi_3() { const self = this; // use self reference variable function normal2() { alert(self.name); ✅ // this -> window object // self -> user object } normal2(); }, }; user.sayHi_1(); // John user.sayHi_2(); // Error, because `this` is undefined user.sayHi_3(); // John
-
without
strict mode,thiswill point to the global object =>window -
If we used
use strict,thiswould beundefinedin a function that is in the global scope. or that is not in a function (in the global scope directly).
-
-
What will be the result of
thisin the following code?const a = { name: 'a', say() { console.log(this); } }; const b = { name: 'b', say() { return function () { console.log(this); }; } }; const c = { name: 'c', say() { return () => { console.log(this); }; } }; a.say(); // a object b.say()(); // window object c.say()(); // c object
- Always remember to ask the interviewer if the function is an arrow function or a normal function, as it will affect the value of
this. - In the above code,
b.say()()will return thewindowobject because it's a normal function, whilec.say()()will return thecobject because it's an arrow function and it's lexically scoped.- Ask youself: who is calling the function? (the object or the global object)
- The nested function is called by the global object, so
thiswill point to the global object.
- Always remember to ask the interviewer if the function is an arrow function or a normal function, as it will affect the value of
It's the process of building software with small pure functions, avoiding shared state, mutable data, and side-effects.
-
It's a declarative programming paradigm, which means that the program structure is based on the functions and what they return, not on the order of execution.
Imperative vs Declarative programming:
-
Functional programming is about Verbs (actions)
-
it's like a black box that takes something in and returns something out
-
it depends on Tiny functions: Save every single line (or few lines) as its own function
-
Recombine/compose Build up our application by using these small blocks of self-contained code combining them up line-by-line by referring to their human-readable name
// Todo list pipe( getPlayerName, getFirstName, properCase, addUserLabel, createUserTemplate )([{ name: 'Abdelrahman', score: 3 }]);
-
the result that we will get a code that is:
- Easier to add features
- More readable
- Easier to debug
-
-
object oriented programming is about pronouns (objects and things) -> "keep state to yourself and send/receive messages"
-
Reduce the potential impact of any given line to maybe 10 other lines (inside the function)
-
structure our code into individual pieces where almost every single line is self-contained
-
No consequences except on that line Each function’s only ‘consequence’ is to have its result given to specifically the next line of code (‘function call’) and not to any other lines
-
Famous functional programming libraries:
These 2 concepts use Closures & Higher order functions
It's the process of converting a function that takes multiple arguments into a function that takes them one at a time.
-
A curried function can be called with any number of arguments, and it will return a new function that expects the rest of the arguments. as if you call it with fewer arguments, it returns a smaller partial function that expects the rest of the arguments.
// instead of this: fn(a, b, c); // you can do this: fn(a)(b)(c);
-
Currying allows us to easily get
partials, which are functions that already have some of the parameters of the original function usingclosures. -
Example:
function curry(fn) { return function (a) { return function (b) { return fn(a, b); }; }; } // or const curry = (fn) => (a) => (b) => fn(a, b); // usage function sum(a, b) { return a + b; } let curriedSum = curry(sum); alert(curriedSum(1)(2)); // 3 // curriedSum(1); // returns a function (b) => fn(a, b)
-
Usually for complex and generic curry functions, we can use libraries like
lodashorramda, but here's a native generic example:// create a function that converts this function into a curried function function add3(a, b, c) { return a + b + c; } // convert to curried function like this: add3(1)(2)(3) // ----------------------- Solution ----------------------- // function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function (...args2) { return curried.apply(this, args.concat(args2)); }; } }; }
-
Notes:
- The currying requires the function to have a fixed number of arguments.
- A function that uses rest parameters, such as
f(...args), can’t be curried this way.
It's the process of applying a function to some/part of its arguments. The partially applied function gets returned for later use
It's just a name for binding some of the function’s arguments to a specific value and returning a new function that takes the rest of the arguments. (Binding arguments to a function)
see it's use in
bindsection
-
it's creating a new outer-function that calls our multi-argument function with the argument, and the multi-argument function stored conveniently in the Backpack
const multiply = (a, b) => a * b; function prefillFunction(fn, prefilledValue) { const inner = liveInput => { const output = fn(liveInput, prefilledValue); return output; }; return inner; } const multiplyBy2 = prefillFunction(multiply, 2); const result = multiplyBy2(5);
-
we can also use
bind()for "this":// Syntax let bound = func.bind(context, [arg1], [arg2], ...); // EX: function mul(a, b) { return a * b; } let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8
- Please note that we actually don’t use
"this"here. Butbindrequires it, so we must put in something likenull. - The call to
mul.bind(null, 2)creates a new functiondoublethat passes calls tomul, fixingmullas the context and2as the first argument. Further arguments are passed “as is”.
- Please note that we actually don’t use
-
Going partial without context ("
this")- What if we’d like to fix some arguments, but not the context
this? For example, for an object method.- The native
bind()does not allow that. We can’t just omit the context and jump to arguments.
- The native
- Fortunately, a function
partial()for binding only arguments can be easily implemented:
function partial(func, ...argsBound) { return function (...args) { return func.call(this, ...argsBound, ...args); }; } // Usage: let user = { firstName: 'John', say(time, phrase) { alert(`[${time}] ${this.firstName}: ${phrase}!`); } }; // add a partial method with fixed time user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow('Hello'); // [10:00] John: Hello!
- Also there’s a ready
_.partialimplementation from lodash library.
- What if we’d like to fix some arguments, but not the context
-
Why do we usually make a partial function?
- The benefit is that we can create an independent function with a readable name (
double,triple). We can use it and not provide the first argument every time as it’s fixed withbind. - In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience.
- For instance, we have a function
send(from, to, text). Then, inside a user object we may want to use a partial variant of it:sendTo(to, text)that sends from the current user.
- For instance, we have a function
- The benefit is that we can create an independent function with a readable name (
A closure allows a function to remember variables from its creation context, even after the outer function has returned (after leaving the scope it was declared in).
Javascript engine makes sure that the function has access to all the variables that were in its scope when the function was created, even if the outer function has finished executing.
-
Why we have closures in JS?
- Because in JS, functions are first-class citizens (can be passed around and returned like any other value), and has lexical scope (where the function was written, not called)
- and closures / backpacks allow us to keep the variables in the function's scope even after the function has finished executing
-
It's a closed-over (variable environment) of the execution context in which the function was created, even after that execution context is gone.

-
Function's
[[Environment]]property:- References the Lexical Environment (in the scope chain) where the function was created.
- functions created with
new Function()always references the global Lexical Environment, not the outer one, preventing access to outer variables. - Benefit: Access outer function's scope from an inner function, even after the outer function has returned.
-
-
How does it work ?
-
When a function is created, it keeps a reference to its outer lexical environment (where it was created), even if the outer function has finished executing.
-
Then, the function is returned and assigned to a variable. The variable becomes a function reference that keeps the reference to the outer lexical environment.
- It's done by returning a function from another function
-
When the function is called, it uses the outer lexical environment to access the variables. (even if the outer function has finished executing)
-
EX:
function makeCounter() { let count = 0; return function () { return count++; }; } // using it let counter = makeCounter(); // now, counter() gives the next number each time it is called // `count` variable is in the "backpack" of the counter function (in [[Environment]] property) counter(); // 0 counter(); // 1
-
-
Notes:
-
Benefits of closures:
-
Memory-efficient: Closures allow us to cache the values inside its block-scoped (where it was called) in case that it was called again.
function heavyDuty(index) { const bigArray = new Array(7000).fill('😄'); console.log('created!'); return bigArray[index]; } heavyDuty(688); // created! (it will create the array every time it's called) BAD ❌ // --------------------------------------------------------- function heavyDutyWithClosure() { const bigArray = new Array(7000).fill('😄'); console.log('created once!'); return function (index) { return bigArray[index]; }; } const getHeavyDuty = heavyDutyWithClosure(); // created once! (it will create the array only once) GOOD ✅ getHeavyDuty(688);
-
Encapsulation / Data privacy: Closures allow us to create private variables that can't be accessed directly from outside the function.
function counterModule() { let count = 0; // private variable that will be unaccessible directly from outside the function // returning an object with methods that can access the count variable return { increment() { return count++; }, decrement() { return count--; }, getCount() { return count; } }; }
-
Module pattern: Closures allow us to create modules that have private variables and methods that can't be accessed directly from outside the module.
-
-
One of the biggest examples of closures are:
-
timers
function sayHi() { let phrase = 'Hello'; setTimeout(function () { alert(phrase); }, 1000); } sayHi(); // Hello after 1 second // it remembers the phrase variable even after the function has finished and was popped off the call stack
- This is because the
setTimeoutfunction is higher-order function that takes a function as an argument, and it's important to be able to access thephrasevariable even after thesayHifunction has finished executing, and the callback function has been moved to themicrotask queuein the event loop.
- This is because the
-
event listeners
(function () { let count = 0; document.getElementById('button').addEventListener('click', function () { alert(count++); // we can access count variable even after the IIFE function has finished executing }); })();
-
Private variables / Modules
function counterModule() { let count = 0; // private variable that will be unaccessible directly from outside the function // returning an object with methods that can access the count variable return { increment() { return count++; }, decrement() { return count--; }, getCount() { return count; } }; } let counter = counterModule(); counter.increment(); // 0 counter.increment(); // 1 counter.getCount(); // 2 counter.count; // undefined (can't access count directly) ❌ (private variable)
-
-
When on an interview, a frontend developer gets a question about “what’s a closure?”, a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the
[[Environment]]property and how Lexical Environments work (Where the function was created, not where it was called). -
We can't manually create closures, they are a natural part of the language that happens automatically when a function is created.
- Because we can't access the
[[Environment]]property directly, but it's there in the engine internals.
- Because we can't access the
-
To manually read the variables in the closure of a function:
function g() { let b = 777; return function () { alert(value); }; } let f = g(); // while g() is called, the value is remembered console.dir(f); // look at the [[Scopes]] property
-
this is a form of Closure, as in this example:
We can do this because javascript functions are first-class citizens (can be passed around like any other value), and closures / backpacks allow us to keep the variables in the function's scope even after the function has finished executing.
-
most importantly ->
generatedFuncdoesn't have any connection tocreateFunction, as:- here
generatedFuncis not equal tocreateFunctionfunction - but it's equal to "what
createFunctionfunction has returned when it was created(ran)"
- here
-
Please note that if
f()is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. In the code below, all 3 of them:.function f() { let value = Math.random(); // 0.838 return function () { return value; }; } // 3 functions in array, every one of them links to Lexical Environment (same random value) let arr = [f(), f(), f()]; // [0.838, 0.838, 0.838]
- It's usually memory-efficient as it caches the values inside its block-scoped (where it was called) (its local memory (like Backpack) + the returned value from the function) in case that it was called again, this will be in the hidden property called
__scope__.-
all functions have the hidden property named
[[Environment]]>
- actually the Backpack doesn't contain "all" the local memory of the function, but it contains only the values/variables that will be used in the function body and will get rid of other unused things in the local memory using garbage collection
- A Lexical Environment object dies (garbage collected) when it becomes unreachable (just like any other object). In other words, it exists only while there’s at least one nested function referencing it.
- But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it’s obvious from the code that an outer variable is not used – it is removed.
- this is the main concept behind iterator function, where it persists a function called returnNextElement, which has:
- Our underlying array itself
- The position we are currently at in our ‘stream’ of elements
- The ability to return the next element
- A Lexical Environment object dies (garbage collected) when it becomes unreachable (just like any other object). In other words, it exists only while there’s at least one nested function referencing it.
-
So iterators turn our data into ‘streams’ of actual values we can access one after another.
-
JavaScript’s built in iterators are actually objects with a next() method that when called returns the next element from the ‘stream’/ flow
-
- Encapsulation: prevent unwanted access to inner variables/functions
-
It's a function that has the following properties:
- Deterministic / Predictable: Always returns the same result for the same input.
- No side-effects: Doesn't change anything outside of the function.
- No shared state: Doesn't depend on the state of the program.
- Immutable: Doesn't change its arguments.
-
pure function must return something
-
side-effects: are about MODIFICATION and doesn't count if we created new item
- usually it's anything that the function does other that returning a value
-
Benefits of pure functions:
- Self-documenting: Easier to understand and debug.
- Testable: Easier to test, as they don't depend on external state.
- Concurrency: Easier to run in parallel, as they don't depend on shared state.
- Memoization: the return value can be cached or memoized for performance optimization.
-
Examples:
-
Pure functions
-
Non-Pure functions
// function that consoles something to the world // functions that returns different result for the same input (like using Math.random())
-
Decorator is a wrapper around a function that alters its behavior. The main job is still carried out by the function.
-
Ex: create a wrapper-function around another function (Pure function) to cache its (behavior or the return-value) for multiple use
-
here: The result of
cachingDecorator(func)is a "wrapp"r”: function(x) that “wraps” the call of func(x) into caching logic:function slow(x) { // there can be a heavy CPU-intensive job here alert(`Called with ${x}`); return x; } function cachingDecorator(func) { let cache = new Map(); return function (x) { if (cache.has(x)) { // if there's such key in cache return cache.get(x); // read the result from it } let result = func(x); // otherwise call func cache.set(x, result); // and cache (remember) the result return result; }; } slow = cachingDecorator(slow); alert(slow(1)); // slow(1) is cached and the result returned alert('Again: ' + slow(1)); // slow(1) result returned from cache
-
there are several benefits of using a separate
cachingDecorator()instead of altering the code ofslow()itself:- The
cachingDecorator()is reusable. We can apply it to another function. - The caching logic is separate, it did not increase the complexity of
slow()itself (if there was any). - We can combine multiple decorators if needed.
- The
-
The caching decorator mentioned above is not suited to work with object methods. The reason is that the wrapper calls the original function as func(x). And, when called like that, the function gets this = undefined.
-
To solve this, we need to do one of the followings
- using the built-in function method func.call(context, …args) that allows to call a function explicitly setting this keyword.
- It runs func providing the first argument as
this, and the next as the arguments.
- It runs func providing the first argument as
- using the built-in function method func.call(context, …args) that allows to call a function explicitly setting this keyword.
In functional programming, we avoid mutable state, and therefore avoid iterative loops using for or while. As an alternative to iteration, we use recursion to break down the problem into smaller ones.
Recursion: is a programming pattern that is useful in situations when a task can be naturally split into several tasks of the same kind, but simpler. Or when a task can be simplified into an easy action plus a simpler variant of the same task.
- When a function solves a task, in the process it can call many other functions. A partial case of this is when a function calls itself. That’s called recursion.
A recursive function has two parts:
Base case: condition(s) under which the function returns an output without making a recursive callRecursive case: condition(s) under which the function calls itself to return the output
Difference between iteration and recursion:
- A recursive solution is usually shorter than an iterative one.
- Recursion: has one problem, in typical JavaScript implementations, it’s about three times slower than the looping version. Running through a simple loop is generally cheaper than calling a function multiple times.
- Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.
- But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts.
- A recursive solution is usually shorter than an iterative one.
recursion sometimes take long time as it calls multiple functions at the same time which occupies the call stack
-
one solution is to use memoization (caching)
- A loop-based algorithm is more memory-saving.
- The iterative solution uses a single context (place in memory which is the iteration index) changing
iand result in the process. Its memory requirements are small, fixed and do not depend onn.
-
another solution is to use tail call optimization
- Note: not all JavaScript engines implements tail calls
-
The maximal recursion depth is limited by JavaScript engine. We can rely on it being
10000, some engines allow more, but100000is probably out of limit for the majority of them. -
Recursive structures: A recursive (recursively-defined) data structure is a structure that replicates itself in parts.
- For examples:
HTMLandXML documents.HTML-tagmay contain a list of: [Text pieces,HTML comments,other HTML tags]
- For examples:
-
First-class objects: Functions are values that can:
- Be assigned to variables
- Be passed as arguments to other functions
- Be returned from functions (enables closures)
-
Higher Order Functions: Functions that accept or return other functions

-
e.g.,
map,filter,reduce,sort,forEach -
It gives us ability to not just tell the function what data to use, but also how to use it (by passing the function that will be used on the data)
-
Benefits: makes functions:
- More readable & generic
- easier to debug
- makes code DRY
-
Allows chaining functions by passing the output of one as the input to another.
function multiplyBy(num) { return function (x) { return x * num; }; } // or const multiplyBy = (num) => (x) => x * num; multiplyBy(2)(3); // 6
-
-
Core of functional and asynchronous programming: Passing functions as arguments is key, even with
promisesorasync/await.
call, apply, bind are higher order functions used to set the this keyword and arguments of a (function / method) manually.
More Here
-
call- Invokes a function with a specified
thiscontext.
let human = { name: 'Ahmed' }; function sayName(greeting) { console.log(greeting + ' ' + this.name); } sayName.call(human, 'Hi!'); // Outputs Hi! Ahmed
- Invokes a function with a specified
-
apply-
Similar to
call, but different on how the arguments are passed. -
it takes arguments as an array which is useful when the method has multiple arguments.
let human = { name: ‘Ahmed’ } function sayName(greeting, city) { console.log(greeting + ' ' + this.name + ' from ' + city) } sayName.apply(human, ['Hi', 'Cairo']) // Outputs Hi! Ahmed from Cairo
-
it's not used in modern javascript because we can instead use
spread operator (...)withcall()sayName.call(human, ...['Hi', 'Cairo']); // instead of sayName.apply(human, ['Hi', 'Cairo']); // ------------------------------------------------ Math.max.apply(null, [1, 2, 3]); // Outputs 3 // instead of Math.max(1, 2, 3);
-
-
bind-
Returns a new function with
thisbounded to a specified context.let human = { name: 'Ahmed' }; function sayName(greeting, city) { console.log(greeting + ' ' + this.name + ' from ' + city); } let greet = sayName.bind(human); greet('Hi!', 'Cairo'); // Outputs Hi! Ahmed from Cairo
-
Used for later use, not immediate invocation.
-
Takes arguments like
call. -
Common use cases:
-
callbacks (event listeners or timers)
let button = document.getElementById('button'); function sayName(greeting, city) { console.log(greeting + ' ' + this.name + ' from ' + city); } button.addEventListener('click', sayName); // Outputs undefined undefined button.addEventListener('click', sayName.bind(human, 'Hi!', 'Cairo')); // Outputs Hi! Ahmed from Cairo
- Note that we don't use
callorapplyhere because we don't want to call the function immediately, we just want to bind thethiskeyword and the arguments to the function. as when we pass a function to an event listener, we pass it for future use, not for immediate invocation.
- Note that we don't use
-
partial application
-
Allows setting arguments for reuse
const human = { name: 'Ahmed' }; function sayName(greeting, city) { console.log(greeting + ' ' + this.name + ' from ' + city); } let greet = sayName.bind(human, 'Hi!'); greet('Cairo'); // Outputs Hi! Ahmed from Cairo greet('London'); // Outputs Hi! Ahmed from London // or function multiply(a, b) { return a * b; } const multiplyByTwo = multiply.bind(null, 2); multiplyByTwo(4); // Outputs 8 // ------------------------------------------------ // just arguments function greet(greeting, name) { return `${greeting} ${name}`; } const sayHello = greet.bind(null, 'Hello'); // use null as we don't have a context sayHello('Ahmed'); // Outputs Hello Ahmed
-
-
-
-
Notes:
-
These functions are accessible from
Function.prototype. -
There're other higher order functions like (
map,filter,reduce,sort,forEach). -
You can use these methods with functions that don't have a
thiskeyword used inside them. by passingnullorundefinedas the first argument.function sayHello(name) { console.log('Hello' + name); } sayHello.call(null, 'Ahmed'); // Hello Ahmed
-
We can call these methods with arrow functions but they will not work as expected because arrow functions don't have their own
thiskeyword. -
We can call these methods with part of the arguments, this is called partial application.
function multiply(a, b) { return a * b; } let multiplyByTwo = multiply.bind(this, 2); // now we don't need to write the first argument each time as it's now "pre-set" multiplyByTwo(4); // Outputs 8 multiplyByTwo(5); // Outputs 10
-
- Referential Transparency: emphasizes that an expression (or function) in a JavaScript program may be replaced by its value / returned value or any other variable having the same value without changing the result of the program. As a result, methods should always return the same value for the given argument without any side effects
- it's like (I can replace the function-call with its returned-output and it’s the same)
It's the combination of multiple functions to create a more complicated one. It's a way to combine functions where the output of one function is the input of another.
- It is the combination of two functions into one, that when applied, returns the result of the chained functions (using reduction of the result value).
In functional Programming, Composition takes the place of inheritance in OOP.
Note that "composition" in OOP is different from "composition" in functional programming. More here composition in OOP
-
Chaining with dots relies on JavaScript prototype feature - functions return arrays which have access to all the high-order-function (map, filter, reduce), but if I want to chain functions that just return a regular output:
const multiplyBy2 = x => x * 2; const add3 = x => x + 3; const divideBy5 = x => x / 5; const initialResult = multiplyBy2(11); const nextStep = add3(initialResult); const finalStep = divideBy5(nextStep); console.log('finalStep', finalStep);
- But that’s risky, people can overwrite, So we can use composition to make it more readable and less risky.
-
Composition is a fancy term which means "combining pure functions to build more complicated ones" (make complex programs out of simple functions).
- Useful when you want to have specific order to run the (operations / steps / functions) on the data
let compose = (fn1, fn2) => { return function (value) { return fn1(fn2(value)); }; }; // or const compose = (fn1, fn2) => fn1(fn2()); // --------------------------------------------------------- // Pipe (opposiste order) let pipe = (fn1, fn2) => { return function (value) { return fn2(fn1(value)); }; }; // or const pipe = (fn1, fn2) => fn2(fn1());
-
Example of generic compose function:
function compose(...functions) { return function (value) { return functions.reduceRight((acc, fn) => fn(acc), value); // reduceRight to start from the right }; } // ---------------------------- Usage ---------------------------- // const lowerCaseString = str => str.toLowerCase(); const splitWords = str => str.split(' '); const joinWithDashes = arr => arr.join('-'); const transformString = compose(joinWithDashes, splitWords, lowerCaseString); console.log(transformString('I Love JavaScript')); // i-love-javascript
- Here, we use (spread operator,
reduce())
- Here, we use (spread operator,
-
Why use composition?
- Easier to add features: We can list our units of code by name and have them run one by one as independent, self-contained pieces.
- More readable:
reduceis often wrapped in compose to say ‘combine up’ the functions to run our data through them one by one. The style is ‘point free’. - Easier to debug: I know exactly the line of code my bug is in - it’s got a label!
we can convert functions more easily to make them suit our task Without writing a new function from scratch, it will appear as we edit the function's body, but in reality we're creating new wrapper outer function and using its Backpack that contains our function we saved --> it's using closure to supercharge our functions
-
here we want to edit the function
multiplyBy2()to only run onceconst oncify = convertMe => { let counter = 0; const inner = input => { if (counter === 0) { const output = convertMe(input); counter++; return output; } return 'Sorry'; }; return inner; }; const multiplyBy2 = num => num * 2; const oncifiedMultiplyBy2 = oncify(multiplyBy2); oncifiedMultiplyBy2(10); // 20 oncifiedMultiplyBy2(7); // Sorry
They differ from Regular functions which return only single value (or nothing). Generators can return (“yield”) multiple values, one after another, on-demand. They work great with iterables, allowing to create data streams with ease.
Generators Summary:
- When we define a generator, when we call
.next()method on it => it will execute the code until it reaches the firstyieldstatement, then it will return the value of theyieldstatement and stop the execution of the function. When we call.next()again, it will continue the execution of the function until it reaches the nextyieldstatement, then it will return the value of theyieldstatement and stop the execution of the function. And so on.- If in the next time we call
.next()and passing on a value as a parameter to the.next()method, this value will be the result of theyieldstatement. So, the value of theyieldstatement will be replaced by the value that we passed to the.next()method.- if we kept calling
.next()method, it will keep executing the code until it reaches the end of the function, then it will return an object with the value ofdoneproperty set totrueand the value of thevalueproperty will be the value that we passed to the.next()method in the last time. ->{ done: true, value: 10 }
-
Once we start thinking of our data as flows (where we can pick of an element one-by-one) we can rethink how we produce those flows - JavaScript now lets us produce the flows using a function
-
the Generator function allows us to exit and renter the execution-context of function manually, unlike iterators which this happens dynamically
-
To create a generator, we need a special syntax construct:
function*, so-called “generator function”. -
Generator functions behave differently from regular ones. When such function is called, it doesn’t run its code. Instead it returns a special object, called “generator object”, to manage the execution.
function* generateSequence() { yield 1; yield 2; return 3; } // "generator function" creates "generator object" let generator = generateSequence(); // The function code execution hasn’t started yet alert(generator); // [object Generator]
Note:
function* f(…)orfunction *f(…)?
- Both syntaxes are correct. But usually the first syntax is preferred
- we usually use the second syntax in object/class ES6 methods
-
The main method of a generator is
next(). When called, it runs the execution until the nearestyield <value>statement (value can be omitted, then it’sundefined). Then the function execution pauses, and the yielded value is returned to the outer code.-
The result of
next()is always an object with two properties:
value: the yielded value.done:trueif the function code has finished, otherwisefalse.- When the generator is done. We should see it from
done: trueandvalue: undefined, and New calls togenerator.next()don’t make sense any more. If we do them, they return the same object:{done: true}.
- When the generator is done. We should see it from
function* generateSequence() { yield 1; yield 2; return 3; } let generator = generateSequence(); let one = generator.next(); alert(JSON.stringify(one)); // {value: 1, done: false}
-
"yield" is a two-way street: That’s because
yieldis a two-way street: it not only returns the result to the outside, but also can pass the value inside the generator.- when we pass a value to the
next()method, it will be evaluated as the yielded value and we will continue the function until we find another yield
function* createFlow() { const num = 10; const newNum = yield num; yield 5 + newNum; yield 6; } const returnNextElement = createFlow(); // The function code execution hasn’t started yet const element1 = returnNextElement.next(); // 10 const element2 = returnNextElement.next(2); // 7
- when we pass a value to the
-
-
generator.return(value)-
it finishes the generator execution and return the given value.
function* gen() { yield 1; yield 2; yield 3; } const g = gen(); g.next(); // { value: 1, done: false } g.return('foo'); // { value: "foo", done: true } g.next(); // { value: undefined, done: true }
-
Often we don’t use it, as most of time we want to get all returning values, but it can be useful when we want to stop generator in a specific condition.
-
-
Iterables are objects that implement the
Symbol.iteratormethod, and we can useES6features on them likefor..ofloop and spread operator...function* generateSequence() { yield 1; yield 2; return 3; } let generator = generateSequence(); for (let value of generator) { alert(value); // 1, then 2 }
-
objectis not iterable by default, that's why we can't usefor..ofloop on it, but we can make it iterable by adding theSymbol.iteratormethod to it -
Note: the example above shows
1, then2, and that’s all. It doesn’t show3!- It’s because
for..ofiteration ignores the last value, whendone: true. So, if we want all results to be shown byfor..of, we must return them withyield
- It’s because
-
-
As generators are iterable, we can call all related functionality, e.g. the spread syntax
...:function* generateSequence() { yield 1; yield 2; yield 3; } let sequence = [0, ...generateSequence()]; alert(sequence); // 0, 1, 2, 3
-
returnNextElementis a special object (a generator object) that when itsnextmethod is run starts (or continues) running createFlow execution-context until it hitsyieldand returns out the value being yielded without continuing the function- We end up with a ‘stream’/flow of values that we can get one-by-one by running
returnNextElement.next() - And most importantly, for the first time we get to pause (‘suspend’) a function being run and then return to it by calling
returnNextElement.next()
- We end up with a ‘stream’/flow of values that we can get one-by-one by running
-
This is actually what happens behind the scene in Asynchronous javascript, as we want to:
- Initiate a task that takes a long time (e.g. requesting data from the server)
- Move on to more synchronous regular code in the meantime
- Run some functionality once the requested data has come back
function doWhenDataReceived(value) { returnNextElement.next(value); } function* createFlow() { const data = yield fetch('http://twitter.com/will/tweets/1'); console.log(data); } const returnNextElement = createFlow(); const futureData = returnNextElement.next(); futureData.value.then(doWhenDataReceived);
-
Async/await simplifies all this and finally fixes the inversion of control problem of callbacks
- as it automatically triggers the function on the promise-resolution (this functionality is still added to the micro-task queue though)
async function createFlow() { console.log('Me first'); const data = await fetch('https://twitter.com/will/tweets/1'); console.log(data); } createFlow(); console.log('Me second');
-
Infinite Scrolling: Generators are perfect for infinite scrolling. We can create a generator that fetches data from the server and yields it one by one. Then we can use the generator in a
for..ofloop to display the data.// Get 10 images batch by each request const allImages = Array.from({ length: 1000 }, (_, i) => `https://picsum.photos/id/${i}/200/200`); function* getBatchOfImages(images, batchSize = 10) { let index = 0; while (index < images.length) { yield images.slice(index, index + batchSize); index += batchSize; } } const imageGenerator = getBatchOfImages(allImages); imageGenerator.next().value; // [1st 10 images] imageGenerator.next().value; // [2nd 10 images]
-
NaN:
typeof NaN=Number--> as it's invalid number- example of
NaN-> division on strings ->"apple" / 3 Nan === NaN--> false- because
NaNis a special value that’s not equal to any value, including itself. and due to "Type coercion",NaNis not equal toNaNbecause it's not a number.
- because
alert(isNaN("str"))--> true- behind the scenes,
isNaN()converts its argument to a number, soisNaN("str")converts"str"to a number and then checks the result for beingNaN, which istrue.
- behind the scenes,
-
BigInt:
-
BigIntis a special numeric type that provides support for integers of arbitrary length. -
BigInt can mostly be used like a regular number,
-
All operations on
bigintsreturnbigints. -
it ends with
n->1234567890123456789012345678901234567890n; -
for example:
alert(1n + 2n); // 3 alert(5n + 2n); // 2 (not 2.5)
-
We can’t mix
bigintsand regular numbers:alert(1n + 2); // Error: Cannot mix BigInt and other types
- to do so, we can explicitly convert them if needed: using either
BigInt()orNumber()constructors
- to do so, we can explicitly convert them if needed: using either
-
The unary plus is not supported on bigints
alert(+25n); // error
-
-
Polyfills
- Polyfilling bigints is tricky. The reason is that many JavaScript operators, such as
+,-and so on behave differently with bigints compared to regular numbers. - we can use the polyfill proposed by the developers of JSBI library.
- Polyfilling bigints is tricky. The reason is that many JavaScript operators, such as
-
MutationObserver is a built-in object that observes a DOM element and fires a callback when it detects a change.
// First, we create an observer with a callback-function:
let observer = new MutationObserver(callback);
// And then attach it to a DOM node:
observer.observe(node, config);configis an object with boolean options “what kind of changes to react on”:childList– changes in the direct children of nodesubtree– in all descendants of nodeattributes– attributes of nodeattributeOldValue– iftrue, pass both the old and the new value of attribute to callback (see below), otherwise only the new one (needs attributes option),attributeFilter– an array of attribute names, to observe only selected ones.characterData– whether to observe node.data (text content),characterDataOldValue– if true, pass both the old and the new value of node.data to callback (see below), otherwise only the new one (needs characterData option).
<div contenteditable id="elem">
Click and
<b>edit</b>
, please
</div>
<script>
let observer = new MutationObserver(mutationRecords => {
console.log(mutationRecords); // console.log(the changes)
});
// observe everything except attributes
observer.observe(elem, {
childList: true, // observe direct children
subtree: true, // and lower descendants too
characterDataOldValue: true // pass old data to callback
});
</script>- UseCases"
- Using
MutationObserver, we can detect when the unwanted element appears in our DOM and remove it. ex: third-party script that contains useful functionality, but also does something unwanted, e.g. shows ads<div class="ads">Unwanted ads</div>. - There are other situations when a third-party script adds something into our document, and we’d like to detect, when it happens, to adapt our page, dynamically resize something etc.
- Using
It's an advance topic and you can find it here Selection and Range if needed
-
consoleis not built in js, but it's from thewebApi -
The saying that: "Everything in JavaScript is an object" is not true. There are 8 basic data types in JavaScript:
BooleanNullUndefinedNumberObject.StringBigIntSymbol-> (new in ES6)- some of them may have object-like features, but they are not objects. An object is a collection of key-value pairs. And the value can be accessed by using the key of the object.
-
Statements Vs Expressions :
Expression: it's a piece of code that produces a value and expected to be used in places where values are expected- Ex:
5+4orTernary Operatororshort circuitingand then you can use it in a${}
- Ex:
Statements: it's a piece of code that does something and doesn't produce a value- Ex:
ifstatement orforloop
- Ex:
-
Strict mode:
"use strict"enables secure coding by enforcing stricter parsing and error handling.- Place
"use strict"at the top of your scripts to enable it. - Benefits:
- Forbids certain operations, ex:
- Using a variable without declaring it first.
- Having a default value for
thisin functions and global scope ->thisisundefinedin strict mode. - Deleting a variable or function using the
deleteoperator. - Duplicating a parameter name in a function.
- Duplicating a property name in an object.
- Using
evalorargumentsas variable names. - Using
with.
- Throws errors for bad syntax instead of failing silently.
- Example: Mistyping a variable name throws an error instead of creating a global variable.
- Forbids certain operations, ex:
- Usage:
- Modern JavaScript classes and modules enable strict mode automatically, so explicit
"use strict"is often unnecessary.
- Modern JavaScript classes and modules enable strict mode automatically, so explicit
-
Refactoring the code: means the process of restructuring code without changing or adding to its external behavior and functionality. -
in
DOM=>:rootelement is calleddocument.documentElement -
to make anything
immutable:Object.freeze(obj)- it only freezes the first level of an object (not a
deep-freeze)
Object.freeze({ jonas: 1500, matilda: 100 }); Object.freeze([ { value: 250, description: 'Sold old TV 📺', user: 'jonas' }, { value: -45, description: 'Groceries 🥑', user: 'jonas' } ]); // or anything because everything is an "object"
- it only freezes the first level of an object (not a
-
if you used const in a
for loop->for ( i = 0; i < 3; i++) {}you will get error, as you won't be able to re-assigni -
There is a widespread practice to use constants (named using capital letters and underscores) as aliases for difficult-to-remember values that are known prior to execution.
const COLOR_ORANGE = '#FF7F00';
-
short-circuiting ->
alert(alert(1) && alert(2)); // undefined // as alert doesn't return anything (returns undefined)
-
How is every thing is an Object ? --> If one would want to do something with a primitive, like a
stringor anumber. It would be great to access them using methods.-
The language allows access to methods and properties of
strings,numbers,booleansandsymbols. -
In order for that to work, a special “object wrapper” that provides the extra functionality is created, and then is destroyed.
-
you can also create this wrapper-object using constructor like
new, but it's highly unrecommended. Things will go crazy in several places.alert(typeof 0); // "number" alert(typeof new Number(0)); // "object"! let zero = new Number(0); if (zero) { // zero is true, because it's an object alert('zero is truthy!?!'); }
-
On the other hand, using the same functions
String/Number/Booleanwithoutnewis totally fine and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive).let num = Number('123'); // convert a string to number
-
The special primitives
nullandundefinedare exceptions. They have no corresponding “wrapper objects” and provide no methods. In a sense, they are “the most primitive”.
-
-
NO JAVASCRIPT
- If you have an
accordion menu,tabbed panels, andresponsive sliderall hide some of their content by default. This content would be inaccessible to visitors that do not have JavaScript enabled if we didn't provide alternative styling. - One way to solve this is by adding a class attribute whose value is "no-js" to the opening
<html>tag. This class is then removed by JavaScript (using thereplace()method of the String object) - if JavaScript is enabled. The "no-js" class can then be used to provide styles targeted to visitors who do not have JavaScript enabled.
<!DOCTYPE html> <html class="no-js"></html>
var elDocument = document.documentElement; elDocument.className = elDocument.className.replace(/(^|\s)no-js(\s|$)/, '$1');
- If you have an
-
polyfills
-
We use the global object to test for support of modern language features.
- If there’s none (say, we’re in an old browser), we can create “polyfills”: add functions that are not supported by the environment, but exist in the modern standard.
// For instance, test if a built-in Promise object exists (it doesn’t in really old browsers): if (!window.Promise) { alert('Your browser is really old!'); window.Promise = ... // custom implementation of the modern language feature }
-
-
In JavaScript, functions are objects.
-
Function objects contain some useable properties; For instance, a function’s name is accessible as the
“name”property:function sayHi() { alert('Hi'); } alert(sayHi.name); // sayHi
-
the name-assigning logic is smart. It also assigns the correct name to a function even if it’s created without one, and then immediately assigned:
let sayHi = function () { alert('Hi'); }; alert(sayHi.name); // sayHi (there's a name!)
-
The “length” property
- built-in property
“length”that returns the number of function parameters, for instance:
function f1(a) {} function f2(a, b) {} function many(a, b, ...more) {} alert(f1.length); // 1 alert(f2.length); // 2 alert(many.length); // 2
- The length property is sometimes used for introspection in functions that operate on other functions.
- For instance, in the code below the ask function accepts a question to ask and an arbitrary number of handler functions to call. Once a user provides their answer, the function calls the handlers. We can pass two kinds of handlers:
- A zero-argument function, which is only called when the user gives a positive answer.
- A function with arguments, which is called in either case and returns an answer.
- built-in property
-
-
eval() built-in function
- it allows to execute a string of code.
let code = 'alert("Hello")'; eval(code); // Hello
-
pre-increment
++ivs post-incrementi++++i=> first increment then returni++=> first return then increment




























