Abílio Azevedo.

JavaScript

Cover Image for JavaScript
Abílio Azevedo
Abílio Azevedo

History of Javascript

Tim Berners Lee in 1989 created the World Wide Web - Server Communication -> Browser Nexus.

Timeline of Web Browsers

Timeline of web browsers

1994 - Marc Andressen, creator of NCSA Mosaic launched the Netscape browser;

1995 - Brendan Eich was recruited to write a programming language for the Netscape 2.0 browser. So he implemented the JavaScript language (initially called Mocha and then Livescript, but due to influences from Java was called Javascript) in 10 days in May 1995, using Java, Scheme, Self as a base and with some influences from Perl;

netscape-navigator-2-01-01

1996 - Microsoft with Internet Explorer copied Javascript and launched JScript;

1997 - Netscape, afraid of Jscript, standardized the JavaScript language with ECMA International, changing the name to ECMAScript;

With the standardization, a committee called TC39 was formed to work on the ECMA-262 specification

ES1 - 1997 - 110 pages - It was created just for the browser

ES2 - 1998 - 117 pages - Compliance with ISO/IEC 16262 regulations

ES3 - 1999 - 188 pages - Exception Handling (throw/try/catch), Regular Expression, switch, do-while

In 2005 with AJAX (Asynchronous JavaScript And XML) and the use of JS by servers, the language got a bad reputation

Microsoft and Yahoo were working on V3.1 and Adobe, Mozilla, Opera and Google on V4. The projects became very distant and were rejected by TC39.

ES5 - 2009 - 252 pages - JSON, strip mode, reserved words as property keys, multiline string, Object API, Array.prototype.*

ES6 - ECMA2015 - 566 pages - Class, Arrow Function, Proxy, Reflect, Map, Set, Destructuring, Rest Parameter, Default Value, Template Literal, Spread Operator, Generators, Promises, Modules

ES7 - ECMA2016 - 586 pages - Array.prototype.includes, Exponentiation operator...

ES8 - ECMA2017 - 885 pages - Async/Await, Object.values, Object.entries, String.prototype.padStart, String.prototype.padEnd, Trailling commas in parameters list, objects and arrays.

ECMA Script Versions

ECMA Script compability

ECMA Script Compatibility

Javascript Concepts

Variables

  • Declaration: The variable name is registered in the execution context, also known as scope, of the function

  • Initialization: The variable is initialized with the undefined value

  • Assignment: A value is assigned to the variable

VAR: When using var, the variable is declared and initialized in the function scope, not respecting block and allowing redeclaration and reassignment

LET: When using let, the variable is declared in the function scope but is only initialized later, respecting block and allowing reassignment but not redeclaration

CONST: When using const, the variable is declared in the function scope but is only initialized later, respecting block and not allowing reassignment or redeclaration.

Be careful when declaring a variable without VAR, LET, CONST because it goes into the global scope:

(function () {
    pi = 3.141592;  
})();

console.log(pi); 

Variable Identifiers: A valid identifier must start with [a-zA-Z_$] followed by [a-zA-Z0-9_$]:

let name123;
let Name123; 
let $name123;
let _name123;

Naming Conventions:

  • Camel Case: myVariable, myMethod, myClass
  • Pascal Case: MyVariable, MyMethod, MyClass
  • Kebab Case: my-variable, my-method, my-class
  • Snake Case: my_variable, my_method, my_class

Data Types:
undefined, null, boolean, string, symbol, number and object

Objects

They are collections of properties (attributes and methods) that consist of key-value pairs

They are created using literal notation or constructor functions like new Object()

They are dynamic and properties can be changed at any time

const person = {
  name: "Maria",
  age: 20
}

Inheritance

The main objective of inheritance is to allow code reuse by sharing properties between objects, avoiding duplication.

Inheritance is based on object prototypes and not classes. Through the __proto__ property that references the object's prototype, it is possible to indicate the object to be inherited:

const functionalLanguage = {
  paradigm: "Functional"
};

const scheme = {
  name: "Scheme",
  year: 1975,
  __proto__: functionalLanguage
};

const javascript = {
  name: "JavaScript",
  year: 1995,
  __proto__: functionalLanguage
};

If we print the scheme object, we won't see the paradigm key because the key is in the prototype, but if we print scheme.paradigm, it will return "Functional" because it looks in the object and in the prototypes.

The hasOwnProperty function checks the object's properties without considering the prototype's properties.

If the same property exists in the object and its prototype, the property of the object itself is returned, overshadowing the property of the prototype.

We have some functions from the Object API to block changes in the object:

Object API

JSON

JSON, or JavaScript Object Notation, is a data interchange format derived from the JavaScript language that was discovered by Douglas Crockford and standardized by ECMA-404.

The JSON.stringify method converts a given data type to JSON. The JSON.parse method converts a JSON to a given data type. They can be used to compare objects and to copy objects.

Symbols

The Symbol type is primitive, unique and immutable, acting as a unique key in an object.

Symbol("a") === Symbol("a") // false

Functions

In JavaScript, everything is based on functions. Instantiation with new or (). An object is a set of keys/values. Functions are first-class, i.e. they can be assigned to a variable, passed by parameter or returned from another function.

In JavaScript, there are two main ways to define a function: function declaration and function expression. The key difference between them lies in how they are defined and how they are hoisted within the code.

Function Declaration:

A function declaration is a statement that defines a named function. It starts with the function keyword followed by the function name, a parameter list enclosed in parentheses (), and the function body enclosed in curly braces {}.

function functionName(param1, param2) {
  // function body
}

Function declarations are hoisted to the top of their scope (either global or local) during the compilation phase. This means that you can call a function declared this way before it is defined in the code.

Function Expression:

A function expression is a way to define a function as part of a larger expression. It starts with the function keyword followed by an optional function name, a parameter list enclosed in parentheses (), and the function body enclosed in curly braces {}. The function expression is often assigned to a variable or passed as an argument to another function.

const functionName = function(param1, param2) {
  // function body
};

// or

const anotherFunction = function namedFunction(param1, param2) {
  // function body
};

Function expressions are not hoisted like function declarations. They are treated as any other expression and evaluated when the execution reaches that line of code. Therefore, you cannot call a function expression before it is defined in the code.

Here's an example that illustrates the difference:

// Function Declaration
sayHello(); // Outputs "Hello!"
function sayHello() {
  console.log("Hello!");
}

// Function Expression
sayHi(); // Throws an error: TypeError: sayHi is not a function
const sayHi = function() {
  console.log("Hi!");
};

In the example above, the function declaration sayHello() can be called before it is defined because it is hoisted to the top of its scope. However, the function expression sayHi cannot be called before it is defined because it is not hoisted.

In general, function declarations are preferred for defining reusable functions, while function expressions are often used for creating functions as needed, such as when passing a function as an argument to another function or when defining a function within another function (closures).

Anonymous functions

They are functions that are dynamically declared at runtime. They are called anonymous functions because they do not receive a name like traditional functions.

const myfn = function(param1, param2) {
  return param1 + param2;
};

myfn(10, 20); // returns 30

Generator Functions:

Generators make it possible to pause the execution of a given function, allowing the use of the event loop cooperatively. They return a sequence of results.

Generator Function

A generator function is marked by the signature with an asterisk (*) and can contain one or more calls using the yield (like it's the "return value" of a function) as in the example below:

function* myFn() {
  yield 'result01';
  yield 'result02';
}

Generators use the next method to iterate over the available values during the execution of the function. When encountering a yield, the execution of the function is paused until the next method is invoked again. The return of the next method is an object containing value and done, following the iteration protocol. Through yield, it's possible to return values similarly to return.

The return method ends the generator and can return a specific value. The throw method throws an exception within the generator, interrupting the execution flow if the exception has not been handled properly.

Since generators implement the iteration protocol, it's possible to use them with Symbol.iterator in a simple way.

Additionally, it's possible to use generators to synchronize asynchronous calls similarly to async/await.

function sum(a, b) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(a + b);
    }, 1000);
  });
}
function async(fn) {
  const gen = fn();
  asyncR(gen);
}
function asyncR(gen, result) {
  const obj = gen.next(result);
  if (obj.done) return;
  obj.value.then(function(result) {
    asyncR(gen, result);
  });
}
async(function* () {
  const a = yield sum(2, 2);
  const b = yield sum(4, 4);
  const result = yield sum(a, b);
  console.log(result);
});

Arguments

Through the implicit variable arguments it is possible to access the parameters of the invoked function:

const sum = function () {
  let total = 0;
  for (let argument in arguments) {
    total += arguments[argument];return total;
};
console.log(sum (1, 2, 3, 4, 5, 6, 7, 8, 91);

Date

The ISO 8601 standard establishes a standard for representing dates as string formats. Below I will present some formats according to the ISO 8601 standard and what happens when we use them to instantiate the Date class object in JavaScript.

console.log(new Date('2021-12-15'));
// Output: Date Tue Dec 14 2021 21:00:00 GMT-0300 (Brasilia Standard Time)  

When we do not specify the time, minute or, optionally, the second, the ISO standard establishes that the time zone of the date is considered UTC (Coordinated Universal Time)

console.log(new Date(2021-12-15T00:00Z'));
// Output: Date Tue Dec 14 2021 21:00:00 GMT-0300 (Brasilia Standard Time)

The output is the same as the previous code. The difference is that we specify the hour and minute and, by putting the uppercase Z character at the end, we are explicitly stating that the time zone is UTC.

console.log(new Date('2021-12-15T00:00')); 
// Output: Date Wed Dec 15 2021 00:00:00 GMT-0300 (Brasilia Standard Time)

When we specify the hour, minute and, optionally, the second, the standard defines that it is assumed that if the date and time refers to the local time zone.

Array

Mutator Methods

The mutator methods, when invoked, modify the array • push: Adds an element at the end • pop: Removes an element from the end • unshift: Adds an element at the beginning • shift: Removes an element from the beginning • splice: Removes, replaces, or adds one or more elements at a given position • sort: Sorts the elements of an array in-place according to the sorting function

array.sort((a, b) => a - b); // ascending
array.sort((a, b) => b - a); // descending  

reverse: Reverses the order of the elements • fill: Fills the elements according to the start and end position

Iterator Methods

The iterator methods, when invoked, iterate over the array

forEach: Executes the function passed as a parameter for each element,

array.forEach(el => {
  // do something with el  
});

filter: Returns a new array containing only the elements that returned true in the function passed as a parameter

const filtered = array.filter(el => el > 2); 

find: Returns the first element that returned true in the function passed as a parameter • some: Returns true if one or more elements returned true in the function passed as a parameter • every: Returns true if all elements returned true in the function passed as a parameter • map: Maps values to a new array from an existing array based on the return value of the function passed as a parameter

const doubled = array.map(el => el * 2);
  • reduce: Reduces an array to a single value by accumulating the elements through a function:
const sum = array.reduce((accumulator, el) => accumulator + el, 0);

Accessor Methods

The accessor methods, when invoked, return specific information about the array

  • indexOf: Returns the position of the first found element;
  • lastIndexOf: Returns the position of the last found element;
  • includes: Returns true if the element exists;
  • concat: Returns a new array resulting from the concatenation of one or more arrays;
  • slice: Returns parts of a given array according to the start and end position;
  • join: Converts the array to a String, joining the elements based on a separator;

Array methods cheatsheet

Map

A Map is an object that stores a set of keys and values that can be of any data type.

const timeUnits = new Map([["second", 1], ["minute", 60], ["hour", 3600]]);
console.log(Array.from(timeUnits)); // [['second', 1],['minute', 60],['hour', 3600]]
  • size: Returns the number of elements;
  • set: Adds a key-value pair;
  • forEach: Iterates over the map;
  • has: Returns true if the key exists;
  • get: Returns the value of a given key;
  • delete: Removes a key-value pair;
  • clear: Removes all elements;

What's the difference between Map and Object?

The map accepts keys of various types, while the object only accepts String or Symbol, other types will be converted to string:

Object

const obj = {};
obj[0] = "Number";
obj[10] = "Number";
obj["10"] = "String";
obj[true] = "Boolean";
obj["true"] = "String";
console.log(obj[10]);  // "String";
console.log(obj["10"]); // "String";
console.log(obj[true]);  // "String";
console.log(obj["true"]); // "String";

Map

const map = new Map();
map.set(10, "Number"); 
map.set("10", "String");
map.set(true, "Boolean");
map.set("true", "String");
console.log(map.get(10)); // Number
console.log(map.get("10")); // String 
console.log(map.get(true)); // Boolean
console.log(map.get("true")); // String

Weak Map

WeakMap is an object, similar to Map, that allows only Object type keys and maintains weak references, being volatile and non-iterable

  • set: Adds a key-value pair;
  • has: Returns true if the key exists;
  • get: Returns the value of a given key;
  • delete: Removes a key-value pair;
const areas = new WeakMap();
const rectangle1 = {
  x: 10, y: 2
};

const rectangle2 = {
  x: 5, y: 3
};

function calculateArea(rectangle){
  if(areas.has(rectangle)){
    console.log("Using cache..."); 
    return areas.get(rectangle);
  }
  const area = rectangle.x * rectangle.y;
  areas.set(rectangle, area);
  return area;
}
console.log(calculateArea(rectangle1)); // 20
console.log(calculateArea(rectangle1)); // Using cache... 20
console.log(calculateArea(rectangle2)); // 15

Set

A Set is an object that stores unique elements, which can be of any data type. It differs from an array in that it does not accept repeated values.

const charsets = new Set(["ASCII", "ISO-8859-1", "UTF-8"]);
console.log(Array.from(charsets)); //["ASCII", "ISO-8859-1", "UTF-8"]
  • size: Returns the number of elements;
  • add: Adds an element;
  • forEach: Iterates over the set;
  • has: Returns true if the element exists;
  • delete: Removes an element;
  • clear: Removes all elements;

WeakSet

WeakSet is an object, similar to Set, that allows only Object type values and maintains weak references, being volatile and non-iterable.

  • add: Adds an element;
  • has: Returns true if the element exists;
  • delete: Removes an element;

Iterables and Iterators

Iterables are objects that can be iterated over, meaning they can be traversed sequentially, accessing each of their elements individually. They implement the iteration protocol, providing a Symbol.iterator method that returns an Iterator. Examples of Iterables include Arrays, Strings, Maps, and Sets.

const languages = ["Fortran", "Lisp", "COBOL"];
const iterator = languages[Symbol.iterator]();

console.log(iterator.next().value); // "Fortran"
console.log(iterator.next().value); // "Lisp"
console.log(iterator.next().value); // "COBOL"
console.log(iterator.next().value); // undefined

Every Iterable has a Symbol.iterator key property that defines the iteration protocol for the object, called Iterators.

They are objects that control the iteration process, maintaining the current state of the iteration and providing values one by one. They have a next() method that returns an object with two properties: value (the next value in the iteration) and done (a boolean indicating whether the iteration has ended).

Iterables and Iterators allow for more flexible and efficient iterations and are fundamental to the use of constructs such as the for...of loops and operators like the spread (...).

function createIterator(...array){
  return {
    [Symbol.iterator]: {
      let i = 0;
      return {
        next () {
          if (i < array.length) {
             return { value: array [i++], done: false };
          } else {
            return { value: undefined, done: true };
          }
        }
      };
    }
  }
};
const iterator = createIterator("Fortran", "Lisp", "COBOL");
console.log([...iterator]);

Classes

Classes are a special type of function that act as a template for creating objects;

  • They are "models" that encapsulate data and behaviors;
  • They are created using a class declaration and the class keyword;
  • They generally follow the object-oriented paradigm, with inheritance, polymorphism, etc.;
  • Once created, they cannot be dynamically altered;
  • Classes are formed by 3 types of members: constructor, prototype methods, and static methods;
  • Prototype methods depend on an instance to be invoked;
  • Classes do not undergo hoisting, regardless of how they were declared;
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const maria = new Person("Maria", 20);

Class keywords:

  • Public: Public interface to be coupled. These class members are available to anyone who can access the class instance (owner).
  • Private: These members are only accessible within the class that instantiated the object. Reduces coupling (does not expose many implementation details).
  • Protected: This keyword allows a little more access than private members, but much less than public. A protected member is accessible within the class (similar to private) and in any object that inherits from it. A protected value is shared by all layers of the prototype chain. It is not accessible to anyone else.

Prototype: We can modify the functionality of the function/object/variables.

function Person(first, last, age, eyecolor) {
  this.firstName = first;
  this.lastName = last;
  this.age = age;
  this.eyeColor = eyecolor;
}

Person.prototype.name = function() {
  return this.firstName + " " + this.lastName;
};

Class vs Generator Function

Generator Function:

function Square(side) {
  this.side = side;
}

Square.prototype.calculateArea = function() {
  return Math.pow(this.side, 2);
}

Square.prototype.toString = function() {
  return `side: ${this.side} area: ${this.calculateArea()}`;
}

Square.fromArea = function(area) {
  return new Square(Math.sqrt(area));
}

const square = Square.fromArea(16);
console.log(square.toString()); // side: 4 area: 16
console.log(square.calculateArea()); // 16

Using Class:

class Square {
  constructor(side) {
    this.side = side;
  }

  calculateArea() {
    return Math.pow(this.side, 2);
  }

  toString() {
    return `side: ${this.side} area: ${this.calculateArea()}`;
  }

  static fromArea(area) {
    return new Square(Math.sqrt(area));
  }
}

const square = Square.fromArea(16);
console.log(square.toString()); // side: 4 area: 16
console.log(square.calculateArea()); // 16

The main logic is the same in both implementations. The main difference is the syntax used. In the class implementation, it is not necessary to define methods on the prototype. Additionally, the static method fromArea is defined directly on the class using the static keyword.

Inheritance

It is possible to create a class hierarchy using the extends keyword. When declaring a constructor in the subclass, it is necessary to invoke the superclass constructor through super() before using the this reference.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // Calls the superclass constructor
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog('Buddy');
dog.speak(); // Buddy barks.

In this example, the Dog class inherits from the Animal class. The Dog class has its own speak() method, which overrides the speak() method of the superclass Animal. In the constructor of the Dog class, we call super(name) to invoke the constructor of the superclass Animal and initialize the name property correctly.

Proxy and Reflect

A proxy is a wrapper object that intercepts fundamental operations on a target object. It can be used to create custom objects, validate inputs, logging, and other functionalities.

The Proxy object provides a set of methods, known as traps, that are invoked when certain operations are performed on the target object. Some examples of traps include:

  • get: invoked when a property is accessed
  • set: invoked when a property is set
  • deleteProperty: invoked when a property is removed
  • has: invoked when the in operation is used
  • ownKeys: invoked when Object.getOwnPropertyNames or Object.getOwnPropertySymbols are called
  • apply: invoked when a function is called
  • construct: invoked when the new function is used

The Reflect API provides methods that allow executing operations on the target object in a similar way to standard object operations, but with the ability to be intercepted by a proxy. It is often used in conjunction with proxies.

Here is an example of how to create a proxy for an array that validates access to numeric properties and throws an error if the property does not exist:

function createArray() {
  return new Proxy([], {
    set(target, key, value) {
      target.length = target.length || 0;
      target.length++;
      return Reflect.set(target, key, value);
    },
    get(target, key) {
      if (typeof key === "string" && key.match(/\d+/)) {
        if (!Reflect.has(target, key)) {
          throw `Property ${key} not found`;
        }
      }
      return Reflect.get(target, key);
    },
    deleteProperty(target, key) {
      if (Reflect.has(target, key)) {
        target.length--;
        return Reflect.deleteProperty(target, key);
      }
      return true;
    }
  });
}

const languages = createArray();
languages[0] = "Python"; // Adds the string "Python" at index 0
languages[1] = "JavaScript"; // Adds the string "JavaScript" at index 1
console.log(languages[2]); // Throws an error: "Property 2 not found"

In this example, the proxy created with createArray() intercepts the set, get, and deleteProperty operations on an array. The set trap updates the length of the array whenever a new element is added. The get trap checks if the numeric property exists before accessing it, throwing an error otherwise. The deleteProperty trap removes the element from the array and updates its length.

Modules

In ES6, or ECMAScript 2015, it was specified in the language itself, based on the concept of import and export.

export: Through the export keyword, it is possible to export any type of data that exists within a module.

import: The import keyword imports any type of exported data into the module.

  • To use modules in Node.js, the files must have the .mjs extension, and they must be executed with the --experimental-modules flag.

  • It is possible to use an alias in the import, renaming what is being imported.

  • Through the * operator, it is possible to import everything that is being exported into a single object.

  • We can also import and export by default using the default keyword.

Operators

Comparison Operators

  • Equal (==) - Returns true if the values of the two operands are equal.
const value = x == y
  • Not equal (!=) - Returns true if the values of the two operands are different.
const value = x != y  
  • Greater than (>) - Returns true if the left operand is greater than the right one.
const value = x &gt; y
  • Less than (<) - Returns true if the left operand is less than the right one.
const value = x &lt; y
  • Greater than or equal to (>=) - Returns true if the left operand is greater than or equal to the right one.
const value = x &gt;= y
  • Less than or equal to (<=) - Returns true if the left operand is less than or equal to the right one.
const value = x &lt;= y

These operators compare two values and return a boolean result (true or false) that can be used in conditional and loop structures.

The main difference between the == and === operators

  • The == operator compares only the value of the operands, performing type coercion if necessary. For example:
5 == "5" // returns true 

Here JavaScript converts the string "5" to number before comparison.

  • The === operator compares value and type of the operands. It's a stricter comparison. Example:
5 === "5" // returns false

Since one operand is number and the other is string, it returns false even though both have the "5" value.

In summary:

  • == compares values performing type coercion
  • === compares values and types without conversion

So in general using === is preferable to avoid unexpected behaviors. But sometimes the == coercion can also be useful.

Arithmetic Operators

Addition + Subtraction - Multiplication * Division / Remainder %

Assignment Operators

Addition += Subtraction -= Multiplication *= Division /= Remainder %=

Binary Operators

Or: | And: & Exclusive Or (XOR): ^ Negation (not): ~ Left shift << Basically it's a multiplication by 2 multiplied by the shift value.

> 4 << 2  
16
> (4).toString(2).padStart(32,0)
'00000000000000000000000000000100' 
> (16).toString(2).padStart(32,0)
'00000000000000000000000000010000'

Right shift >> Basically it's a division by 2 multiplied by the shift value.

> 128 >> 1
64 
> (128).toString(2).padStart(32,0)
'00000000000000000000000010000000'
> (16).toString(2).padStart(32,0) 
'00000000000000000000000001000000'

Right shift with sign change >>>

Other Operators

&& Operator for IF: The && (logical AND) operator evaluates the left expression and only evaluates the right expression if the left one is truthy. This allows for conditional returns like:

function foo(algo) {
  return algo && algo.method();  
}

|| Operator for IF: The || (logical OR) operator evaluates the left expression and only evaluates the right expression if the left one is falsy. This allows for conditional returns like:

function foo(algo) {
  return algo.method2() || algo.method();
}

For: Loop statement to iterate over data structures:

for(let i = 0; i < array.length; i++) {
  // do something
}

Spread operator: The spread (...) operator "spreads" the properties of an object or the elements of an array into another one. Useful for cloning and merging:

// Cloning  
const newArray = [...originalArray];  
const newObject = {...originalObject};   

// Merging arrays
const mergedArray = [...array1, ...array2];  
const mergedObject = {...object1, ...object2};

Destructuring operator: Allows extracting array values or object properties into distinct variables:

// Array
const [a, b] = [10, 20];

// Object   
const {prop1, prop2} = {prop1: 10, prop2: 20}; 

Numeric conversions

Not all numeric operators perform the desired (type coercion) conversion:

&gt; &quot;10&quot; + 0   
&quot;100&quot;
&gt; &quot;10&quot; - 5  
5  

Some conversions can lose information:

&gt;parseInt(&quot;9.9&quot;,10)
9  
&gt;parseFloat(&quot;0xFF&quot;)  
0
&gt;parseFloat(&quot;0b10&quot;)
0  
```## IEEE 754
IEEE 754 is a numerical representation standard created in 1985 and adopted by several programming languages like JavaScript, Ruby, Python and Java.
![IEEE 754 Single Floating Point Format.svg](//images.ctfassets.net/bp521nfj3cq3/7aRiOLra0gXN4wxWAzMvdf/2c3d2eda2a012bc9d4a7618cefa47c5b/IEEE_754_Single_Floating_Point_Format.svg.png)

Rounding errors can occur: 

0.1 + 0.2
0.30000000000000004 666.7 - 666.6 0.10000000000002274
33.3 * 3
9989999999999999 12.2 / 0.1 121.99999999999999

You can check on the [IEEE 754 calculator.](http://weitz.de/ieee/)   

Infinity, which can be positive or negative, is returned when an operation exceeds the number type limits.  

1/0;
Infinity
Math.pow(10, 1000);
Infinity Number.MAX_VALUE * 2 Infinity
Math.log(0);
-Infinity -Number.MAX_VALUE * 2
-Infinity


NaN, or Not a Number, is returned when we perform a numeric operation where the result cannot be determined. It does not throw an error, continues the flow.   

```javascript
&gt; 10 *&quot;Javascript&quot;;   
NaN  
&gt; 0/0;
NaN   
&gt; Math.sqrt(-9);
NaN   
&gt; Math.log(-1);  
NaN
&gt; parseFloat(&quot;JavaScript&quot;);  
NaN

String

In JavaScript, there are two main types of strings:

  1. String literal ("normal" string): Strings created by placing text between single quotes (&#39;&#39;) or double quotes (""``). For example:
let string1 = 'This is a string';
let string2 = "Also a string"; 

String literals are the most common string types in JavaScript.

  1. String Object: The String object in JavaScript provides additional methods and properties to work with strings. For example:
let s = new String("Hello World");
console.log(s.length); // 11

Here we create a String object using the String constructor and then access the length property of that object.

The main difference is that string literals are more straightforward, while the String object allows for extended functionality.

But in most cases, string literals are sufficient, with String object usage being less common. Some key differences:

  • String literals have better performance
  • String objects can have methods added and behaviors modified, while string literals are more immutable.

So in summary, for most purposes, focusing on using string literals is ideal for working with strings in JavaScript. The String object is more advanced and rarely necessary.

String literal String Object

let counter = 0;
console.time("performance"); 
while(counter < 100000){
  new String("JavaScript");
  counter++;
}
console.timeEnd("performance");


let counter = 0;
console.time("performance"); 
while(counter < 100000){
  "JavaScript";
  counter++;
}
console.timeEnd("performance");
    
performance: 5.56884765625 ms performance: 1.032958984375 ms

Math

Math is a global object that contains mathematical constants and methods for performing number-related operations.

> Math.E;
2.718281828459045  
> Math.LN10; // Natural logarithm of 10  
2.302585092994046  
> Math.LN2; // Natural logarithm of 2
0.6931471805599453
> Math.LOG10E; // Base 10 logarithm of E 
0.4342944819032518
> Math.LOG2E; // Base 2 logarithm of E
1.4426950408889634  
> Math.PI;  
3.141592653589793
> Math.SQRT1_2; // Square root of 1/2  
0.7071067811865476
> Math.SQRT2; // Square root of 2
1.4142135623730951
  • abs: Converts the number's sign to positive;
  • ceil: Rounds the number up;
  • floor: Rounds the number down;
  • round: Rounds the number up if the decimal part is from 5 to 9 and down if it is from 0 to 4;
  • sign: Returns 1 if the number is positive and -1 if negative;
  • trunc: Eliminates the number's decimal part, making it an integer;
  • min: Returns the smallest number passed as a parameter
> Math.min(1,2,3,4,5,6)  
1
  • max: Returns the largest number passed as a parameter
> Math.max(1,2,3,4,5,6)    
6
  • random: Returns a random number between 0 and 1, not including 1

Regex

Regular expressions are structures formed by a sequence of characters that specify a formal pattern that is used to validate, extract or even replace characters within a String.

We have two ways to represent a regular expression:

let regExp1 = new RegExp("john@gmail.com")

let regExp2 = /john@gmail.com/;

We can test:

let result = regExp.test("john@gmail.com");
console.log(result); //true

We can execute:

let result = regExp.exec("john@gmail.com");
console.log(result); //['john@gmail.com', index: 0, input: 'john@gmail.com', groups: undefined]

Metacharacters: are characters with specific functions, that inform patterns and positions impossible to be specified with normal characters.

^ - Starts with a certain character

$ - Ends with a certain character

\ - The backslash is used before special characters, in order to escape them

[abc] - Accepts any character inside the group, in this case a, b and c

[!abc] - Does not accept any character inside the group, in this case a, b or c

[0-9] - Accepts any character between 0 and 9

[^0-9] - Does not accept any character between 0 and 9

\w - Represents the set [a-zA-Z0-9_]

\W - Represents the set [^a-zA-Z0-9_]

\d - Represents the set [0-9]

\D - Represents the set [^0-9]

\s - Represents a whitespace

\S - Represents a non-whitespace

\n - Represents a newline

\t - Represents a tab

Quantifiers can be applied to characters, groups, sets or metacharacters.

{n} - Quantifies a specific number

{n,} - Quantifies a minimum number

{n,m} - Quantifies a minimum and maximum number

? - Zero or one

*- Zero or more

+- One or more

Capture Groups () - Determine a capture group to extract values from a given String

this

The "this" implicit variable that gives us access to the object that was responsible for invoking the method. Therefore, "this" is related to the lexical context, depending on where it is called. When we use an arrow function, "this" is fixed where the function was declared.

function f1() { console.log(this == window) }
function f2() { console.log(this == body) }
const f3 = () => console.log(this == window)

getter and setter

Getter and setter functions are used to intercept access to the properties of a given object.

const rectangle = {
  set x(x) {
    this._x = x;
  }
  set y(y) {
    this._y = y;  
  }
  get area() {
    return this._x * this._y;
  }
};
rectangle.x = 10;
rectangle.y = 2;
console.log(rectangle.area);

Call and Apply

The call and apply methods in JavaScript are used to invoke a function, allowing you to specify the value of this and pass arguments to the function. Here is the text with explanations and quick examples:

Through the call and apply operations, it is possible to invoke a function by passing this as a parameter. The difference between them lies in how the arguments are passed to the function:

call:

The call method calls the function, passing the value of this as the first argument and the function arguments separated by commas. Example:

function greet(greeting, name) {
  console.log(`${greeting}, ${name}!`);
}
const obj = { name: 'John' };
greet.call(obj, 'Hello', 'John'); // Output: Hello, John!

apply:

The apply method calls the function, passing the value of this as the first argument and the function arguments in an array. Example:

function sum(a, b) {
  return a + b;
}
const result = sum.apply(null, [3, 5]); // Output: 8

Both methods allow you to execute a function with a specific this value, which is useful in situations such as:

  • Borrowing methods from one object to another.
  • Chaining constructors in prototype inheritance.
  • Applying array methods to array-like objects (e.g., arguments).

It's important to note that, starting with ECMAScript 6 (ES6), the arrow function syntax (=&gt;) does not have its own this context. Instead, it inherits the this from the lexical scope it belongs to. Therefore, call and apply do not affect the this of arrow functions.

Bind

Binds a specific context for this. Useful when you lose the correct context of this:

In JavaScript, it's difficult to guess the .this context of functions by just looking at them. This is because they can be executed in the future, and this is the major cause of those "undefined is not a function" errors.

Contrary to what many say, the solution is simpler than you might imagine. What you need to keep in mind is: who will execute that function in the future?

A great example is the browser's "click" event. Let's take a look at this scenario:

const instance = {
    name: 'test',
    myOnClick() {
      console.log('name', this.name)
   }
}
window.addEventListener('click', instance.myOnClick)

The myOnClick function of the instance object will be triggered in the future and will inherit the this from window, because addEventListener is part of the window context. Therefore, the name property will not exist, and we'll get an "undefined" result.

That's where the .bind function comes in. With it, you configure exactly where the function will inherit the this context from.

So, to solve the problem, you just need to "bind" the instance manually as the context:

window.addEventListener('click', instance.myOnClick.bind(instance))

This works for cases involving classes, objects, and, most importantly, when you need to convert a callback to a Promise in Node.js, something like:

await util.promisify(fs.write.bind(fs))(...args)

New

How to create an object from the same structure? The factory function, which is a type of pattern, returns a new object after being directly invoked.

// Factory implementation equivalent to the new method
const _new = function(fn, ...params) {
  const obj = {};
  Object.setPrototypeOf(obj, fn.prototype);
  fn.apply(obj, params);
  return obj;
}

What to do to eliminate duplication and reuse properties between objects? Every function has a property called prototype, which is linked to the proto of the object created by the new operator:

const Person = function (name, city, year){
  this.name = name;
  this.city = city;
  this.year = year;
};
Person.prototype.getAge = function() {
  return (new Date()).getFullYear() - this.year;
};
const person1 = new Person("Linus Torvalds", "Helsinki", 1969);
const person2 = new Person("Bill Gates", "Seattle", 1955);
console.log(person1);
console.log(person1.__proto__); console.log(person1.getAge());
console.log(person2);
console.log(person2.__proto__); console.log(person2.getAge());
console.log(person1.__proto__ === person2.__proto__); // true

Don't forget to use the new operator when using constructor functions so that the prototype connections and properties are made.

instanceof

With the instanceof operator, it's possible to check if an object was created through a specific constructor function by analyzing its prototype chain

// Implementation of instanceof
const _instanceof = function (obj, fn){
  if (obj === fn.prototype) return true;
  if (obj === null) return false;
  return _instanceof(obj.__proto__, fn);
}
const date = new Date();
console.log(date instanceof Date);
console.log(date instanceof Object);
console.log(date instanceof Array);
console.log(_instanceof(date, Date));
console.log(_instanceof(date, Object));
console.log(_instanceof(date, Array));

Arrow functions

Arrow functions have a simpler and more direct approach to writing a function and can improve code readability in various situations Arrow functions do not have their own this and arguments variables, so they are not suitable for use as object methods:

const person = {
  name: "James Gosling", city: "Alberta", year: 1955,
  getAge: () => {
    return (new Date()).getFullYear() - this.year;
  }
};
console.log(person);
console.log(person.getAge()); //NaN because this is undefined

Execution Context

The Execution Context is the environment where the code is executed, consisting of the variable object, scope chain, and this. Inside a function, it's possible to access existing variables outside of it through the scope chain. But they are isolated internally, so it's not possible to access from outside a variable that was declared inside a function In the case below, this is from fn1 and therefore returns p1 as undefined.

const obj1 = {
  p1: 10,
  getP1: function() {
    const fn1 = function() {
      return this.p1;
    }
    return fn1();
  }
};
console.log(obj1.getP1()); // undefined

We can solve this:

const obj1 = {
  p1: 10,
  getP1: function() {
    const that = this
    const fn1 = function() {
      return that.p1;
    }
    return fn1();
  }
};
console.log(obj1.getP1()); // 10

Or using an arrow function that does not carry the this:

const obj1 = {
  p1: 10,
  getP1: function() {
    const fn1 = () => {
      return this.p1;
    }
    return fn1();
  }
};
console.log(obj1.getP1()); // 10

Closures

Every function allows the use of variables that were neither declared nor passed as parameters. The problem is that since functions are first-class, depending on the situation, there could be ambiguity, and this was solved with the concept of closure, which is a function with a static scope chain that is defined at the time when the function is created. For this reason, all functions in the JavaScript language are closures. Although static, the scope chain refers to objects that are in memory and can be shared by more than one function.

function fn1() {
  let v1 = 10;
  return {
    m1() {
      console.log(++v1);
    },
    m2() {
      console.log(--v1);
    }
  };
}
const obj1 = fn1();
obj1.m1(); //11
obj1.m2(); //10

Exception Handling

Any type of data can be thrown as an error, interrupting the execution flow.

// Function that can throw an exception
function divide(a, b) {
  if (b === 0) {
    throw new Error("Cannot divide by zero.");
  }
  return a / b;
}

try {
  // Calling the function that can throw an exception
  const result = divide(10, 0); // Throws an exception
  console.log(result);
} catch (error) {
  // Catch and handle the exception
  console.error("An error occurred:", error.message);
} finally {
  // Optional block that is executed regardless of whether an exception occurred or not
  console.log("Operation finished.");
}

Tagged templates

Tagged templates (or template tags) are an advanced JavaScript feature that allows processing template literal values ​​with a custom function.

The function used to process the template literal is called a "tag function". It receives as parameters the static strings of the template and the interpolated values.

function format(parts, ...values) {
  const formatted = values.map(value => {
    if(typeof value === 'number') {
      return value.toLocaleString('en-US');  
    }

    return value;
  });

  return parts.reduce((str, part, i) => {
    return `${str}${part}${formatted[i] || ''}`;
  }, '');  
}

const price = 1000.99;
const message = format`The price is $ ${price}`;

console.log(message); // The price is $ 1,000.99  

In this way, the custom format tag applies the appropriate formatting to the interpolated values ​​in the template literal.

This greatly facilitates reusing and standardizing string output rules in an application.

Asynchronicity

Asynchronicity in JavaScript allows code to execute in a non-blocking way (since javascript is single threaded), improving performance and user experience. However, just returning a Promise does not automatically make the code asynchronous.

Let's look at an example:

console.log("Before the asynchronous function"); 

async function loopMillionsOfTimes() {
  for(let i = 0; i < 1000000; i++) {
    // some simple task
  }
  console.log("Loop finished");
}

loopMillionsOfTimes().then(() => 
  console.log("Promise returned")
);

console.log("After the asynchronous function");

The result will be:

Before the asynchronous function
Loop finished  
After the asynchronous function
Promise returned

Despite returning a Promise, the entire loop will block execution of the code until it finishes. This is because we are not using any internal asynchronous JavaScript APIs.

To really make this asynchronous, we can use setTimeout:

console.log("Before the asynchronous function");

async function asyncLoop() {
  return new Promise((resolve)=> setTimeout(() => {
    for(let i = 0; i <= 1000000; i++){
      // something simple
    }
    // long loop
    console.log("Loop finished");
    resolve();
  }, 0))

}

asyncLoop().then(() =>
  console.log("Promise returned")
);

console.log("After the asynchronous function");

The result will be:

Before the asynchronous function
After the asynchronous function 
Loop finished
Promise returned

This way, the loop will be scheduled for future execution, allowing the code to continue normally.

Other common asynchronous APIs in JavaScript include fetch, requestAnimationFrame, setInterval and many more. Using them properly, we can write high-performance asynchronous code in JavaScript.

Promises

Promises are objects responsible for modeling asynchronous behavior, allowing their treatment in an easier and more direct way. To create a promise, you simply instantiate it, executing the resolve function in case of success, being handled through then.

In case of failure, the reject function must be executed, being handled through catch.

It's possible to centralize the treatment of a promise by chaining its returns.

console.time("performance");
function sum(a, b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(a + b);
    }, 1000);
  });
}
sum(2, 2)
  .then(function(a) {
    return sum(4, 4).then(function(b) {
      return sum(a, b);
    });
  })
  .then(function(result) {
    console.log(result);
    console.timeEnd("performance"); // 3.006s
  })
  .catch(function(e) {
    console.log(e);
  });

We can execute several promises at the same time, returning after all have succeeded using Promise.all.

console.time(&quot;performance&quot;);
function sum(a, b) {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
      resolve(a + b);
    }, 1000);
  });
}
Promise.all([sum(2, 2), sum(4, 4)])
  .then(function(values) {
    const [a, b] = values;
    return sum(a, b).then(function(result) {
      console.log(result);
      console.timeEnd(&quot;performance&quot;);
    });
  })
  .catch(function(e) {
    console.log(e);
  });

We can also execute several promises at the same time, returning after the first one has succeeded using Promise.race.

Async/Await

async/await facilitates the interaction with asynchronous calls, waiting for the return of a given promise. To handle potential exceptions associated with asynchronous calls, it's possible to use a try/catch block. It's also possible to use the for-await-of block to iterate over a promises iterator.

// Declaring an asynchronous function
async function fetchData() {
  try {
    // Awaiting the promise's return
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    // Handling exceptions
    console.error(error);
  }
}

// Calling the asynchronous function
fetchData();

// Using for-await-of with a promises iterator
const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3',
];

const fetchAllData = async () => {
  const data = [];
  for await (const url of urls) {
    const response = await fetch(url);
    data.push(await response.json());
  }
  console.log(data);
};

fetchAllData();

Compilers

Babel is a JavaScript compiler. It is used to convert ES6 code into a compatible JS version for different environments.

Not everything supports ES6. If you are using modern syntax like React, you will need Babel on hand.

We hope all browsers support ES6 one day. But for now, to make sure everything works perfectly, Babel can't be missing from your project.

The module folding or tree-shaking is an optimization technique that removes unused Javascript code during the construction of an application before putting it into production.

It analyzes application files to identify unused exports and imports from Javascript modules. Then it eliminates those unreferenced features, reducing the size of the compiled Javascript package without affecting functionality.

In this way, tree-shaking removes "dead code" and leaves only the resources actually needed to run the application, improving loading performance and memory usage. It is very useful in modern applications that use large modular Javascript libraries.

Bundler

webpack is a JavaScript module bundler for web applications. Some of its main features and benefits are:

  • Bundling - webpack takes all Javascript, CSS, images, fonts and other files and dependencies and bundles them into one or more optimized bundles for production. This improves performance by reducing the number of requests.

  • Loaders - webpack's loaders allow you to integrate a variety of different languages and preprocessors into your build pipeline, such as JSX, TypeScript, SASS, Less etc.

  • Code splitting - webpack allows you to split your code into multiple bundles that can be loaded on demand or in parallel, improving page load time.

  • Minification and code optimization - webpack can automatically minify and optimize all the Javascript, CSS, HTML and images of your application for production through plugins.

  • Hot Module Replacement - HMR updates modules in the browser in real time without having to refresh the entire page, greatly improving the development experience.

  • Simplified environment and build - webpack takes care of the entire development and production build process, task configuration, environments and more.

Interpreters

A Javascript interpreter reads the Javascript source code line by line and executes it immediately, without a separate compilation step. As the interpreter walks through the code, it allocates memory for variables and functions and associates them with their respective scopes.

Hoisting refers to the behavior of the Javascript interpreter to move function, variable and class declarations to the top of their scopes. This happens because the interpreter makes two passes over the code:

On the first pass, the interpreter "hoists" all function and variable declarations to the top of their scopes. Functions are entirely moved, while only variable declarations are moved, not assignments.

On the second pass, the code is executed line by line. Since declarations have already been moved to the top


More posts

Cover Image for The Phychology of Money

The Phychology of Money

Morgan Housel offers valuable insights on financial management and decision-making. The author emphasizes that financial success depends more on behavior than on intelligence or technical knowledge. Housel highlights the importance of long-term vision and resilience in the face of market volatility, encouraging us to focus on sustainability rather than short-term gains.

Cover Image for Bellsant

Bellsant

I've been at the forefront of developing a cutting-edge health and wellness app. Our tech stack combines React Native for cross-platform mobile development with a serverless NodeJS backend, leveraging AWS Lambda for scalability and cost-efficiency.

Abílio Azevedo
Abílio Azevedo

NewsLetter

I will send the content posted here. No Spam =)

Experienced Software Engineer with degree in Electrical Engineering with over 10 years of hands-on expertise in building robust and scalable mobile, web and backend applications across various projects mainly in the fintech sector. Mobile (React Native), Web (React and Next.JS) and Backend (Node.JS, PHP and DJANGO). My goal is to create products that add value to people. - © 2024, Abílio Azevedo