JavaScript Tutorial
What is JavaScript?
JavaScript is a programming language that originated in 1995 as a simple client-side scripting language for Netscape Navigator browser but metamorphosed into one of the underpinning forces behind World Wide Web (alongside HTML and CSS), and had its usage extended to server-side programming (primarily due to the origination of the Node.js server-side runtime environment). Although JavaScript started as one company endeavor, it is now used universally with most major browsers featuring JavaScript runtime environments, and Ecma International standardizing its versions.
Introduction
From the technological perspective, JavaScript is a programming language that:
- has a C-like syntax,
- is high-level,
- is just-in-time compiled,
- is weakly typed,
- is dynamically typed,
- features prototype-based inheritance (aka prototype-based object-orientation),
- features class-based inheritance (aka class-based object-orientation), and
- supports multi-paradigm programming, including object-oriented, functional, imperative, and event-driven programming.
JavaScript Origins
In 1993, the NCSA Mosaic browser, developed at the National Center for Supercomputing Applications at the University of Illinois, was released. It became the first publicly used browser available on the Windows operating system that integrated text and graphics. It supported multiple Internet protocols such as Hypertext Transfer Protocol (HTTP) and File Transfer Protocol (FTP) but it did not feature a scripting language similar to JavaScript. Microsoft licensed one of the versions of Mosaic in 1995 and built Internet Explorer on top of that.
In 1994, the lead developers of Mosaic founded Netscape Navigator browser which quickly became prevalent. In 1995, it was decided to add a scripting language to Netscape Navigator to allow for dynamic modifications of documents. One of the ideas was to use the Java language in collaboration with Sun Microsystems, the other was to embed the Schema language. However, finally, it was decided to devise a new one. The task was entrusted to Brendan Eich.
The newly devised scripting language was called LiveScipt in one of the beta iterations of the Netscape browser but the name was changed to JavaScript for the official release. The first official version of the Netscape Navigator browser that featured JavaScript was released in December 1995.
Microsoft also released its browser, Internet Explorer, in 1995. Internet Explorer was equipped with JScript which interpreter was reverse-engineered JavaScript interpreter from Netscape Navigator. Thus, the march to the prevalence of JavaScript as the main scripting language of the web has begun.
JavaScript Ecma Standardization
Approximately one year after its official debut Netscape submitted JavaScript to Ecma International for standardization.
The first version of JavaScript standardized by Ecma International was called ECMAScript (ES1) and released in 1997.
The second standardized JavaScript version called ECMAScript 2 (ES2) was released in 1998.
The third standardized JavaScript version called ECMAScript 3 (ES3) was released in 1999.
In the beginning, Microsoft and its browser Internet Explorer adhered to the Ecma standardization but later its collaboration stopped. Given the prevailing market share of Internet Explorer at the time, this caused the fourth version of the standardized JavaScript version (ES4) to never be released.
Microsoft's dominance in the browser market of the early 2000s was broken due to the release in 2004 of Netscape Navigator successor, Firefox, and the release in 2008 of Google Chrome. Google Chrome's featured fast JavaScript runtime engine - V8. Both browsers bit huge chunks from the browser market. The need for standardization arose anew.
The joint effort of interested parties and Ecma International led to the release of epochal JavaScript versions, namely ECMAScript 5 (ES5) in 2009 and ECMAScript 6 (ES6) in 2015.
The subsequent versions of JavaScript are denoted by their release year rather than a number. Therefore, after ES6, the ECMAScript releases were called ECMAScript 2016, ECMAScript 2017, ECMAScript 2018, ECMAScript 2019, etc. Although, their numeric names, ES7, ES8, ES9, ES10, etc. are also sometimes used.
JavaScript as a Client-Side Programming Language
As outlined above, JavaScript originated as a user-agent (browser) built-in programming language.
Today, JavaScript is the primary and the most pervasive client-side language of the World Wide Web.
On the client-side JavaScript can be used among others to:
- manipulate Document Object Model, i.e. to add, change, and remove elements of documents in browsers, and
- fetch data from remote hosts.
To streamline JavaScript development on the client-side many libraries and frameworks were developed. Currently, amongst the most popular are:
JavaScript as a Server-Side Programming Language
Although JavaScript began its life as a client-side (browser embedded) scripting language it is also now vastly used as a server-side language.
The popularity of JavaScript as a server-side programming language began with the creation of Node.js in 2009 by Ryan Dahl.
Node.js uses JavaScript V8 engine developed by Google under the hood. Node.js features event loop and I/O APIs.
The default package manager for Node.js is called npm.
There are many server-side JavaScript libraries and frameworks that facilitate development. Some of the notable are:
JavaScript as a High-Level Programming Language
JavaScript is a high-level programming language.
A high-level programming language is a programming language that does not require a direct management of computer hardware (e.g. a memory management) but is capable of abstracting away such control when needed through its built-in underlying procedures and therefore allows for easier and faster program development.
JavaScript as a Just-In-Time Compiled Programming Language
JavaScript is a just-in-time compiled programming language.
A just-in-time compiled programming language is a programming language in which source code is being compiled to machine code in parts during those parts run-time execution as opposed to ahead-of-time complete source-code compilation. Generally, the just-in-time compilation combines the speed of ahead-of-time compilation and the flexibility of interpretation.
JavaScript as a Weakly-Typed Programming Language
JavaScript is often referred to as a weakly typed programming language.
However, there are many different definitions of what a weakly typed programming language actually is.
When referring to JavaScript as a weakly typed language it is often understood as JavaScript capabilities of implicit casting of its values in some of its built-in operators.
For example, when comparing two values of different data types using abstract comparison == operator those values are implicitly cast to congruent data types before actual comparison.
1 == "1" // => trueAnother example is an implicit casting of both operands to strings - when any of the operands is not a number - by the binary + operator.
1 + "1" // => "11"Yet another example is an implicit casting of both operands to numbers - when any of the operands is not a number - by the binary - operator.
1 - "1" // => 0JavaScript as a Dynamically Typed Programming Language
JavaScript is a dynamically typed programming language.
A dynamically-typed programming language is a programming language in which an identifier (such as a variable) current data type is inferred by the interpreter during runtime and is allowed to dynamically change during that runtime as opposed to being statically set (explicitly or implicitly) before runtime compilation.
// JavaScript (dynamic typing)
let foo = 42
foo = "forty-two" // Re-assignment from number to string allowed.// TypeScript (static typing)
let foo = 42 // foo data type implicitly statically set to type 'number'
foo = "forty-two" // Type 'string' is not assignable to type 'number'.JavaScript as a Prototype-Based Programming Language
Since its beginning in 1995, JavaScript has been a prototype-based programming language.
A prototype-based programming language is an object-oriented programming language in which an object can inherit properties of another object through a) setting that object as a prototype and delegating property calls to that other object (delegation-based prototyping), or b) copying that other object properties (concatenative prototyping), and as opposed to inheriting properties through an instantiation of a specially dedicated construct called a class.
JavaScript implements delegation-based prototyping.
const human = { profession: 'working', order: 'Primates' }
const student = { name: 'Dave', profession: 'studying' }
Object.setPrototypeOf(student, human)
student.name // => 'Dave'
student.profession // => 'studying'
student.order // => 'Primates'Further reading at Prototypal Inheritance in JavaScript.
JavaScript as a Class-Based Programming Language
Since ECMAScript 2015 (ES6), JavaScript is a class-based programming language.
A class-based programming language is an object-oriented programming language in which an object can inherit properties from a specially dedicated for inheritance construct called a class (which might and might not be an object itself), and as opposed to inheriting properties from a non-class object.
class Dog {
constructor(name) {
this.name = name
}
bark() {
console.log('Woof!')
}
}
const pluto = new Dog('Pluto')
pluto.name // => 'Pluto'
pluto.bark() // 'Woof!'Further reading at Class-Based Inheritance in JavaScript.
JavaScript as an Object-Oriented Programming Language
JavaScript is an object-oriented programming language.
An object-oriented programming language is a programming language which features objects which are programming constructs a) with unique identity, b) encapsulating data pieces (often called state) and procedures (often called methods), jointly referred to as properties, and c) which can inherit the said properties from specially dedicated for inheritance constructs called classes (which might and might not be objects themselves) and/or from other non-class objects, which are then called prototypes.
const pirate = {
name: 'Hook',
battleCry: function() { console.log('Huzzah!') }
}
pirate.name // => 'Hook'
pirate.battleCry() // 'Huzzah!'However, JavaScript is not a purely object-oriented programming language (like for example Ruby) as it still features primitives (such as numbers, strings, and booleans) which have no state, nor methods associated with them.
JavaScript as a Functional Programming Language
JavaScript is a functional programming language.
A functional programming language is a programming language in which functions can be bound to identifiers, such as among others variables, passed as other function arguments, and returned as values from other functions.
When functions have capabilities of being bound to identifiers, passed as arguments, and being other functions return values, they are often referred to as the first-class citizens.
JavaScript can make use of two functional programming paradigms:
- an impure functional programming, and
- a pure functional programming.
An impure functional programming is a functional programming:
- which can rely on out-of-scope values and therefore can return different values when provided the same arguments, and
- in which function execution is allowed to have side effects such as out-of-scope value modification.
A pure functional programming is a functional programming:
- which cannot rely on out-of-scope values and therefore always returns the same value when provided the same arguments, and
- in which function execution is not allowed to have side effects such as out-of-scope value modification.
JavaScript as an Imperative Programming Language
JavaScript can be used as an imperative programming language.
An imperative programming language is a programming language which uses a sequence of commands (in Latin imperare means command), aka statements, each of which denotes a specific action to be carried out that can directly change a given program's process state.
let age = 42
const feelingYoung = true
if (feelingYoung) {
age = age / 2
}JavaScript as an Event Driven Programming Language
JavaScript can be used as an event-driven programming language.
An event-driven programming language is a programming language in which the control flow of a program's process is determined by events triggered by a) the process itself, b) other process messages, and c) user interactions (key presses, mouse clicks, mouse scrolls, etc.).
// Logs "window clicked" every time the browser window is clicked.
const logWindowClicked = () => console.log('window clicked!')
window.addEventListener('click', logWindowClicked, false)Variables
Var
Before ES6, the way to declare a variable was using the var keyword.
var foo = 'bar'Not only the reference of a var variable can be changed but the variable itself can be redeclared.
var foo = 'bar'
foo = "rab"
foo // => "rab"
var foo = 42
foo // => 42Function Scoping of Var
Variables declared with the var keyword are function scoped therefore - unless being declared in the global scope - they are only available within the scope of functions in which they are declared.
var foo = 'bar'
function logFoo() {
var foo = 'rab'
console.log(foo)
}
console.log(logFoo()) // "rab"
console.log(foo) // "bar"
if (true) {
var foo = '42'
}
console.log(foo) // 42Hoisting Var
var variables - irrespective of where in a given scope are declared - are hoisted before code execution i.e. moved to the beginning of the scope and initialized with a value of undefined.
function logVar() {
console.log(foo)
var foo = 'bar'
console.log(foo)
}
logVar() // undefined "bar"Let
In addition to var ES6 introduced let and const keywords. let allows for defining variables, and const for defining constants.
Reference of both - a var variable and a let variable - can be changed but the let variable - as opposed to the var variable - cannot be redeclared.
let foo = 'bar'
foo = 'rab'
foo // => "rab"
let foo = 42 // Uncaught SyntaxError: Identifier 'foo' has already been declaredBlock (aka Lexical, aka Static) Scoping of Let
A variable declared with let is not function scoped like var but block scoped. It means that a separate scope for let variable does not only exist within a global scope, within a function scope but also within a block scope.
let foo = 'bar'
if (true) {
let foo = 'rab'
}
console.log(foo) // "bar"Const
ES6 introduced the possibility of declaring constants with a const keyword.
Not only constants cannot be redeclared but also their references cannot be changed. Although, a reference of a once declared constant cannot change that does not mean that the referenced value is not allowed to change.
const foo = 'bar'
foo = 'rab' // Uncaught TypeError: Assignment to constant variable.
const foobar = { a: 1 }
foobar.a = 2
foobar // { a: 2 }Constants - just like variables declared with let - are lexically scoped.
Primitives
6 + 1 Primitives in JavaScript?
In ES6 there are six primitive data types: undefined, boolean, number, string, bigInt, and symbol.
Further, a special primitive type is null, aka Structural Root Primitive.
Immutability
All primitive type values in JavaScript are immutable.
Checking Type
To check whether a given value is of a given type typeof is used.
typeof undefined === 'undefined' // => true
typeof true === 'boolean' // => true
typeof 1 === 'number' // => true
typeof '1' === 'string' // true
typeof BigInt(4242424242424242424242424242424) === 'bigint' // => true
typeof Symbol() === 'symbol' // => true
typeof null === 'object' // trueundefined
undefined is used to express that the referenced value does not exist. For example, a variable that has been declared but has not been assigned any value has the value of undefined assigned automatically.
boolean
There are two Boolean values in JavaScript: true and false.
number
The number data type denotes both an integer or a floating-point number.
The range of integer values that the number data type can represent is from -(2^53 − 1) to 2^53 − 1. To check whether a given integer number can be safely expressed by the number data type the Number.isSafeInteger() method can be used. If a given integer falls out of the range it is expressed by double-precision floating-point approximation. This problem is solved by the bigint data typed.
bigint
The BigInt data type can be used to represent integers with arbitrary precision that fall out of the save range of the Number data type. A BigInt is created through appending n at the end of a given integer of Number type or through use of the BigInt() constructor.
typeof 9007199254740992n // => "bigint"
typeof BigInt(9007199254740992) // => "bigint"string
JavaScript type String is dedicated to representing textual data. A string in JavaScript is an ordered set of characters which in turn are visual representations of 16-bit unsigned integers. The index of the first character is 0.
Strings in JavaScript are immutable.
// String.substr() is used to get a substring.
'abcdef'.substr(1,3) // => "bcd"
//To concatenate a string use String.concat().
'abc'.concat('def') // => "abcdef"symbol
A primitive type Symbol is a novelty introduced in ES6 to JavaScript. A symbol in JavaScript is an anonymous, unique value, an identifier that can be used as an object property.
To create a symbol use the function Symbol() to which a description argument can be provided. Providing the same description twice to Symbol() does not make the two symbols equal. Each symbol in JavaScript is unique.
Symbol('foo') === Symbol('foo') // => false
const symA = Symbol()
const symB = Symbol()
const anObj = {
[symA]: 1,
[symB]: 2
}
console.log(anObj) // {Symbol(): 1, Symbol(): 2}
anObj[symA] // => 1
anObj[symB] // => 2Symbol properties are ignored by JSON.stringify().
null
null is used to denote that the referenced value exists but the value is used to express emptiness. null is often used as value to keys where an object value can be expected in the future.
foo // Uncaught ReferenceError: foo is not defined
let foo // => undefined
foo = null // => nullFunctions
What Are Functions in JavaScript?
A function in JavaScript is a potentially reusable set of expressions and statements which accepts an input of zero or more parameters and returns a value.
A function can be defined (aka declared) in two ways. The first way - available in both ES5 and ES6 - is to use 1) the function keyword along with optional function name, 2) zero or more function parameters enclosed in parenthesis and separated by commas, and 3) the block of code denoted with curly braces.
function sum(a, b) {
return a + b
}
sum(2, 2) // => 4A function without a name is considered to be an anonymous function.
(function(a, b) {
return a + b
})(2, 2) // => 4
const sum = function(a, b) { return a + b }
sum(2, 2) // => 4Returning Values
To return a specific value the keyword return has to be used. If no return value is specified a function returns this for constructor functions called with new and undefined for all other functions.
Passing Arguments
Primitive data types (such as numbers, strings, or booleans) are passed by value. Non primitive data types (such as objects or arrays) are passed through passing copied values of their references. Therefore, all arguments of any data types are passed by value.
It means that any changes to a value of an argument are not effective outside of the enclosing function body however that does not mean that it is not possible to change the object referenced by the argument.
const enterpriseCaptains = { best_captain: 'Picard' }
function replaceCaptains(captains) {
captains = { best_captain: 'Hook' }
}
replaceCaptains(enterpriseCaptains)
enterpriseCaptains // => { best_captain: 'Picard' }
function changeCaptain(captains) {
captains.best_captain = 'Janeway'
}
changeCaptain(enterpriseCaptains)
enterpriseCaptains // => { best_captain: 'Janeway' }First Class Citizens
Functions in JavaScript are first class citizens which mean they can be a) passed as arguments to other functions, b) values returned by other functions, and c) pointed to by variables or stored as object property values.
When a function accepts another function as an argument and/or returns a function it is called a higher order function. Otherwise, it is called a first order function.
An example of a higher order function is a map function.
const numbers = [1,2,3]
numbers.map(function(number) { return number * 2 }) // => [2,4,6]Hoisting
Function declarations are hoisted, initialized with the declared value and can be called in a given scope before their actual declaration. Compare it to hoisting of var, let, and const.
sum(2, 2) // => 4
function sum(a, b) { return a + b }Recursion
A function can call itself in three ways: 1) through calling its declared name, 2) through calling the reference to itself, and 3) through calling arguments.callee from within its body.
Closures
Functions in JavaScript are closures which mean they carry with themselves variables, constants and function declarations declared outside of their bodies but used within their bodies.
Arrow Functions
Another way of declaring a function to a function keyword is using an arrow function notation that was introduced in ES6.
const sum = (a, b) => { return a + b }
sum(2, 2) // => 4The curly braces and the return keyword can be omitted in a one line arrow function in which case the function always returns its body's expression.
const sum = (a, b) => a + b
sum(2, 2) // => 4Objects
What are objects in JavaScript?
In JavaScript objects are collections of properties i.e. key identified values. Values can be all JavaScript primitive and structural types.
Each property in an object is described by its attributes regarding whether a given property can be retrieved, changed, deleted or enumerated on. The attributes can be set using Object.defineProperty().
Properties can be categorised in two categories: 1) data properties, and 2) accessor properties. Accessor properties can be associated with get and/or set functions that are used respectively to retrieve and store values.
String & Symbol Keyed Objects
What is commonly referred to as an object in JavaScript is a string or a symbol keyed object. Such objects are most often created object literal syntax.
const myObj = {
a: 1,
b: 2
}
console.log(myObj) // {a: 1, b: 2}
myObj.c = 3
console.log(myObj) // {a: 1, b: 2, c: 3}
myObj.a = 4
console.log(myObj) // {a: 4, b: 2, c: 3}Mutability of Objects
Objects in JavaScript are mutable. It is possible to change them through adding additional properties and/or changing the existing ones.
Arrays: Integer Keyed Objects
Arrays in JavaScript are special kinds of integer keyed objects inheriting from Array.prototype. Arrays are most commonly declared and defined using the literal syntax [...].
const myArr = ['a', 'b', 'c']
myArr.push('d')
console.log(myArr) // ['a', 'b', 'c', 'd']
myArr[0] // => "a"
myArr[1] // => "b"
myArr[myArr.length - 1] // => "d"Operations on Arrays
There are many built-in capabilities of arrays inherited from Array.prototype: forEach, push, pop, shift, unshift, splice, slice, join, concat, indexOf, map, reduce, reverse, values, toString, .
Sets & WeakSets: Collections of Unique Values
Set and WeakSet are collection of unique values. The difference between Set and WeakSet is that 1) the Set type object can include both primitives and object references as values and the WeakSet type can include only references to objects, 2) object referenced values in WeakSet type object are held weakly which impacts garbage collection and do not allow for enumerability.
The most notable instance methods of Set and WeakSet are: add, delete, and has.
Maps & WeakMaps: Objects as Keys
The objects of Map and WeakMap types are ordered collections of key-value properties in which objects can be keys (as opposed to string and symbol keyed objects). The difference between Map and WeakMap is that 1) keys in the Map type objects might be primitives and objects and in WeakMap only objects, 2) in object of WeakMap type keys (as well as values) are weakly referenced and therefore are not enumerable.
Map and WeakMap can be used to enhance performance to allow for easier iteration.
The most notable instance methods of Map and WeakMap are: get, set, delete, and has.
Spread: Shallow Cloning & Appending
Spread is syntactically denoted by three dots ... and it allows 1) an iterable object such as an array or a string to be spread (shallowly cloned and appended) a) as a function arguments in argument list where the function expects zero or more arguments, or b) as a part of another iterable object, or 2) (ES2018) an object with enumerable properties to be spread (shallowly cloned and appended) as a part of another object.
function multiply(a, b, c) {
return a * b * c
}
const numbers = [5,10,15,20]
multiply(...numbers) // => 750
// Compare with Function.prototype.apply().
const newNumbers = [2, 4, 6]
const combinedNumbers = [ 42, ...numbers, ...newNumbers, 43]
combinedNumbers // => [42, 5, 10, 15, 2, 4, 6, 43]
// Compare with Array.prototype.concat().
// ES2018
const anObj = { a: 1 }
const anotherObj = { b: 2 }
const combinedObject = { ...anObj, ...anotherObj, c: 3 }
combinedObject // => {a: 1, b: 2, c:3} // Compare with Object.assign().Spread syntax can be useful at new operator given that apply can't be used.
Using a spread syntax with object literals shallowly (and not deeply) clones enumerable properties (excluding prototype) of the spread objects. The difference between spread and Object.assign() is that the latter - in addition to cloning - triggers setters and the spread does not.
Not all objects are iterable. As a rule, the spread syntax - with the exclusion of ES2018 spreading properties - can be applied to iterable objects only.
Rest: Variadic Functions
Syntactically rest parameter syntax is denoted - just like the spread syntax - with three dots ... and is a way of creating of variadic functions i.e. functions that accepts indefinite number of arguments as an instances of Array.
function multiply(...numbers) {
return numbers.reduce((p, c) => p * c)
}
multiply(1, 2, 3, 15, 20) // => 1800The rest parameter can be used in a given function parameters only once and at the end of the parameter list.
The difference between arguments and the rest parameters is that the arguments 1) is not an array but another kind of object with its own defined properties and, 2) the arguments includes all function arguments and rest parameter only those encompassed by it.
Object Destructuring
The object destructuring assignment in JavaScript is a syntactical way of drawing enumerable properties from objects (including arrays) and assigning them to variables.
let [a, b] = [1,2]
a // => 1
b // => 2
const [c,d] = [3, 4]
const [e, f, ...g] = [1, 2, 3, 4, 5] // Rest syntax
g // => [3, 4, 5]
const { a, b } = { a: 1, b: 2, c: 3 }
const { a, b, ...c } = { a: 1, b: 2, c: 3, d: 4, e: 5 } // ES2018
c // => {c: 3, d: 4, e: 5}
const { a, b, c } = [1, 2]
c // => undefined
const { a } = {}
a // => undefinedIt is possible to indicate a default value in the destructuring assignment.
const [a=2, b=1] = [3]
a // => 3
b // => 1
const { a = 10, b = 15 } = { a: 5 }
a // => 5
b // => 15It is possible to ignore some values during a destructuring assignment.
const [a, , b] = [41, 42, 43]
a // => 41
b // => 43It is possible to assign new property names during destructuring assignment.
const { a: x, b: y, c: z = 3 } = { a: 1, b: 2 }It is possible to use assigning new property names and default values at the same time.
It is possible to destructure nested properties.
const { foo: { bar } } = { foo: { bar: 1 } }
bar // => 1It is possible to use destructuring assignment with function parameters.
function logAAndB({ a, b }) {
console.log(a)
console.log(b)
}
logAAndB({ a: 1, b: 2 }) // 1 2It is possible to assign new property names and use default values during destructuring assignment of a function parameters.
Classes
What are classes in JavaScript?
Just like constructor functions classes in JavaScript are blueprints (or templates) for creating objects.
Classes in JavaScript are built upon prototype JavaScript functionality and are a type of functions.
Class can be initialized through a class declaration or a class expression. Once a class is declared or expressed it cannot be re-declared or re-expressed.
Class Declarations
A class declaration is signalled using the class keyword.
class Animal {
}Class declarations - as opposed to function declarations - are not hoisted. Trying to access a class before its declaration results in a RerefenceError.
Class Expressions
Class expressions - just like function expressions - can be unnamed or named.
const Animal = class {
}
Animal // => class {}
Animal.name // => 'Animal'
const Person = class Human {
}
Person // => class Human {}
Human // Uncaught ReferenceError: Human is not defined
Person.name // => 'Human'Strict Mode
The body of a class is always executed in strict mode - irrespective of whether use strict directive is present or not.
Constructor & Initialization
A class can include one (and only one) constructor method within body. In the constructor method properties of its particular instances can be assigned.
Instances are initialized using the new keyword.
class Animal {
constructor(species, name) {
this.species = species
this.name = name
}
}
const geraltsHorse = new Animal('horse', 'Roach')
geraltsHorse // => Animal {species: 'horse', name: 'Roach'}
const cirisHorse = new Animal('horse', 'Kelpie')
cirisHorse // => Animal {species: 'horse', name: 'Kelpie'}Extending Classes
One of the powerful features of classes is their capability of being extended. An extended class (subclass) inherits features of the class it extends (superclass).
class Animal {
constructor(species, name) {
this.species = species
this.name = name
}
presentMe() {
console.log(`I am a ${this.species} and my name is ${this.name}.`)
}
}
class Horse extends Animal {
}
const napoleonsHorse = new Horse('horse', 'Marengo')
napoleonsHorse.presentMe() // => "I am a horse and my name is Marengo."Although, the method presentMe() was not defined on the Horse class it is available to instances of the Horse class as the class Horse extends features of the class Animal.
Super
When a subclass defines a property that is already defined on its superclass it overwrites it. However, it is still possible to refer to the superclass property with the super keyword.
If a constructor function is present in a subclass then to use this the keyword super needs to be invoked first.
class Animal {
constructor(species, name) {
this.species = species
this.name = name
}
presentMe() {
console.log(`I am a ${this.species} and my name is ${this.name}.`)
}
}
class Horse extends Animal {
constructor(name) {
super('horse')
this.name = name
}
}
const alexandersHorse = new Horse('Bucephalus')
alexandersHorse // => Horse {species: 'horse', name: 'Bucephalus'}
alexandersHorse.presentMe() // => "I am a horse and my name is Bucephalus."Single Superclass
In JavaScript there is no multiple inheritance. A class can extend only one class.
Extending Classes with Constructor Functions
It is possible in JavaScript to extend classes with constructor functions. However, it is not possible to extend classes with objects that are not constructor functions.
Instance Methods
Functions defined without a static keyword within a class definition body are assigned as method properties to instances of the class.
class Teacher {
grade() {
return 'You get an A!'
}
}
const profSnape = new Teacher()
profSnape.grade() // => 'You get an A!'
const profHand = new Teacher // Parenthesis are optional with no arguments.
profHand.grade() // => 'You get an A!'
Teacher.grade() // Uncaught TypeError: Teacher.grade is not a functionStatic Methods
Functions defined with static keyword within a class definition body become static (aka class) methods that can be invoked directly from the class object but not from its intances.
class Teacher {
static grade() {
return 'You get an A!'
}
}
const profSnape = new Teacher()
profSnape.grade() // Uncaught TypeError: profSnape.grade is not a function
Teacher.grade() // 'You get an A!'Private Fields
Both instance methods and static methods - as well as all other non-function properties - are public which means that they are accessible from outside of the class definition body. To make them private the sigil # can be used.
class Person {
#name // A private field needs to be declared first.
constructor(occupation, name) {
this.occupation = occupation
this.#name = name
}
}
const aKnight = new Person('knight', 'Don Kichot')
aKnight.occupation // 'knight'
aKnight.name // undefinedComparing Values
Comparing Primitives without Casting aka Strict Comparison
The operators === and !== are used to compare two operands without casting their data types. In other words, when operands are of different data types === always returns false and !== always returns true. When operands have the same primitive data types and the same values === returns true and !== returns false.
42 === '42' // => false
42 !== '42' // => true
42 === 42 // true
42 !== 42 // false
0 === -0 // => true
null === undefined // => falseStrict comparison operator is used by Array.prototype.indexOf.
Comparing Primitives with Casting aka Abstract Comparison
When two operands are compared using == and != JavaScript will first try to type cast them to match if necessary and only then compare their values.
42 == 42 // => true
42 == '42' // => true
42 != '42' // => false
1 == true // => true
2 == true // => false
0 == false // => true
0 == undefined // => false
NaN == NaN // => false
0 == null // => false
0 == -0 // => true
null == undefined // => trueComparing Objects
Both strict and abstract comparisons are primarily used to compare primitives. Comparing two different objects - even with the exact same structure - returns false irrespective whether the comparison is effected with === or ==.
{} === {} // => false
{} == {} // => false
a = { a: 1 }
b = { a: 1 }
a == b // => falseOther Comparison Algorithms
In addition to === and == there are two other comparison algorithms in ES2015: SameValueZero and SameValue.
Conditional Execution
Truthy & Falsy
Conditional execution in JavaScript is dependent on truthy and falsy values. All expressions in JavaScript are truthy unless they are one of the following: 1) false, 2) null, 3) undefined, 4) an empty string, 5) the number zero or 6) NaN.
if
if statement allows for execution of a given block of code only under a specified JavaScript expression is of a truthy value.
if (2 + 2 === 4) {
console.log('2 and 2 does equal 4.')
} // "2 and 2 does equal 4."if ... else if
if statement can be extended with else if. The block of code after the else if is executed only when the condition following the if is falsy and the condition following the else if is truthy.
const anArr = [1,2,3]
if (anArr.length === 2) {
console.log('The array has two items.')
} else if (anArr.length === 3) {
console.log('The array has threee items.')
} // "The array has threee items."if ... else
if statement can be extended with else. else is not followed by any condition and the block of code after the else is executed when no condition in if ... else (or if ... else if ... else) statement is truthy.
if (2 + 2 === 5) {
console.log('2 + 2 does equal 5.')
} else {
console.log('2 + 2 does not equal 5.')
} // "2 + 2 does not equal 5.."if ... else if ... else
if statement can be extended with multiple else if and combined with else simultaneously.
switch
Although the statements are not tantamount, in some circumstances in place of if ... else if ... else statement a switch statement can be used. Notice a break keyword is the switch statement.
const otherArr = [1,2,3,4,5]
if (otherArr.length === 2) {
console.log('The array has two items.')
} else if (otherArr.length === 3) {
console.log('The array has threee items.')
} else {
console.log("I don't know how many items the array has.")
} // "I don't know how many items the array has."
switch(otherArr.length) {
case 2:
console.log('The array has two items.')
break
case 3:
console.log('The array has threee items.')
break
default:
console.log("I don't know how many items the array has.")
} // "I don't know how many items the array has.A switch statement checks whether a given expression matches provided cases.
Once a case is matched switch statement executes all statements following the matched case unless a break (or return) keyword is met.
switch uses strict comparison (===) to evaluate whether a given case matches.
Ternary Operator
The ternary operator checks whether a given condition is truthy and depending on that executes and returns the evaluated value of one of two provided expression.
const theAnswer = 2 + 2 === 4 ? '2 + 2 equals 4.' : '2 + 2 does not equal 4.'
console.log(theAnswer) // '2 + 2 does not equal 4.'Looping
For
The most popular way of iterating in JavaScript is using the for statement.
for (let i = 0; i < 10; i++) {
console.log(i)
} // 0,1,2,3,4,5,6,7,8,9 => undefinedFor In
The for...in statement is used to iterate in an arbitrary order over enumerable properties of an object keyed by strings only (therefore not by symbols).
const o = { a: 1, b: 2 }
for (const k in o) {
console.log(k)
console.log(o[k])
} // "a", 2, "b", 2 => undefinedFor Of
The for...in iterator iterates over property names but the for..of iterator iterates over property values..
const o = { a: 1, b: 2 }
for (const v of o) {
console.log(v)
} // 1, 2 => undefinedWhile
The while loop runs as long as a specified condition is of a truthy value.
let n = 0
while (n < 10) {
console.log(n)
n++
} // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 => 9Do While
The do...while statement repeats until a specified condition is falsy.
let n = 0
do {
console.log(n)
n++
} while (n < 10) // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 => 9The difference of the do...while loop to the while loop is that the code block will run at least once with do...while even when the provided condition is falsy.
Break
The break statement can be used with for iterators, loops and, as already noted, with switch. Using the break statement stops the relevant iteration or loop.
Continue
While the break statement stops a given iteration or a loop as a whole the continue statement omits only a given part of the iteration process and continues with the following parts.
Labeled Loops
JavaScript allows for labeled loops that identify them and make them referable in other parts of the code. The break and continue statements can be used with labeled loops to target them by their name. This can be used for example in nested loops.
let n = 0
myLoop: while (n < 3) {
console.log(n)
n++
}Exception Handling
What Are Exceptions in JavaScript?
An exception in JavaScript is a disruption of the runtime of a program that - if uncaught - halts that runtime.
ECMAScript & Runtime Environments Exceptions
In JavaScript any primitive and/or object can be used to denote an exception but there are also built-in object constructors to facilitate that. Some of them are declared by the language itself (an ECMAScript exception) or by runtime environments such as browsers (a DOMException). ECMAScript exceptions include such exception constructors as Error, ReferenceError, SyntaxError or TypeError. DOM include such exception constructors as NetworkError, TimeoutError or NotAllowedError. All those exception constructors have message and name properties defined.
foo // Uncaught ReferenceError: foo is not definedDeliberately Throwing Exceptions
It is possible to deliberately throw an exception in code using the throw keyword.
throw new Error() // Uncaught Error
throw 42 // Uncaught 42
throw '42' // Uncaught 42Creating Custom Exception Objects
As already noted, any primitive, object or function can be used to denote an exception. Further, it is possible to declare a custom exception object constructor.
function MyCustomException(message) {
this.name = 'MyCustomException'
this.message = message
}
throw new MyCustomException('Something went wrong') // Uncaught MyCustomException {name: "MyCustomException", message: "Something went wrong"}It is possible to define a property toString to make the exception be pretty printed by various tools - MyCustomException.prototype.toString
Catching Exceptions
Both - built in exceptions and user created exceptions - can be caught and handled which can prevent a program from stopping. An exception in JavaScript is caught using the try...catch statement.
try {
foo
} catch (e) {
console.log(e.name)
} // ReferenceError => undefinedThe try...catch statement can be extended with finally. The code in the finally block is executed irrespective of whether an exception was thrown, caught or even handled.
try {
foo
} catch (e) {
console.log(e.name)
} finally {
console.log('Finally!')
} // ReferenceError Finally!Handling Specific Errors
It is possible to handle specific exceptions in the catch block in specific ways. instanceof keyword can be handy at that.
try {
foo
} catch (e) {
if (e instanceof EvalError) {
console.log('Not another eval error!')
} else {
console.log('Phew! Not an eval error.')
}
} // Phew! Not an eval error.Logging Exceptions
When logging exceptions it is advised to use console.error instead of console.log.
This
This & Its Contexts
In JavaScript this keyword is a reference to an object in the context of which JavaScript statements and expressions are being interpreted at a given moment. this can be invoked at all times however - in some circumstances - its value may be undefined.
As noted, the value referenced by this depends on the execution context. The following contexts are relevant for the value referenced by this:
- Global Context
- Non-strict-mode Non-arrow Global Function Context
- Strict-mode Non-arrow Global Function Context
- Non-arrow Non-global Method Function Context
- Constructor Context
- Class Context
- DOM Event Handler Context
- Bound Context
- Arrow Function Context.
Global Context, Non-strict-mode Non-arrow Global Function Context, and Strict-mode Non-arrow Global Function Context can be jointly referred to as the Default Context. Non-arrow Non-global Method Function Context, Constructor Context, Class Context, and DOM Event Handler Context can be jointly referred to as the Implicit Context. Bound Context can be referred to as the Explicit Context. And, Arrow Function Context can be referred to as the Lexical Context.
Global Context
In the global context this refers to the global object.
console.log(this) // Window {window: Window, self: Window ...
// Functions declared in the global context and variables declared with 'var' in the global context become properties of the global object.
var theNumber = 42
console.log(this.theNumber) // 42Non-strict-mode Non-arrow Global Function Context
When the strict mode is not enabled and a function is called as a global function then the value referenced by this is the global object.
function logThis() {
console.log(this)
}
logThis() // WindowStrict-mode Non-arrow Global Function Context
When the strict mode is enabled and a function is called as a global function then the value referenced by this is undefined.
function logThis() {
'use strict'
console.log(this)
}
logThis() // undefinedNon-arrow Non-global Method Function Context
When a non arrow function is called as a non-global object's method then the value referenced by this within that function body is that object.
const anObj = {
foo: 42,
bar: function() { console.log(this) }
}
anObj.bar() // {foo: 42, bar: ƒ}Constructor Context
When a function is called with new keyword this becomes a reference to the object that is being created as a result of that call.
function Horse(name) {
this.name = name
console.log(this.name)
}
const geraltsHorse = new Horse('Roach') // Roach
const cirisHorse = new Horse('Kelpie') // KelpieClass Context
In the class context this refers to an object being a specific instance of a class.
class Animal {
constructor(name) {
this.name = name
console.log(this)
}
}
new Animal('Roach') // Animal {name: "Roach"}
new Animal('Kelpie') // Animal {name: "Kelpie"}Derived classes instances (i.e. instances of classes that extend other classes) do not have the initial this binding. The super() idiom needs to be used to be able to reference this in derived classes instances.
DOM Event Handler Context
Within a DOM event handler this references the HTML element that is the receiver of the event.
<button onclick="function() { console.log(this) }">
</button>Bound (Explicit) Context
It is possible to bind a specific this reference within a given function. There are three ways to achieve this: apply, call and bind. apply and call - in addition to binding a specific value to this reference - call the function. bind only binds the this reference to a specific value but does not call the function and instead returns the function object itself.
Arrow Function (Lexical) Context
this within the body of an arrow function retains the reference to the value of the enclosing lexical context irrespective of whether the function is a non-global method or not.
const anObj = {
foo: 42,
bar: () => { console.log(this) }
}
anObj.bar() // Windowclass Animal {
constructor(name) {
this.name = name
}
logName = () => console.log(this.name)
}
const samsPony = new Animal('Bill')
samsPony.logName() // BillContext Precedence
The value referenced by this is set observing the following context precedence: 1) Constructor Context, 2) Bound Context, 3) Non-arrow Method Context, 4) Class Context, 5) Non-arrow Global Function Context, and 6) Global Context.
Promises & Asynchronicity
Intro
Promise() is built into JavaScript since ES6.
Promise() constructor function is a built-in JavaScript object an instance of which represents a promise of returning a value. Such a promise is pending until it is settled through being resolved (aka fulfilled) or rejected.
Creating a Promise
A promise in JavaScript is created through using of Promise() constructor function. The constructor accepts two callback functions. The first one is to be executed if the code instructions underpinning the promise succeed and the second one if they fail. The first callback function's argument is the value which is returned by the promise if it resolves (aka fulfils). The second callback function's argument is the error that shall be thrown by the promise if the promise is rejected.
const twoAndTwoPromise = new Promise(function(resolve, reject) {
const sum = 2 + 2
if (sum === 4) {
resolve('2 and 2 does equal 4')
} else {
reject(Error('2 and 2 does not equal 4!'))
}
})The above promise is always fulfilled as 2 and 2 always equals 4 (in JavaScript). However, when a more elaborate instructions are provided - such as connecting with external resources - they might as well fail for many reasons such as unexpected response status or a network error. Then the reject callback can be used to reject the whole promise.
Asynchronicity of a Promise
When a XMLHttpRequest() request is being made within a promise the promise does not block other instructions from outside its body to be executed when the request is pending. Instead, once the request is completed (successfully or not) only then the resolve or reject callback functions are being executed.
const getUrl = function(url) {
return new Promise(function(resolve, reject) {
const request = new XMLHttpRequest()
request.open('GET', url)
request.onload = function() {
if (request.status === 200) {
resolve(request.response)
} else {
reject(Error(`Unable to get ${url}!`))
}
}
request.onerror = function() {
reject(Error('Network error!'))
}
request.send()
})
}
console.log(getUrl('https://soundof.it/edit-page/javascript-tutorial'))
console.log('After promise!')
// Promise {<pending>}
// After promise!The promise is in a state of pending during the time after the its execution but before its fulfilment or rejection.
Using Values Returned by Promises
Then
As already noted, once a promise is executed the directly subsequent code from outside its body is being executed before the promise is fulfilled or rejected.
const promiseValue = getUrl('https://www.codelumi.com')
console.log(promiseValue)
console.log('Here!')
// Promise {<pending>}
// Here!To use a value returned by a promise upon its fulfilment the Promise.prototype.then() needs to be appended to the promise.
getUrl('https://www.codelumi.com') // getUrl() returns a promise.
.then(value => console.log(value))
console.log('Here!')
// Here!
// <!doctype html> ...Promise.prototype.then() accepts two callback functions as arguments. The first callback is called when the promise is fulfilled and the second when the promise is rejected.
getUrl('https://www').then(value => console.log(value), error => console.log(error))
console.log('Here!')
// Here!
// Error: Network error! ...
// => GET https://www/ net::ERR_NAME_NOT_RESOLVEDCatch
Instead of using the second callback of Promise.prototype.then() to reach the promise error the error can also be caught using Promise.prototype.catch() which accepts only one callback function dedicated to the error handling.
getUrl('https://www').catch(error => console.log(error))
console.log('Here!')
// Here!
// Error: Network error! ...
// => GET https://www/ net::ERR_NAME_NOT_RESOLVEDFinally
There is also Promise.prototype.finally() which is called irrespective of whether the promise was resolved or rejected.
getUrl('https://www')
.catch(error => console.log(error))
.finally(() => console.log('It\'s finally over!'))
console.log('Here!')
// Here!
// Error: Network error! ...
// It's finally over!
// => GET https://www/ net::ERR_NAME_NOT_RESOLVEDPromise Composition aka Promise Chaining
If one of the callback handler functions provided to Promise.prototype.then() or Promise.prototype.catch():
- returns a value - then
then()returns a promise that is to be fulfilled with that value, - returns a pending promise - then
then()returns the promise, - throws an error - then
then()returns a rejected promise with the error of the rejected promise.
As then() and catch() return promises themselves they can be chained with subsequent then()s and/or catch()es.
getUrl('https://soundof.it/javascript-tutorial')
.then(value => {
console.log(value)
return getUrl('https://soundof.it/ruby-tutorial')
})
.then(value => {
console.log(value)
})
// => Promise {<pending>}
// <!doctype html> ... <title>JavaScript Tutorial</title> ...
// <!doctype html> ... <title>Ruby Tutorial</title> ...Promise() Static Methods
Promise() constructor function object comes with many built-in static methods, such as:
resolve(value)- returns a promise object that is to be resolved with the provided value,reject(error)- returns a promise object that is to be rejected with the provided error,all(promises)- tries to resolve all promises,any(promises)- tries to resolve at least one promise from the promises,allSettled(promises)- settles all promises, irrespective of whether they resolve or reject,race(promises)- waits for the settlement of the first promise.
Async / Await
async and await functionality was introduced by ECMAScript 2017.
When a promise is appended with then() the code within one of the then() callback functions will always be executed after the code following directly the promise chain.
getUrl('https://soundof.it/javascript-tutorial')
.then(value => {
console.log(value)
})
console.log('Outside of the promise!')
// Outside of the promise chain!
// <!doctype html> ...async and await keywords allow for asynchronous promise-based behaviour to be written in a way mimicking a synchronous code.
async function getUrlAndLogIt(url) {
console.log(await getUrl(url))
console.log('Outside of the promise!')
}
console.log('Outside of the async function getUrlAndLogIt!')
getUrlAndLogIt('https://soundof.it/javascript-tutorial')
// Outside of the async function getUrlAndLogIt!
// <!doctype html> ...
// Outside of the promise!The code following the await keyword within an async function is not executed until the promise prepended with the await keyword is settled.
An async function implicitly returns a promise that is resolved with the value explicitly returned by the async function unless rejected due to an uncaught exception.
Modules
Intro
During the course of JavaScript evolution, many ways of working with modules were developed. Currently the most notable are:
- CommonJS (CJS) format used in Node.js (
module.exports/require), and - ECMAScript modules (ESM) - ES6 (ES2015) native module format (
export/import).
Another library worth mentioning is Asynchronous Module Definition (AMD). Both CJS and AMD are supported by Webpack however version 2 of Webpack supports ESM natively and recommends it over CJS and AMD. Other known module formats are System.register and Universal Module Definition (UMD).
Only CommonJS and ECMAScript modules shall be explained herein.
CommonJS
CommonJS modules are loaded synchronously.
CommonJS makes each file to have its unique module context.
To export a default value from a module use module.exports = foo.
// ./horses.js
const horses = ['Roach', 'Kelpie', 'Bucephalus']
module.exports = horses
// Alternatively: "module.exports = ['Roach', 'Kelpie', 'Bucephalus']".
// ./riders.js
const horses = require('./horses')
horses // => (3) ['Roach', 'Kelpie', 'Bucephalus']
typeof horses // => objectTo export multiple values simultaneously use exports.foo = bar.
// ./horses.js
const geraltsHorse = 'Roach'
const cirisHorse = 'Kelpie'
exports.geraltsHorse = geraltsHorse
exports.cirisHorse = cirisHorse
exports.alexandersHorse = 'Bucephalus'
// ./riders.js
const horses = require('./horses.js')
horses // => (3) ['Roach', 'Kelpie', 'Bucephalus']
typeof horses // => object'The values exported with the statement exports are being added to module.exports property.
ESM: ES6 Module Format
Although ES6 module format is vastly used it is not yet supported by all browsers therefore using a transpiler might be needed to transpile it to a more generally supported format such as CJS or AMD.
With ES6 it is possible to export top level functions, variables, and classes.
Modules exported and imported with ES6 export statement are always in strict mode.
Default Export & Import
A file can include one ES6 default export.
// ./horses.js
const horses = ['Roach', 'Kelpie', 'Bucephalus']
export default horses
// Alternatively: export default ['Roach', 'Kelpie', 'Bucephalus']
// ./riders.js
import horses from './riders.js'
horses // => (3) ['Roach', 'Kelpie', 'Bucephalus']
typeof horses // => objectNamed Exports
A file can include multiple ES6 named exports.
// .horses.js
export const roach = 'Roach'
export const kelpie = 'Kelpie'
export const bucephalus = 'Bucephalus'
// .riders.js
import { roach, kelpie, bucephalus } from './horses.js'
console.log(roach, kelpie, bucephalus) // Roach Kelpie Bucephalus
import * as horses from './horses.js'
console.log(horses) // Module {__esModule: true, Symbol(Symbol.toStringTag): 'Module'}
console.log(horses.roach) // RoachDynamic Import
It is possible to load modules asynchronously with the import statement. Such an operation relies on promises.
Aggregating
It is possible import and export modules simultaneously to aggregate exports in a single module.
Module Loaders & Bundlers
A module loader is a program that loads and interprets modules at runtime. Examples: Require.js, SystemJS.
A module bundler bundles modules into a single module at build time. Examples: Browserify, Webpack.
Regular Expressions
What are Regular Expressions?
In JavaScript, a regular expression object
- is an instance of
RegExp()constructor function, and - represents a sequence of characters constituting a pattern that can be used to match a sequence or multiple sequences of characters in strings.
RegExp Constructor
In JavaScript to initialize a regular expression object a literal syntax (//) or a constructor initialization syntax (new) can be used .
const foo = /oach/
'Roach'.match(foo) // => ['oach', index: 1, input: 'Roach', groups: undefined]
'Kelpie'.match(foo) // => null
const bar = new RegExp('ike')
'Mike'.match(bar) // => ['ike', index: 1, input: 'Mike', groups: undefined]
'Leo'.match(bar) // => null
foo.constructor // => ƒ RegExp() { [native code] }
bar.constructor // => ƒ RegExp() { [native code] }Regular expressions initialized with the literal syntax are compiled at script load but regular expressions initialized with the constructor syntax are compiled at runtime.
Regular Expression Pattern Syntax
Regular expression patterns include:
- simple alphanumeric characters - matched outright (e.g.
michael), - special characters - allowing for matching a group of simple characters (e.g. digits
\d, alphanumerics\w, spaces\s), a combination of characters (e.g. matching one or more ofaa+), etc.
Special characters can be escaped with \ to be used outright.
Special Characters
Special characters are used for:
- matching any characters using the dot character (
.), - matching types of characters - digits (
\d), non-digits (\D), alphanumerics\w, non-alphanumerics (\W), spaces\s, non-spaces (\S), etc., - indicating specific match locations - e.g. the beginning of a string (
^), the end of a string ($), before a specific pattern (foo(?=bar)), etc. - matching logical disjunctions - either
fooorbaror both (foo|bar) , any ofa,borc([abc]or[a-c]) - matching logical negation - e.g. any character but
a([^a]) - matching groups of characters using
(), - quantifying the number of characters that comply with the match - zero or more (
*), one or more (+), zero or one (?), n times (a{n}), at least n times (a{n, }), between n and m times (a{n, m})
RegExp() Methods
The most notable RegExp() instance methods are:
RegExp.prototype.exec()- returns an array of data on match ornullotherwise, andRegExp.prototype.test()which returnstrueon match orfalseotherwise.
const myRegex = /abc/
myRegex.exec('abc') // => ['abc', index: 0, input: 'abc', groups: undefined]
myRegex.exec('123') // => null
myRegex.test('abc') // => true
myRegex.test('123') // => falseString() Regex Methods
The most notable String() instance methods are:
String.prototype.match()- returns an array of data with capturing groups on match andnullotherwise,String.prototype.search()- returns index of a match or-1otherwise,String.prototype.replace()- replaces the matched pattern with a provided string,String.prototype.split()- splits a provided string in place or places of the pattern match.
const myRegex = /abc/
const foo = 'xyz.abc.xyz.abc.xyz.abc'
foo.match(myRegex) // => ['abc', index: 4, input: 'xyz.abc.xyz.abc.xyz.abc', groups: undefined]
foo.search(myRegex) // => 4
foo.replace(myRegex, '123') // => 'xyz.123.xyz.abc.xyz.abc'
foo.split(myRegex) // => (4) ['xyz.', '.xyz.', '.xyz.', '']TODO
Flags
Regexes can be appended with flags that can affect the behaviour of their patterns.
Flags are attached at the initialization of a regex.
const myRegexA = /foo/flags
const myRegexB = new RegExp(bar, flags)Global Search
One of the most commonly used flags - often used with String.prototype.replace() - is g which stands for global search.
const nonGRegex = /abc/
const gRegex = /abc/g
const foo = 'abc.abc.abc'
foo.replace(nonGRegex, '123') // => '123.abc.abc'
foo.replace(gRegex, '123') // => '123.123.123'Case Insensitive Search
Another commonly used flag is i which stands for case-insensitive search.
const nonIRegex = /abc/
const iRegex = /abc/i
const foo = 'ABC'
foo.match(nonIRegex) // => null
foo.match(iRegex) // => ['ABC', index: 0, input: 'ABC', groups: undefined]Multi-line Search
To perform a multi-line search use the m flag.
Combining Flags
Multiple flags can be used simultaneously.
const multiRegex = /abc/gi
const foo = 'ABC.ABC.ABC'
foo.replace(multiRegex, '123') // => '123.123.123'Prototypal Inheritance
What is prototypal inheritance in JavaScript?
JavaScript is a prototype-based language which means that each non-null object in JavaScript has a private property __proto__ (aka [[Prototype]] property) which references its prototype. This prototypal inheritance results in the following:
- When a property of an object is accessed the lookup process starts. First, it checks whether the accessed property is the own property of that object (i.e. whether it is defined directly on that object). If it is not, it looks for that property at its prototype, then at the prototype of its prototype, etc. If such a prototype chain traversal is unsuccessful then
undefinedis returned. - At iteration, every enumerable own property of an object and every enumerable property on that object's prototype chain is enumerated.
const anObj = { a: 1 }
const anObj2 = Object.create(anObj) // Creates an object with anObj as the prototype.
anObj2 // => {}
anObj2.a // => 1
for (const prop in anObj2) { console.log(prop) } // a
const anArr = [1]
const anArr2 = Object.create(anArr) // Creates an object with anArr as the prototype.
anArr2 // => []
anArr2[0] // => 1
anArr2.map(el => el) // => [1]Object.getPrototypeOf() can be used to get an object's prototype alternatively to accessing __proto__ property.
Own Properties
When a property is defined directly on a given object it is called its own property. To check whether a given property is an own property of an object Object.prototype.hasOwnProperty() can be used.
Prototype Chain
As already noted, all objects in JavaScript have their prototypes referenced by the __proto__ property. __proto__ references create prototypal chain. At the end of all prototypal chains is null.
Prototype Inherited Properties
Non-own properties defined on an object's prototype are accessible to all objects prototyped with that prototype. However, properties set directly on an object have precedence to the properties inherited from prototypal chain.
function Person() {}
const personA = new Person()
const personB = new Person()
personA.surname = 'Doe'
personA.surname // => 'Doe'
personB.surname // => undefined
personA.__proto__.surname = 'Smith'
personA.surname // => 'Doe'
personB.surname // => 'Smith'Traversing of a prototypal chain of a given object to look for properties may have negative consequences for performance. Further, when no property exists at the object and down its prototypal chain the full traversal needs to be effected to return undefined.
Prototyping
There are several ways of prototyping in JavaScript including:
- constructor functions,
Object.create(),Object.setPrototypeOf(), and- manually referencing a prototype in the
__proto__property (considered deprecated).
Constructor Functions
A constructor function (not to be mislead with the Function() constructor) is a function invoked with the new keyword that can serve as a reusable & parameterizable blueprint for creating new objects.
A constructor function uses the keyword this to parametrize objects created with it.
Conventionally a constructor function:
- should begin with a capital letter, and
- should not return a value explicitly.
function Animal(species, name) {
this.species = species
this.name = name
}Once a constructor function is declared we can create objects using it as a parameterizable blueprint with the keyword new.
function Animal(species, name) {
this.species = species
this.name = name
}
const geraltsHorse = new Animal('horse', 'Roach')
console.log(geraltsHorse) // Animal {species: 'horse', name: 'Roach'}
const cirisHorse = new Animal('horse', 'Kelpie')
console.log(cirisHorse) // Animal {species: 'horse', name: 'Kelpie'}The steps at creating an object with a constructor function are as follows.
- A new plain object is being created.
- The property
__proto__is being added to that newly created object which establishes a reference between that newly created object and the constructor function'sprototypeproperty. - As the keyword
thisin the constructor function's body refers to the newly created object the properties are being added to it based on the constructor function's parameters. - If the constructor function does not return any value explicitly it returns implicitly the newly created object.
Constructor Function Prototype Property
All objects created with the same constructor function share the same prototype function which is the constructor function's property called prototype. The prototype function is assigned to all objects created with that constructor function and referenced by them using the __proto__ property.
Further, all objects created with a constructor function have a property called constructor which references that constructor function itself.
function Person(name) {
this.name = name
}
const student = new Person('Michael')
Person.prototype === student.__proto__ // => true
Person === student.constructor // => true
student.__proto__ === student.constructor.prototype // trueThe constructor function and its prototype property are shared among all objects created with that constructor function.
function Person(name) {
this.name = name
}
const studentA = new Person('Michael')
const studentB = new Person('Leo')
studentA === studentB // => false
studentA.constructor === studentB.constructor // => true
studentA.__proto__ === studentB.__proto__ // => true
studentA.__proto__ === studentA.constructor.prototype // => trueAll functions but arrow functions in JavaScript have the prototype property.
JavaScript Built-in Constructor Functions
JavaScript has many built in constructor functions including: Function(), Object(), Array(), String(), Number(), Boolean(), RegExp(), and Date(). Note that Math() is not a constructor function.
Strings, numbers and booleans created with constructor functions are not primitives. Generally, primitive alternatives created with literal syntax should be used as simply being faster.
Non-array objects initialized with literal syntax have Object set as their constructors. Array objects initialized with literal syntax have Array set as their constructors.
const anObj = {}
anObj.constructor // => ƒ Object() { [native code] }
const anArr = []
anArr.constructor // => ƒ Array() { [native code] }Constructor Function Change
It is possible to effectively change a reference to a constructor function but only for objects and not primitives. Changing of a constructor function's reference does not influence instanceof operator.
Creating New Objects with Object's Create Method
Since ES5 it is possible to create a new object with Object.create() method providing it with the an existing object or null as the prototype. The second parameter of Object.create() can be provided to specify property descriptors. Similar effects can be achieved with Object.defineProperties() method.
Setting Prototype
It is possible to change an object prototype using Object.setPrototypeOf().
Time & Date
ECMAScript Epoch: Milliseconds Since 1st January 1970 UTC
An instance of the JavaScript Date() constructor function primarily represents the ECMAScript Epoch i.e. a number of milliseconds that have elapsed since the 1st January UTC. Adversely, the UNIX Epoch represents the number seconds that have elapsed since 1st January 1970 UTC.
Coordinated Universal Time (UTC) & Local Time
As noted, instances of the ECMAScript Epoch are calculated on the basis of the Coordinated Universal Time but a specific client (host) system might be within a time zone with the local time differing from the UTC. Instances of Date() come with methods that allow for getting and setting both local time and UTC.
Date() Object Initialization
To get a new Date() object representing the current local (host) time use new Date().
new Date() // => Tue Sep 28 2021 08:40:08 GMT+0200 (Central European Summer Time)To get a new Date() object on the basis of a specified local time use new Date(year, month, day, hour, minute, second, millisecond). Month is zero indexed but a day of the month is not.
new Date(1984, 0, 24, 5, 0, 0, 0) // => Tue Jan 24 1984 05:00:00 GMT+0100 (Central European Standard Time)To get a new Date() object on the basis of a specified UTC time use new Date(Date.UTC(year, month, day, hour, minute, second)).
const macLaunchDate = new Date(Date.UTC(1984, 0, 24, 5, 0, 0)) // => Tue Jan 24 1984 06:00:00 GMT+0100 (Central European Standard Time)
macLaunchDate.toUTCString() // => Tue, 24 Jan 1984 05:00:00 GMTInstance Methods
Instances JavaScript Date() have many getter built-in methods such as:
getTime()- returns the JavaScript Epoch,getTimezoneOffset()- returns timezone offset between local time & UTC,getFullYear()&getUTCFullYear(),getMonth()&getUTCMonth(),getDate()&getUTCDate()- date of a month,getDay()&getUTCDay()- day of a week,getHours() & getUTCHours(),getMinutes()&getUTCMinutes(),getSeconds()&getUTCSeconds(),getMilliseconds()&getUTCMilliseconds(), and- methods allowing for getting textual representations of JavaScript
Date()instances such astoString(),toDateString(),toISOString(),toLocaleDateString(),toUTCString(), etc.
Most of the getter methods have corresponding setter methods such as setDate(), setTime(), setFullYear(), setUTCFullYear(), etc.
Adding & Subtracting Dates
As each instance of the Date() object primarily represents a number of milliseconds since 1st January 1970 they can be - as numbers - added & subtracted.
const dateA = new Date(2021, 05, 30)
const dateB = new Date(2021, 05, 15)
const dateDiffNo = dateA - dateB // => 1296000000
const dateDiff = new Date(dateDiffNo)
dateDiff // => Fri Jan 16 1970 00:00:00 which is 15 days from the JavaScript Epoch and therefore the difference in days between dateA & dateBIt needs to be mentioned that deriving and presenting the actual passed time in days, months & years in the way described above can be burdened with errors as days, months & years can have different lengths.
Date.UTC() Static Method
The static method UTC() of Date() returns the JavaScript Epoch.
Strict Mode
What is Strict Mode?
Strict mode has been introduces in ES5.
Strict mode makes JavaScript to be interpreted with some differences to the generic mode - sometimes unofficially called the sloppy mode.
Strict mode is triggered through usage of use strict literal expression.
Not all browsers support strict mode.
ES5 Modules
Strict mode is automatically applied to ES5 modules.
ES6 Classes
Strict mode is automatically applied to ES6 classes.
Scripts & Functions
Strict mode can be applied manually to:
- a whole script, or
- a function.
To add the strict mode to the whole script add use strict literal expression before any statements and/or expressions.
'use strict'
const horse = 'Kelpie'
// ...To add the strict mode to a function add use strict literal expression before any statements and/or expressions in the function's body.
function doSomethingStrictly() {
'use strict'
const horse = 'Kelpie'
// ...
}Strict Mode Features
The primary features introduced by the strict mode are:
- additional runtime errors,
- additional syntax errors,
- enhancing security,
- inaccessibility of
caller,callee&arguments, - changed behaviour of
this, - better optimization.
Additional Runtime Errors
Strict mode disallows assigning a value to a not yet declared variable. Such an operation in sloppy mode is allowed and creates a new global variable through adding a new property to the global object.
'use strict'
foo = 42 // ReferenceError: foo is not definedFurther, in strict mode trying to delete an undeletable object property such as prototype throws TypeError.
Additional Syntax Errors
Strict mode aims at throwing errors instead of silently failing them as sloppy mode sometimes does.
The following will throw a SyntaxError:
- misusing of ES5 reserved keywords such as
private,protected,public,static,interfaceandyield(some of which serve as placeholders for now for future ES versions), - declaring identical function parameter names,
- employing the keywords
argumentsorevalas variable names or function argument names, - trying to delete variable names with
deletekeyword, - using
withstatement.
Enhancing Security
Strict mode does not allow eval to create a new variable in the scope that called it.
Inaccessibility of caller, callee & arguments
'caller', 'callee', and/or 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them. Such an operation throws TypeError.
Changed Behaviour of this
Please see LINK https://soundof.it/edit-page/javascript-tutorial#this.
Better Optimization
Some changes and restrictions introduced by the strict mode allows for better optimization of the JavaScript code.
One of such changes is that the arguments object in strict mode references a copy of named arguments and not the named arguments themselves.
JSON
What is JSON: JavaScript Object Notation?
JSON - abbreviation for JavaScript Object Notation - is a data interchange and file format that:
- is language independent although being based to some extent upon JavaScript object literal notation,
- is humanly readable,
- conveys data within the constraints of the JSON allowed value types,
- was first specified in the early 2000s but standardized in 2013,
- official MIME type is
application/json.
Value Types
There are the following values in JSON:
String- a sequence of zero or more characters delimited with double quotes (e.g."Roach"),Number- a signed double-precision float - written without quotes (e.g.42),Boolean-true/false,Array-[ ... ]- an ordered list of other allowed JSON types,Object-{ ... }- a collection (an object structure) of name-value pairs in which names are strings delimited with double quotes and types are allowed JSON types,null.
{
"geraltsHorse": "Roach",
"theAnswerToTheUniverse": 42,
"isFriday": true,
"birds": ["eagle", "stork", "hawk"],
"ridersAndHorses": {
"geraltsHorse": "Roach",
"cirisHorse": "Kelpie",
"alexandersHorse": "Bucephalus"
},
"availableTime": null
}Valid JSONs
A valid JSON is not only name-value pair collection contained within an object structure ({ ... }) but all other value types even when not enclosed within such a structure. Therefore a standalone array, a standalone string or any other standalone value type is a valid JSON.