Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
JS concepts


In JavaScript, objects have a special hidden property [[Prototype]] (as named in the specification), that is either null or references another object. That object is called “a prototype”:

The property [[Prototype]] is internal and hidden, but there are many ways to set it.

One of them is to use the special name __proto__, like this:

let animal = {
  walks: true

let rabbit = {
 jumps: true 

rabbit__proto__ = animal

When we read a property from object, and it’s missing, JavaScript automatically takes it from the prototype. In programming, this is called prototypal inheritance.

The prototype chain can be longer as well

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");

let rabbit = {
  jumps: true,
  __proto__: animal

let longEar = {
  earLength: 10,
  __proto__: rabbit

// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)



  1. The references can’t go in circles. JavaScript will throw an error if we try to assign proto in a circle.
  2. The value of proto can be either an object or null. Other types are ignored.

Also it may be obvious, but still: there can be only one [[Prototype]]. An object may not inherit from two others.

Writing doesn’t use prototype

The prototype is only used for reading properties.

Write/delete operations work directly with the object.

let user = {
  name: "John",
  surname: "Smith",

  set fullName(value) {
    [, this.surname] = value.split(" ");

  get fullName() {
    return `${} ${this.surname}`;

let admin = {
  __proto__: user,
  isAdmin: true

alert(admin.fullName); // John Smith (*)

// setter triggers!
admin.fullName = "Alice Cooper"; // (**)

alert(admin.fullName); // Alice Cooper, state of admin modified
alert(user.fullName); // John Smith, state of user protected

The value of “this”

this is not affected by prototypes at all.

No matter where the method is found: in an object or its prototype. In a method call, this is always the object before the dot.

That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object.

// animal has methods
let animal = {
  walk() {
    if (!this.isSleeping) {
      alert(`I walk`);
  sleep() {
    this.isSleeping = true;

let rabbit = {
  name: "White Rabbit",
  __proto__: animal

// modifies rabbit.isSleeping

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)

for…in loop

The loop iterates over inherited properties too.

For example:

let animal = {
  eats: true

let rabbit = {
  jumps: true,
  __proto__: animal

// Object.keys only returns own keys
alert(Object.keys(rabbit)); // jumps

// loops over both own and inherited keys
for(let prop in rabbit) alert(prop); // jumps, then eats

If you don't want to access inherited properties use obj.hasOwnProperty(key). It returns true if obj has its own (not inherited) property named key.

let animal = {
  eats: true

let rabbit = {
  jumps: true,
  __proto__: animal

for(let prop in rabbit) {
  let isOwn = rabbit.hasOwnProperty(prop);

  if (isOwn) {
    alert(`Our: ${prop}`); // Our: jumps
  } else {
    alert(`Inherited: ${prop}`); // Inherited: eats


F.prototype(function prototype)

Please note that F.prototype here means a regular property named "prototype" on F. It sounds something similar to the term “prototype”, but here we really mean a regular property with this name.

let animal = {
  eats: true

function Rabbit(name) { = name;

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true

Setting Rabbit.prototype = animal literally states the following: "When a new Rabbit is created, assign its [[Prototype]] to animal".


F.prototype only used at new F time

F.prototype property is only used when new F is called, it assigns [[Prototype]] of the new object.

If, after the creation, F.prototype property changes (F.prototype = "another object"), then new objects created by new F will have another object as [[Prototype]], but already existing objects keep the old one.

Default F.prototype, constructor property

Every function has the "prototype" property even if we don’t supply it.

The default "prototype" is an object with the only property constructor that points back to the function itself.

Like this:

function Rabbit(){
  // some code

console.log(Rabbit.prototype.constructor === Rabbit) // true

Native prototypes


let obj = {};
alert( obj ); // "[object Object]" ?

Where’s the code that generates the string "[object Object]"? That’s a built-in toString method, but where is it? The obj is empty!

…But the short notation obj = {} is the same as obj = new Object(), where Object is a built-in object constructor function, with its own prototype referencing a huge object with toString and other methods.



let arr = new Array();
let arr = [];

Get last elements with “at”

  1. is exactly the same as arr[i], if i >= 0.
  2. for negative values of i, it steps back from the end of the array.

Methods pop/push, shift/unshift

Queue operations

  1. arr.push()
  2. arr.shift()

Stack operations

  1. arr.push()
  2. arr.pop()

Methods that work with the end of the array

  1. pop
  2. push

Methods that work with the beginning of the array

  1. shift
  2. unshift


Gives comma separated values as string

let arr = [1, 2, 3];

alert( arr ); // 1,2,3

alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"
  • Arrays do not have Symbol.toPrimitive, neither a viable valueOf, they implement only toString conversion, so here [] becomes an empty string, [1] becomes "1" and [1,2] becomes "1,2".

Array methods

Add/remove items

We already know methods that add and remove items from the beginning or the end:

arr.push(...items) – adds items to the end, arr.pop() – extracts an item from the end, arr.shift() – extracts an item from the beginning, arr.unshift(...items) – adds items to the beginning.


  • Modifies array in-place
  • Returns deleted elements

arr.splice(start[, deleteCount, elem1, ..., elemN])

  • Allows negative elements as well
  • If element are to be inserted in the array, they are inserted before start index
let arr = [1, 2, 5];

// from index -1 (one step from the end)
// delete 0 elements,
// then insert 3 and 4
arr.splice(-1, 0, 3, 4);

alert( arr ); // 1,2,3,4,5


arr.slice([start], [end])

  • It returns a new array copying to it all items from index start to end (not including end). Both start and end can be negative, in that case position from array end is assumed.


arr.concat(arg1, arg2...)

  • The method arr.concat creates a new array that includes values from other arrays and additional items.
  • It accepts any number of arguments – either arrays or values.
  • The result is a new array containing items from arr, then arg1, arg2 etc.
  • If an argument argN is an array, then all its elements are copied. Otherwise, the argument itself is copied.

Iterate: forEach

The arr.forEach method allows to run a function for every element of the array.

arr.forEach(function(item, index, array) { // ... do something with item });

Searching in array

  1. arr.indexOf arr.indexOf(item, from)
  2. arr.lastIndexOf arr.lastIndexOf(item, from)
  3. arr.includes arr.includes(item, from)
  4. arr.find(fn) arr.findIndex(fn) Imagine we have an array of objects. How do we find an object with the specific condition? and findIndex
let result = arr.find(function(item, index, array) {
  // if true is returned, item is returned and iteration is stopped
  // for falsy scenario returns undefined

The arr.findIndex method is essentially the same, but it returns the index where the element was found instead of the element itself and -1 is returned when nothing is found.

  1. filter let results = arr.filter(function(item, index, array) { // if true item is pushed to results and the iteration continues // returns empty array if nothing found });

Transform an array

  1. map
let result =, index, array) {
  // returns the new value instead of item
  1. sort

    -The call to arr.sort() sorts the array in place, changing its element order. -The items are sorted as strings by default. -Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed "2" > "15".

  2. reduce/reduceRight

let value = arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]);


Map is a collection of keyed data items, just like an Object. But the main difference is that Map allows keys of any type.


  1. new Map() – creates the map.
  2. map.set(key, value) – stores the value by the key.
  3. map.get(key) – returns the value by the key, undefined if key doesn’t exist in map.
  4. map.has(key) – returns true if the key exists, false otherwise.
  5. map.delete(key) – removes the value by the key.
  6. map.clear() – removes everything from the map.
  7. map.size – returns the current element count.
let map = new Map();

map.set('1', 'str1');   // a string key
map.set(1, 'num1');     // a numeric key
map.set(true, 'bool1'); // a boolean key

// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'

alert( map.size ); // 3

Chaining in map

map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

Iteration over Map

  1. map.keys() – returns an iterable for keys,
  2. map.values() – returns an iterable for values,
  3. map.entries() – returns an iterable for entries [key, value], it’s used by default in for..of.
let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]

// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion

// iterate over values (amounts)
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50

// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
  alert(entry); // cucumber,500 (and so on)
  • The iteration goes in the same order as the values were inserted. Map preserves this order, unlike a regular Object.

Map from Object: Object.entries

let obj = {
  name: "John",
  age: 30

let map = new Map(Object.entries(obj));

Object from Map: Object.fromEntries

let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);

let obj = Object.fromEntries(map.entries());

This also works, map without entries

let obj = Object.fromEntries(map); // omit .entries()


A Set is a special type collection – “set of values” (without keys), where each value may occur only once.


  1. new Set(iterable) – creates the set, and if an iterable object is provided (usually an array), copies values from it into the set.
  2. set.add(value) – adds a value, returns the set itself.
  3. set.delete(value) – removes the value, returns true if value existed at the moment of the call, otherwise false.
  4. set.has(value) – returns true if the value exists in the set, otherwise false.
  5. set.clear() – removes everything from the set.
  6. set.size – is the elements count.

Iteration over Set

let set = new Set(["oranges", "apples", "bananas"]);

for (let value of set) 

// the same with forEach:
set.forEach((value, valueAgain, set) => {

Weak Map

  • If we use an object as the key in a regular Map, then while the Map exists, that object exists as well. It occupies memory and may not be garbage collected.
  • WeakMap is fundamentally different in this aspect. It doesn’t prevent garbage-collection of key objects.
let john = { name: "John" };

let map = new Map();
map.set(john, "...");

john = null; // overwrite the reference

// john is stored inside the map,
// we can get it by using map.keys()
  • The first difference between Map and WeakMap is that keys must be objects, not primitive values:
let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // works fine (object key)

// can't use a string as the key
weakMap.set("test", "Whoops"); // Error, because "test" is not an object


  1. weakMap.get(key)
  2. weakMap.set(key, value)
  3. weakMap.delete(key)
  4. weakMap.has(key)


  • WeakMap does not support iteration and methods keys(), values(), entries(), so there’s no way to get all keys or values from it.
  • If an object has lost all other references (like john in the code above), then it is to be garbage-collected automatically. But technically it’s not exactly specified when the cleanup happens.


  1. The main area of application for WeakMap is an additional data storage.
    • If we’re working with an object that “belongs” to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive – then WeakMap is exactly what’s needed.
  2. Caching
    • We can store (“cache") results from a function, so that future calls on the same object can reuse it.
// 📁 cache.js
let cache = new Map();

// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculations of the result for */ obj;

    cache.set(obj, result);

  return cache.get(obj);

// Now we use process() in another file:

// 📁 main.js
let obj = {/* let's say we have an object */};

let result1 = process(obj); // calculated

// ...later, from another place of the code...
let result2 = process(obj); // remembered result taken from cache

// ...later, when the object is not needed any more:
obj = null;

alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!)

For multiple calls of process(obj) with the same object, it only calculates the result the first time, and then just takes it from cache. The downside is that we need to clean cache when the object is not needed any more.
Replace Map with WeakMap in the code above.

// 📁 cache.js
let cache = new WeakMap();

// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculate the result for */ obj;

    cache.set(obj, result);

  return cache.get(obj);

// 📁 main.js
let obj = {/* some object */};

let result1 = process(obj);
let result2 = process(obj);

// ...later, when the object is not needed any more:
obj = null;

// Can't get cache.size, as it's a WeakMap,
// but it's 0 or soon be 0
// When obj gets garbage collected, cached data will be removed as well


  • WeakSet behaves similarly to Weakmap
  • It is analogous to Set, but we may only add objects to WeakSet (not primitives).
  • An object exists in the set while it is reachable from somewhere else.
  • Like Set, it supports add, has and delete, but not size, keys() and no iterations.

Destructuring assignment

Destructuring assignment is a special syntax that allows us to “unpack” arrays or objects into a bunch of variables, as sometimes that’s more convenient.
Destructuring also works great with complex functions that have a lot of parameters, default values, and so on. Soon we’ll see that.

Array destructuring

// we have an array with the name and surname
let arr = ["John", "Smith"]

// destructuring assignment
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;

alert(firstName); // John
alert(surname);  // Smith

Works with any iterable on the right-side

let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]); syntax

  • Usually, if the array is longer than the list at the left, the “extra” items are omitted.
// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (from array)
alert(surname); // Anonymous (default used)

Object destructuring

let {var1, var2} = {var1:…, var2:…}

// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;

{“what : goes where”} = obj Default Value

let {width = 100, height = 200, title} = options;

Combine : and default values

let {width: w = 100, height: h = 200, title} = options; syntax

let options = {
  title: "Menu",
  height: 200,
  width: 100

// title = property named title
// rest = object with the rest of properties
let {title,} = options;

// now title="Menu", rest={height: 200, width: 100}
alert(rest.height);  // 200
alert(rest.width);   // 100

Nested destructuring

let options = {
  size: {
    width: 100,
    height: 200
  items: ["Cake", "Donut"],
  extra: true

// destructuring assignment split in multiple lines for clarity
let {
  size: { // put size here
  items: [item1, item2], // assign items here
  title = "Menu" // not present in the object (default value is used)
} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200
alert(item1);  // Cake
alert(item2);  // Donut

Smart function parameters

// we pass object to function
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]

// ...and it immediately expands it to variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
  // title, items – taken from options,
  // width, height – defaults used
  alert( `${title} ${width} ${height}` ); // My Menu 200 100
  alert( items ); // Item1, Item2


Syntax function({ incomingProperty: varName = defaultValue ... })

Difference between regular functions and arrow functions

  1. Arrow functions don't have their own bindings to this, arguments or super, and should not be used as methods.
  2. Arrow functions can’t run with new - Not having this naturally means another limitation: arrow functions can’t be used as constructors. They can’t be called with new.
  3. Arrow functions don't have access to the keyword.
  4. Arrow functions cannot be used as constructors.
  5. Arrow functions cannot use yield, within its body.

Arrow functions have no “this”

let group = {
  title: "Our Group",
  students: ["John", "Pete", "Alice"],

  showList() {
      student => alert(this.title + ': ' + student) // No error: Work as expected

  • Not having this naturally means another limitation: arrow functions can’t be used as constructors. They can’t be called with new.

Arrow functions VS bind

There’s a subtle difference between an arrow function => and a regular function called with .bind(this):

  1. .bind(this) creates a “bound version” of the function.
  2. The arrow => doesn’t create any binding. The function simply doesn’t have this. The lookup of this is made exactly the same way as a regular variable search: in the outer lexical environment.

Arrows have no “arguments”

  • That’s great for decorators, when we need to forward a call with the current this and arguments.
  • For instance, defer(f, ms) gets a function and returns a wrapper around it that delays the call by ms milliseconds:
function defer(f, ms) {
  return function() {
    setTimeout(() => f.apply(this, arguments), ms);

function sayHi(who) {
  alert('Hello, ' + who);

let sayHiDeferred = defer(sayHi, 2000);
sayHiDeferred("John"); // Hello, John after 2 seconds

Currying is the concept of breaking a function(that expects multiple arguments) that takes multiple argument into a series of functions that takes one argument. Source

Applications *Source

  • The curried function will go through the appliation and different other parts of the application will sprinkle their ingredients into it and eventually we will get our final output.
  • A curriable function takes an argument and adds this dependency to function untill all the dependencies have been fulfilled.
  • Currying can be used to implement async job. The curried function can be kept and passed around while other I/O operations are being carried out.



Encapsulates the IO operation and allows to obtain the result later.

Decorators and forwarding, call/apply

Transparent caching

If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations.

function cachingDecorator(fn){
  let m = new Map()
  return function(x){
      return m.get(x)
    } else {
      let result = fn(x)
      m.set(x, result)
      return result

In the code above cachingDecorator is a decorator: a special function that takes another function and alters its behavior.


  1. The cachingDecorator is reusable. We can apply it to another function.
  2. The caching logic is separate, it did not increase the complexity of slow itself (if there was any).
  3. We can combine multiple decorators if needed (other decorators will follow).

Using “” for the context

The caching decorator mentioned above is not suited to work with object methods.

// we'll make worker.slow caching
let worker = {
  someMethod() {
    return 1;

  slow(x) {
    // scary CPU-heavy task here
    alert("Called with " + x);
    return x * this.someMethod(); // (*)

// same code as before
function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    let result = func(x); // (**)
    cache.set(x, result);
    return result;

alert( worker.slow(1) ); // the original method works

worker.slow = cachingDecorator(worker.slow); // now make it caching

alert( worker.slow(2) );, …args)

Syntax, arg1, arg2, ...) Example

function sayHi() {

let user = { name: "John" };
let admin = { name: "Admin" };

// use call to pass different objects as "this" user ); // John admin ); // Admin

cachingDecorator with call

function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    let result =,x); // (**)
    cache.set(x, result);
    return result;

func.apply(context, args)

Syntax: func.apply(context, args)

  • It runs the func setting this=context and using an array-like object args as the list of arguments.

  • Passing all arguments along with the context to another function is called call forwarding.

function wrapper(func){
  return func.apply(this,arguments)

Borrowing a method

function hash() {
  alert( [] ); // 1,2

hash(1, 2);
  • We take (borrow) a join method from a regular array ([].join) and use [] to run it in the context of arguments.


  • Debounce is like a secretary that accepts “phone calls”, and waits until there’s ms milliseconds of being quiet. And only then it transfers the latest call information to “the boss” (calls the actual f).



  1. Set a timer in the outer context and clear it out whenever the call is made to fn.
  2. call the fn.
function debounce(fn, delay){
      	let timer 

	return function(){

      timer = setTimeout(() => {
          return fn.apply(this, arguments) 
      }, delay)


throttle(f, ms)

  • When it’s called multiple times, it passes the call to f at maximum once per ms milliseconds.

  • The difference with debounce is that it’s completely different decorator:

    • debounce runs the function once after the “cooldown” period. Good for processing the final result.
    • throttle runs it not more often than given ms time. Good for regular updates that shouldn’t be very often.


  1. Store 3 states in outer function
    1. isThrottled flag
    2. savedThis
    3. savedArgs
  2. When the wrapper is called initially, call the fn with current arguments and this. Set the flag isThrottled flag as true. setTimeout is send a callback to be triggered after ms milliseconds.
  3. If the wrapper is called again within ms millisecond, the this and arguments are saved in savedThis and savedArgs respectively and the wrapper returns.
  4. The callback sent to setTimeout is now triggered. If there are any savedArgs, call the wrapper and set the flag isthrottled to false.
function throttle(fn, ms){
	let isThrottled = false,
    savedThis, savedArgs
	return function wrapper(){
       		savedThis= this
          	savedArgs = arguments
      	fn.apply(this, arguments)
      	setTimeout(() => {
          isThrottled = false
           wrapper.apply(savedThis, savedArgs)
           savedThis = savedArgs = null

function binding

Losing this

let user = {
 name: 'shivam',
 sayHello: function(){
   console.log(`Hello ${`);

setTimeout(user.sayHello, 0) // Hello undefined

Solution 1 : wrapper

let user = {
 name: 'shivam',
 sayHello: function(){
   console.log(`Hello ${`);

setTimeout(() => user.sayHello(), 0) // Hello undefined

But there is a pitfall here. user can modify before the setTimeout triggers.

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);

setTimeout(() => user.sayHi(), 1000);

// ...the value of user changes within 1 second
user = {
  sayHi() { alert("Another user in setTimeout!"); }

// Another user in setTimeout!

Solution 2 : bind

let boundFunc = func.bind(context);

The result of func.bind(context) is a special function-like “exotic object”, that is callable as function and transparently passes the call to func setting this=context.

Example 1

const user = {
  name: 'shivam'

function sayHello(phrase){
  console.log(`${phrase} ${}`)

const wrapper = sayHello.bind(user)

console.log(wrapper.sayHello('Hello'))// Hello shivam

Example 2

let user = {
  name: 'shivam',
  sayHello: function(){
    console.log(`Hello ${}`)

let sayHi = user.sayHello.bind(user)

console.log(sayHi())// Hello shivam

setTimeout(sayHi,100) // Hello shivam

// even if the value of user changes within 1 second
// sayHi uses the pre-bound value which is reference to the old user object
user = {
  sayHi() { alert("Another user in setTimeout!"); }
  • In the line (*) we take the method user.sayHi and bind it to user. The sayHi is a “bound” function, that can be called alone or passed to setTimeout – doesn’t matter, the context will be right.

Partial function

We can bind not only this, but also arguments. That’s rarely done, but sometimes can be handy.

Syntax let bound = func.bind(context, [arg1], [arg2], ...);

Here the mul function is created with another context

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
alert( double(5) ); // = mul(2, 5) = 10


function partial(fn, ...args){
  return function(...argvar){
  	return,...args, ...argvar)


  1. Second bind

Attaching events to a DOM element

There are 3 ways in which event(s) can be attached to a DOM element.

inline HTML

<button onclick="bgChange()">Press me</button>

Not a good idea to mix up JS and HTML

DOM Query selector

	const buttons = document.querySelectorAll('button');
	for (let i = 0; i < buttons.length; i++) {
		buttons[i].onclick = bgChange;


myElement.addEventListener('click', functionA);
  • Can attach multiple event listenders
  • Remove event listeners


In web page there is a main thread. Rendering, JS engine and other things are handled by main thread which gives a deterministic order.


  • Functional programming is a programming paradigm where programs are constructed by applying and composing functions. It is a declarative programming paradigm (a style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow.) in which function definitions are trees of expressions that map values to other values, rather than a sequence of imperative statements which update the running state of the program.
  • Functional programming means using functions to the best effect for creating clean and maintainable software.


Pure functions

  • Pure functions (or expressions) have no side effects (memory or I/O).
  • If the result of a pure expression is not used, it can be removed without affecting other expressions.
  • If a pure function is called with arguments that cause no side-effects, the result is constant with respect to that argument list (sometimes called referential transparency or idempotence), i.e., calling the pure function again with the same arguments returns the same result. (This can enable caching optimizations such as memoization.)
  • If there is no data dependency between two pure expressions, their order can be reversed, or they can be performed in parallel and they cannot interfere with one another (in other terms, the evaluation of any pure expression is thread-safe).
  • If the entire language does not allow side-effects, then any evaluation strategy can be used; this gives the compiler freedom to reorder or combine the evaluation of expressions in a program (for example, using deforestation).


  • Another tenet of functional programming philosophy is not to modify data outside the function. In practice, this means to avoid modifying the input arguments to a function. Instead, the return value of the function should reflect the work done. This is a way of avoiding side effects. It makes it easier to reason about the effects of the function as it operates within the larger system.

First class functions

  • A first class function is a function that is treated as a “thing in itself,” capable of standing alone and being treated independently. Functional programming seeks to take advantage of language support in using functions as variables, arguments, and return values to create elegant code.

Higher-order functions

  • A function that accepts a function as an argument, or returns a function, is known as a higher-order function — a function that operates upon a function.

Curried functions


  1. Infoworld
  2. Wiki


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. Or, as we’ll see soon, to deal with certain data structures.

The execution context and stack

  • The information about the process of execution of a running function is stored in its execution context.
  • The execution context is an internal data structure that contains details about the execution of a function: where the control flow is now, the current variables, the value of this (we don’t use it here) and few other internal details.
  • One function call has exactly one execution context associated with it.

When a function makes a nested call, the following happens:

  1. The current function is paused.
  2. The execution context associated with it is remembered in a special data structure called execution context stack.
  3. The nested call executes.
  4. After it ends, the old execution context is retrieved from the stack, and the outer function is resumed from where it stopped.

Rest and spread syntax

function sumAll(...args) { // args is the name for the array
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;

The “arguments” variable

In old times, rest parameters did not exist in the language, and using arguments was the only way to get all arguments of the function. And it still works, we can find it in the old code.

But the downside is that although arguments is both array-like and iterable, it’s not an array. It does not support array methods, so we can’t call for example.

  • Arrow functions do not have "arguments"

If we access the arguments object from an arrow function, it takes them from the outer “normal” function.

Here’s an example:

function f() {
  let showArg = () => alert(arguments[0]);

f(1); // 1

Spread syntax

let arr = [3, 5, 1];

alert( Math.max(arr) ); // NaN

alert( Math.max(...arr) ); // 5

There’s a subtle difference between Array.from(obj) and [...obj]:

  1. Array.from operates on both array-likes and iterables.
  2. The spread syntax works only with iterables.


Code blocks

  // do some job with local variables that should not be seen outside

  let message = "Hello"; // only visible in this block

  alert(message); // Hello

alert(message); // Error: message is not defined

Nested functions

A function is called “nested” when it is created inside another function.


function sayHiBye(firstName, lastName) {

  // helper nested function to use below
  function getFullName() {
    return firstName + " " + lastName;

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );


Lexical Environment

In JavaScript, every running

  1. function,
  2. code block {...}, and
  3. the script as a whole have an internal (hidden) associated object known as the Lexical Environment.

The Lexical Environment object consists of two parts:

  1. Environment Record – an object that stores all local variables as its properties (and some other information like the value of this).
  2. A reference to the outer lexical environment, the one associated with the outer code.

Step 1. Variables

A “variable” is just a property of the special internal object, Environment Record. “To get or change a variable” means “to get or change a property of that object”.



Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution:

  1. When the script starts, the Lexical Environment is pre-populated with all declared variables.
  2. Initially, they are in the “Uninitialized” state. That’s a special internal state, it means that the engine knows about the variable, but it cannot be referenced until it has been declared with let. It’s almost the same as if the variable didn’t exist.
  3. Then let phrase definition appears. There’s no assignment yet, so its value is undefined. We can use the variable from this point forward.
  4. phrase is assigned a value.
  5. phrase changes the value.

Step 2. Function Declarations

A function is also a value, like a variable.

The difference is that a Function Declaration is instantly fully initialized. When a Lexical Environment is created, a Function Declaration immediately becomes a ready-to-use function (unlike let, that is unusable till the declaration).

Naturally, this behavior only applies to Function Declarations, not Function Expressions where we assign a function to a variable, such as

let say = function(name)....`

Step 3. Inner and outer Lexical Environment


During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global):

  1. The inner Lexical Environment corresponds to the current execution of say. It has a single property: name, the function argument. We called say("John"), so the value of the name is "John".
  2. The outer Lexical Environment is the global Lexical Environment. It has the phrase variable and the function itself.

When the code wants to access a variable – the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.

In this example the search proceeds as follows:

For the name variable, the alert inside say finds it immediately in the inner Lexical Environment. When it wants to access phrase, then there is no phrase locally, so it follows the reference to the outer Lexical Environment and finds it there.


Step 4. Returning a function

function makeCounter() {
  let count = 0;

  return function() {
    return count++;

let counter = makeCounter();


What’s different is that, during the execution of makeCounter(), a tiny nested function is created of only one line: return count++. We don’t run it yet, only create.

All functions remember the Lexical Environment in which they were made. Technically, there’s no magic here: all functions have the hidden property named [[Environment]], that keeps the reference to the Lexical Environment where the function was created:

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.


So, counter.[[Environment]] has the reference to {count: 0} Lexical Environment. That’s how the function remembers where it was created, no matter where it’s called. The [[Environment]] reference is set once and forever at function creation time.

Later, when counter() is called, a new Lexical Environment is created for the call, and its outer Lexical Environment reference is taken from counter.[[Environment]]:


Must solve question

  1. Block level scope function
  2. let scope pitfall
  3. Make army array function

The old var

“var” has no block scope

Example 1:

if (true) {
  var test = true; // use "var" instead of "let"

alert(test); // true, the variable lives after if

let will throw an error

if (true) {
  let test = true; // use "let"

alert(test); // ReferenceError: test is not defined
for (var i = 0; i < 10; i++) {
  var one = 1;
  // ...

alert(i);   // 10, "i" is visible after loop, it's a global variable
alert(one); // 1, "one" is visible after loop, it's a global variable

“var” tolerates redeclarations

...but let doesn't

var user = "Pete";

var user = "John"; // this "var" does nothing (already declared)
// doesn't trigger an error

alert(user); // John

“var” variables can be declared below their use (HOISTING)

function sayHi() {
  phrase = "Hello";


  var phrase;

Declarations are hoisted, but assignments are not.


In the past, as there was only var, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called “immediately-invoked function expressions” (abbreviated as IIFE).

(function() {

  var message = "Hello";

  alert(message); // Hello


So, the parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it’s a Function Expression: it needs no name and can be called immediately.

Function object & Named function expression(NFE)

As we already know, a function in JavaScript is a value.

Every value in JavaScript has a type. What type is a function?

In JavaScript, functions are objects.

The “name” property

function sayHi() {

alert(; // sayHi
let sayHi = function() {

let user = {

  sayHi() {
    // ...

  sayBye: function() {
    // ...


alert(; // sayHi
alert(; // sayBye

The “length” property

  • returns the number of function parameters
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2

Named Function Expression

Named Function Expression, or NFE, is a term for Function Expressions that have a name.

let sayHi = function(who) {
  alert(`Hello, ${who}`);
let sayHi = function func(who) {
  alert(`Hello, ${who}`);
let sayHi = function func(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    func("Guest"); // Now all fine

let welcome = sayHi;
sayHi = null;

welcome(); // Hello, Guest (nested call works)

Use of referencing the function itself

  1. It allows the function to reference itself internally.
  2. It is not visible outside of the function.


  1. sum(n1)(n2).....

Property flags and descriptors

Property Flag

  1. value
  2. writable - if true, the value can be changed, otherwise it’s read-only.
  3. enumerable - if true, then listed in loops, otherwise not listed.
  4. configurable - if true, the property can be deleted and these attributes can be modified, otherwise not.

All the property flags are true by default

Getting property flags

let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);

Setting property flag

Object.defineProperty(obj, property, descriptor)

If the property exists, defineProperty updates the flag, otherwise it creates the property with given flags and if flag is not provided, its assumed false.

let x = {}

Object.defineProperty(x, 'a', {
	value: 12

console.log(Object.getOwnPropertyDescriptor(x, 'a'))
	value: 12,
    writable: false,
    configurable: false,
    enumerable: false


  1. Option 1
let user = {
  name: 'john'

Object.defineProperty(user, 'name', {
 writable: false 

user['name'] = 'paul'
// Error in strict mode only, no error in non strict mode and value wouldn't update.
  1. Option 2
let user = {

Object.defineProperty(user, 'name',{
 value: 'shivam',
  enumerable: true,
  configurable: true


let user = {
 name: 'shivam',
  toString: function(){

Object.defineProperty(user, 'toString', {
	enumerable: false
for (let key in user) alert(key); // name , only
  1. Non-enumerable properties are also excluded from Object.keys:
  2. Non-enumerable properties are also excluded from for ... in


The non-configurable flag (configurable:false) is sometimes preset for built-in objects and properties.
A non-configurable property can’t be deleted, its attributes (not the value) can’t be modified.

For instance, Math.PI is non-writable, non-enumerable and non-configurable:

console.log(Object.getPropertyOwnDescriptor(Math, 'PI'))
  "value": 3.141592653589793,
  "writable": false,
  "enumerable": false,
  "configurable": false
Math.PI = 3; // Error, because it has writable: false

// delete Math.PI won't work either


  • Making a property non-configurable is a one-way road. We cannot change it back with defineProperty.
  • Please note: configurable: false prevents changes of property flags and its deletion, while allowing to change its value.
  • We can change writable: true to false for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though.


Sytax Object.defineProperties(obj, { prop1: descriptor })


  • Gets all property descriptors at once.
  • Can be used to clone object with descriptors.
let clone = Object.defineproperties({}, Object.getOwnPropertyDescriptors(objToClone))

Normally when we clone an object, we use an assignment to copy properties, like this:

for (let key in user) {
  clone[key] = user[key]

…But that does not copy flags. So if we want a “better” clone then Object.defineProperties is preferred.

Another difference is that ignores symbolic and non-enumerable properties, but Object.getOwnPropertyDescriptors returns all property descriptors including symbolic and non-enumerable ones.

Sealing an object globally

  1. Object.preventExtensions(obj) - Forbids the addition of new properties to the object.
  2. Object.seal(obj) - Forbids adding/removing of properties. Sets configurable: false for all existing properties.
  3. Object.freeze(obj) - Forbids adding/removing/changing of properties. Sets configurable: false, writable: false for all existing properties.
  4. Object.isExtensible(obj) - Returns false if adding properties is forbidden, otherwise true.
  5. Object.isSealed(obj) - Returns true if adding/removing properties is forbidden, and all existing properties have configurable: false.
  6. Object.isFrozen(obj) - Returns true if adding/removing/changing properties is forbidden, and all current properties are configurable: false, writable: false

Property getters and setters

` obj = { get propName(){ //code to execute on obj.propname }

set propName(){
// code to execute when setting obj.propname = value

} `


If we have an obj with property firstName and lastName, we can create a getter for fullName.

let user = {
  firstname : "john",
  lastname: "mayers",
  get fullname(){
   return `${this.firstname} ${this.lastname}` 

Assigning value to a getter will throw an error

let user = {
  get fullName() {
    return `...`;

user.fullName = "Test"; // Error (property has only a getter)

Instead create a setter for such property

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${} ${this.surname}`;

  set fullName(value) {
    [, this.surname] = value.split(" ");

Accessor descriptors

Descriptors for accessor (get and set) properties are different from those for data properties.

For accessor properties, there is no value or writable, but instead there are get and set functions.

That is, an accessor descriptor may have:

  • get – a function without arguments, that works when a property is read,
  • set – a function with one argument, that is called when the property is set,
  • enumerable – same as for data properties,
  • configurable – same as for data properties.


Object are just a place holder in memory, represented in the form of key:value pair.

let user = new Object(); // "object constructor" syntax
let user = {};  // "object literal" syntax


let user = {     // an object
  name: "John",  // by key "name" store value "John"
  age: 30        // by key "age" store value 30


user['branch'] = 'cs'


delete user.branch

Computed property

let bag = {
  [fruit]: 5,

Property value shorthand

let bag = {

Invalid property names

  • Valid : keywords can be property names
let bag = {
  return: 0,
  for: 1,
  let: 3
  • Invalid proto can only be an object
let bag.__proto__ = true 

Check for existing property

key in obj

Iterating across all the properties

for (let key in obj){

Ordering of keys

for (let key in obj)
  • numeric keys are ordered according to their values
  • rest are ordered in the order of its creation


let schedule = {};

function isEmpty(obj){
  for (let key in obj){
    return true
  return false
alert( isEmpty(schedule) ); // true

Object referencing and copying

Variable store a reference to the object not the actual object. Primitives store actual values.

Copying the object copies the reference of the object.

let user = {
  name: "John",
  age: 30        

const newUser = user // copies by reference
let a = {};
let b = a; // copy the reference

alert( a == b ); // true, both variables reference the same object
alert( a === b ); // true
let a = {};
let b = {}; // two independent objects

alert( a == b ); 


  1. Inbuild method Object.assign(destination,[src1, src1, ...])

  2. Excercise....

Garbage Collection

Automatically. Reachability

let user = {
  name: "John"
user = undefined;

The basic garbage collection algorithm is called “mark-and-sweep”.


let user = {
  name: "John",
  age: 30,

  sayHi() {
    // "this" is the "current object"


user.sayHi(); // John
let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert( ); // unreliable


“this” is not bound

Constructor, "new" operator

function User(name) {
  // this = {};  (implicitly)

  // add properties to this = name;
  this.isAdmin = false;

  // return this;  (implicitly)

When a function is executed with new, it does the following steps:

  1. A new empty object is created and assigned to this.
  2. The function body executes. Usually it modifies this, adds new properties to it.
  3. The value of this is returned.
  4. Any function (except arrow functions, as they don’t have this) can be used as a constructor.

  • That can be used inside the function to know whether it was called with new, “in constructor mode”, or without it, “in regular mode”.
  • It is undefined for regular calls and equals the function if called with new.
  • We can also make both new and regular calls to do the same, like this:
function User(name) {
  if (! { // if you run me without new
    return new User(name); // ...I will add new for you
  } = name;

let john = User("John"); // redirects call to new User
alert(; // John

Return from constructors

  • If return is called with an object, then the object is returned instead of this.
  • If return is called with a primitive, it’s ignored.

Optional chaining

let user = null;

alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
  • the ?. immediately stops (“short-circuits”) the evaluation if the left part doesn’t exist.

  • The optional chaining ?. is not an operator, but a special syntax construct, that also works with functions and square brackets.

  • For Eg


Symbol Type

By specification, only two primitive types may serve as object property keys:

  1. String type
  2. Symbol type
obj[true] === obj["true"]
obj[1] === obj["1"]

A “symbol” represents a unique identifier.

let id = Symbol() //or
let id = Symbol('id')

Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn’t affect anything.

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

Symbols - Hidden properties

Symbols allow us to create “hidden” properties of an object, that no other part of code can accidentally access or overwrite.

  • Symbols are skipped by for…in
  • Object.keys(user) also ignores them.
  • In contrast, Object.assign copies both string and symbol properties:


  1. Number
  2. Boolean
  3. Null
  4. undefined
  5. Big INT
  6. String
  7. Symbol

Don't use primitives constructor methods

let zero = new Number(0);

if (zero) { // zero is true, because it's an object
  alert( "zero is truthy!?!" );

May use primitive conversion

let num = Number("123"); // convert a string to number


let billion = 1000000000;
let billion = 1_000_000_000;
let billion = 1e9;  // 1 billion, literally: 1 and 9 zeroes
1e3 === 1 * 1000; // e3 means *1000
1.23e6 === 1.23 * 1000000; // e6 means *1000000
alert( 7.3e9 );  // 7.3 billions (same as 7300000000 or 7_300_000_000)


let mсs = 0.000001;
let mcs = 1e-6; // six zeroes to the left from 1
1.23e-6 === 1.23 / 1000000; // 0.00000123


let x = 0xFF 
let x = 0xFF


let x = 0o12;


let x= 0b01010


The method num.toString(base) returns a string representation of num in the numeral system with the given base.


x = 123..toString()
num.toFixed(2) // 2 decimal places

the funny thing

// Hello! I'm a self-increasing number!
alert( 9999999999999999 ); // shows 10000000000000000

Two zeroes

Another funny consequence of the internal representation of numbers is the existence of two zeroes: 0 and -0.

That’s because a sign is represented by a single bit, so it can be set or not set for any number including a zero.

NaN and Infinite

  • isNaN(value) converts its argument to a number and then tests it for being NaN:

  • isFinite(value) converts its argument to a number and returns true if it’s a regular number, not NaN/Infinity/-Infinity

Compare with, NaN) === true, -0) === false

parseInt(number, base) and parseFloat()

  • Conversion to numbers
    1. Number()
    2. +value but these methods don't work with values such as '100px', '100rem',. For such cases parseInt and parseFloat is used as:
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5

alert( parseInt('12.3') ); // 12, only the integer part is returned
alert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading

Other Math functions

  1. Math.random()
  2. Math.max(a, b, c...) / Math.min(a, b, c...)
  3. Math.pow(n, power)

String Type

let single = 'single-quoted';
let double = "double-quoted";

let backticks = `backticks`;

Length of string


Accessing characters

let str = `Hello`;

// the first character
alert( str[0] ); // H
alert( str.charAt(0) );

for (let char of "Hello") {
  alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc)

Strings are immutable

Strings can’t be changed in JavaScript. It is impossible to change a character.

This doesn't work

let str = 'Hi';

str[0] = 'h'; // error
alert( str[0] ); // doesn't work

The usual workaround is to create a whole new string and assign it to str instead of the old one.

let str = 'Hi';

str = 'h' + str[1]; // replace the string

alert( str ); // hi


Changing the case

'Interface'[0].toLowerCase()// Return i

Searching for a substring

  1. indexOf =>
str.indexOf(substr, pos)

It looks for the substr in str, starting from the given position pos, and returns the position where the match was found or -1 if nothing can be found. Returns position if found else -1.

  1. lastIndexOf
str.lastIndexOf(substr, position)

str.lastIndexOf(substr, position) that searches from the end of a string to its beginning.

  1. includes

str.includes(substr, pos) returns true/false depending on whether str contains substr within.

alert( "Widget with id".includes("Widget") ); // true
  1. startsWith and endsWith
alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"

substring of a string

  1. str.slice(start [, end])

Returns the part of the string from start to (but not including) end.

let str = "stringify";
alert( str.slice(0, 5) ); // 'strin', the substring from 0 to 5 (not including 5)
alert( str.slice(0, 1) ); // 's', from 0 to 1, but not including 1, so only character at 0
alert( str.slice(2) ); // 'ringify', from the 2nd position till the end
alert( str.slice(-4, -1) ); // 'gif'
  1. str.substring(start [, end]) This is almost the same as slice, but it allows start to be greater than end.
let str = "stringify";
// these are same for substring
alert( str.substring(2, 6) ); // "ring"
alert( str.substring(6, 2) ); // "ring"
  1. str.substr(start [, length]) Returns the part of the string from start, with the given length.
let str = "stringify";
alert( str.substr(2, 4) );
let str = "stringify";
alert( str.substr(-4, 2) ); 

Comparing strings

  1. str.codePointAt(pos) Returns the code for the character at position pos:
// different case letters have different codes
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
  1. String.fromCodePoint(code)
alert( String.fromCodePoint(90) ); // Z


  1. str.trim()
  2. str.repeat(n)


let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)


let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
All arguments have the same meaning as setTimeout. But unlike setTimeout it runs the function not only once, but regularly after the given interval of time.

setTimeout as setInterval

let tID = setTimeout(function tick(){
  // some code

Advantage of using this approach Nested setTimeout allows to set the delay between the executions more precisely than setInterval.

If requests are failing then the interval time can be increased

let delay = 5000;

let timerId = setTimeout(function request() {
  ...send request...

  if (request failed due to server overload) {
    // increase the interval to the next run
    delay *= 2;

  timerId = setTimeout(request, delay);

}, delay);

Advantage of using setTimeout instead of setInterval for recurring tasks

  1. Code 1
let i = 1;
setInterval(function() {
}, 100);
  1. Code 2
let i = 1
setTimeout(function run(){
  setTimeout(run, 100)


The real delay between func calls for setInterval is less than in the code!

Image The nested setTimeout guarantees the fixed delay (here 100ms).

Zero delay setTimeout

There’s a special use case: setTimeout(func, 0), or just setTimeout(func).
This schedules the execution of func as soon as possible. But the scheduler will invoke it only after the currently executing script is complete.
So the function is scheduled to run “right after” the current script.

For instance, this outputs “Hello”, then immediately “World”:

setTimeout(() => {alert("World")})

Server side rendering

  • The HTML, Css, JS and content loads initially.
  • Initial load time is low but the page isn't responsive(interactive) instantly since the browser is blocked executing JS.
  • Initial blank screen flicker while loading.
  • But more server requests
  • Not ideal for lot of interactive actions.
Pros Cons
Search engines can crawl for better SEOs Frequent server request
Initial load is faster An overall slow page rendering
Great for static sites Full page reload
Non rich site interactions


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment