JavaScript

Table of Contents

Syntax

function

function sayHi() {
  alert( "Hello" );
}

let sayHi = function() {
  alert( "Hello" );
};
let age = 16; // take 16 as an example

if (age < 18) {
  welcome();               // \   (runs)
                           //  |
  function welcome() {     //  |
    alert("Hello!");       //  |  Function Declaration is available
  }                        //  |  everywhere in the block where it's declared
                           //  |
  welcome();               // /   (runs)

} else {

  function welcome() {     //  for age = 16, this "welcome" is never created
    alert("Greetings!");
  }
}

// Here we're out of figure brackets,
// so we can not see Function Declarations made inside of them.

welcome(); // Error: welcome is not defined
let func = (arg1, arg2, ...argN) => expression

let double = n => n * 2; // paran can be omitted

let sum = (a, b) => {  // the figure bracket opens a multiline function
  let result = a + b;
  return result; // if we use figure brackets, use return to get results
};

hoisting

There is also function hoisting. A crude timeline of how JS gets executed:

  1. Parse the scope and detect all function definitions
  2. Execute the code top-to-bottom with all functions found in step 1 available as variables. following cases are exceptions:
    • Assignments are not evaluated until the code is executed.
    • Wrapping a function in parenthesis (()) is a quick way to convert a function definition into a function expression

function

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

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

  return sum;
}

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6
let arr = [3, 5, 1];

alert( Math.max(...arr) ); // 5 (spread turns array into a list of arguments)
func.call(context, arg1, arg2, ...)

function sayHi() {
  alert(this.name);
}

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

// use call to pass different objects as "this"
sayHi.call( user ); // this = John
sayHi.call( admin ); // this = Admin
let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    alert("Called with " + x);
    return x * this.someMethod(); // (*)
  }
};

function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func.call(this, x); // "this" is passed correctly now
    cache.set(x, result);
    return result;
  };
}

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

alert( worker.slow(2) ); // works
alert( worker.slow(2) ); // works, doesn't call the original (cached)

class

class MyClass {
  constructor(...) {
    // ...
  }
  method1(...) {}
  method2(...) {}
  get something(...) {}
  set something(...) {}
  static staticMethod(..) {}
  // ...
}

Classes provide "super" keyword for that.

Following two code blocks are equivalent:

function User(name) {
  this.name = name;
}

User.prototype.sayHi = function() {
  alert(this.name);
}

let user = new User("John");
user.sayHi();
class User {

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

  sayHi() {
    alert(this.name);
  }

}

let user = new User("John");
user.sayHi();

The proof of the fact:

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// proof: User is the "constructor" function
alert(User === User.prototype.constructor); // true

// proof: there are two methods in its "prototype"
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
function makeClass(phrase) {
  // declare a class and return it
  return class {
    sayHi() {
      alert(phrase);
    };
  };
}

let User = makeClass("Hello");

new User().sayHi(); // Hello
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }

  // built-in methods will use this as the constructor
  static get [Symbol.species]() {
    return Array;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

// filter creates new array using arr.constructor[Symbol.species] as constructor
let filteredArr = arr.filter(item => item >= 10);

// filteredArr is not PowerArray, but Array
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
class Rabbit {}
let rabbit = new Rabbit();

// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // true

imports, exports

// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;

// -------------

// myFile.js
import { pi, exp } from './mathConstants.js'; // Named import -- destructuring-like syntax
console.log(pi) // 3.14
console.log(exp) // 2.7

// -------------

// mySecondFile.js
import * as constants from './mathConstants.js'; // Inject all exported values into constants variable
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7


// coolNumber.js
const ultimateNumber = 42;
export default ultimateNumber;

// ------------

// myFile.js
// You can just omit '.js'
import number from 'coolNumber';
// Default export, independently from its name, is automatically injected into number variable;
console.log(number) // 42

Types

Object

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

let user = {
  name: "John",
  age: 30,
  "likes birds": true  // multiword property name must be quoted
};

alert( user.name ); // John
alert( user.age ); // 30
delete user.age;

// multiword
user["likes birds"] = true;
alert(user["likes birds"]); // true

let fruit = "apple";
let bag = {
  [fruit]: 5, // the name of the property is taken from the variable fruit
};
alert(bag.apple);

function makeUser(name, age) {
  return {
    name, // same as name: name
    age   // same as age: age
    // ...
  };
}

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

alert("age" in user);                     // true, user.age exists
alert("blabla" in user);                  // false, user.blabla doesn't exist
alert(user.noSuchProperty === undefined); // true means "no such property"

for (key in object) {
  // executes the body for each key among object properties
}

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// copies all properties from permissions1 and permissions2 into user
Object.assign(user, permissions1, permissions2);

// now user = { name: "John", canView: true, canEdit: true }

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

let clone = Object.assign({}, user);
let user = {
  name: "John",
  age: 30
};

// loop over values
for (let value of Object.values(user)) {
  alert(value); // John, then 30
}

Iterator

let range = {
  from: 1,
  to: 5
};

// 1. call to for..of initially calls this
range[Symbol.iterator] = function() {

  // 2. ...it returns the iterator:
  return {
    current: this.from,
    last: this.to,

    // 3. next() is called on each iteration by the for..of loop
    next() {
      // 4. it should return the value as an object {done:.., value :...}
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// now it works!
for (let num of range) {
  alert(num); // 1, then 2, 3, 4, 5
}

Map

let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

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

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

// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
  alert(entry); // cucumber,500 (and so on)
}

String

let name = "John";

// embed a variable
alert( `Hello, ${name}!` ); // Hello, John!

// embed an expression
alert( `the result is ${1 + 2}` ); // the result is 3

// Multiple lines
let guestList = `Guests:
 * John
 * Pete
 * Mary
`;

Symbol

// “Symbol” value represents a unique identifier.
// The first argument is a description("id", in this case), useful for debugging
let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

let user = { name: "John" };
// Work as a hidden property
let id = Symbol("id");

user[id] = "ID Value";
alert( user[id] ); // we can access the data using the symbol as the key

let id = Symbol("id");
let user = {
  name: "John",
  age: 30,
  [id]: 123
};

for (let key in user) alert(key); // name, age (no symbols)

// the direct access by the symbol works
alert( "Direct: " + user[id] );

// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created

// read it again
let idAgain = Symbol.for("id");

// the same symbol
alert( id === idAgain ); // true

let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// get name from symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
// we can omit "function" and just write sayHi().
let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert(this.name);
  }

};

user.sayHi(); // John

function sayHi() {
  alert(this);
}

sayHi(); // undefined

In this case this is undefined in strict mode. If we try to access this.name, there will be an error. In non-strict mode (if one forgets use strict) the value of this in such case will be the global object (window in a browser, we’ll get to it later). This is a historical behavior that "use strict" fixes. Please note that usually a call of a function that uses this without an object is not normal, but rather a programming mistake. If a function has this, then it is usually meant to be called in the context of an object

let user = {
  name: "John",
  hi() { alert(this.name); }
}

// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined

To make user.hi() calls work, JavaScript uses a trick – the dot '.' returns not a function, but a value of the special Reference Type. The value of Reference Type is a three-value combination (base, name, strict)

Any other operation like assignment hi = user.hi discards the reference type as a whole.

So, as the result, the value of this is only passed the right way if the function is called directly using a dot obj.method() or square brackets obj[method]() syntax (they do the same here).

let user = {
  firstName: "Ilya",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // Ilya
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

// 1. A new empty object is created and assigned to this.
// 2. The function body executes. Usually it modifies this, adds new properties to it.
// 3. The value of this is returned.


function User(name) {
  if (!new.target) { // if you run me without new
    return new User(name); // ...I will add new for you
  }

  this.name = name;
}

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

Reference

Global functions

alert("Hello");

// the same as
window.alert("Hello");

let user = "John";
alert(user); // John

alert(window.user); // undefined, don't have let
alert("user" in window); // false

alert, prompt, confirm

alert("Hello");

let age = prompt('How old are you?', 100); // always supply a 'default'
alert(`You are ${age} years old!`);

let isBoss = confirm("Are you the boss?");
alert( isBoss ); // true if OK is pressed

encodeURI, encodeURIComponent, decodeURI, decodeURIComponent

setTimeout, setInterval

function sayHi() {
  alert('Hello');
}

setTimeout(sayHi, 1000);
setTimeout(() => alert('Hello'), 1000);

let timerId = setTimeout(...);
clearTimeout(timerId);

// repeat with the interval of 2 seconds
let timerId = setInterval(() => alert('tick'), 2000);

// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);

Some use cases of setTimeout(...,0):

Topics

Type Conversions

alert( Number("   123   ") ); // 123
alert( Number("123z") );      // NaN (error reading a number at "z")
alert( Number(true) );        // 1
alert( Number(false) );       // 0
alert( Boolean(1) );          // true
alert( Boolean(0) );          // false
alert( Boolean("hello") );    // true
alert( Boolean("") );         // false
alert( 1 + '2' );             // '12' (string to the right)
alert( '1' + 2 );             // '12' (string to the left)

Check for an undefined or null variable

if (some_variable == null){
  // some_variable is either null or undefined
}

In general it is recommended to use = instead of ==. The proposed solution is an exception to this rule. The JSHint syntax checker even provides the eqnull option for this reason.

Comparisons

alert( '2' > 1 );            // true, string '2' becomes a number 2
alert( '01' == 1 );          // true, string '01' becomes a number 1
alert( true == 1 );          // true
alert( false == 0 );         // true
alert( '' == false );        // true

alert( 0 == false );         // true
alert( 0 === false );        // false, because the types are different

alert( null > 0 );           // false
alert( null == 0 );          // false, null special rule applied
alert( null >= 0 );          // true,  null is converted to 0

alert( null == undefined );  // true
alert( null === undefined ); // false

Style Guides

Garbage collection

Destructuring

// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];

// first and second elements are not needed
let [, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul

let user = {};
[user.name, user.surname] = "Ilya Kantor".split(' ');

alert(user.name); // Ilya

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

// loop over keys-and-values
for (let [key, value] of Object.entries(user)) {
  alert(`${key}:${value}`); // name:John, then age:30
}

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar

alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

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

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

let {title, width, height} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

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

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

// width -> w
// height -> h
// title -> title

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

let options = {
  title: "Menu"
};

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

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200
let title, width, height;

// error in this line
{title, width, height} = {title: "Menu", width: 200, height: 100};

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

alert( title ); // Menu
// we pass object to function
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

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

showMenu(options);
// these two calls are almost the same:
func(1, 2, 3);
func.apply(context, [1, 2, 3])

// The only syntax difference between call and apply is that call expects a list of arguments, while apply takes an array-like object with them.
let args = [1, 2, 3];
func.call(context, ...args); // pass an array as list with spread operator
func.apply(context, args);   // is same as using apply

// method borrowing (Use Array method with iterable)
function hash() {
  alert( [].join.call(arguments) ); // 1,2
}

hash(1, 2);

function showArgs() {
  alert( Array.prototype.join.call(arguments, " - ") );
}
setTimeout(() => user.sayHi(), 1000); // Hello, John!, but problematic

let user = {
  firstName: "John"
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John


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

let say = user.say.bind(user);

say("Hello"); // Hello, John ("Hello" argument is passed to say)
say("Bye"); // Bye, John ("Bye" is passed to say)
function partial(func, ...argsBound) {
  return function(...args) { // (*)
    return func.call(this, ...argsBound, ...args);
  }
}

// Usage:
let user = {
  firstName: "John",
  say(time, phrase) {
    alert(`[${time}] ${this.firstName}: ${phrase}!`);
  }
};

// add a partial method that says something now by fixing the first argument
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());

user.sayNow("Hello");
// Something like:
// [10:00] Hello, John!

function curry(f) {
  return function(..args) {
    // if args.length == f.length (as many arguments as f has),
    //   then pass the call to f
    // otherwise return a partial function that fixes args as first arguments
  };
}
let user = {};

Object.defineProperty(user, "name", {
  value: "John"
});

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/*
  {
  "value": "John",
  "writable": false,
  "enumerable": false,
  "configurable": false
  }
*/
writable
if true, can be changed, otherwise it’s read-only.
enumerable
if true, then listed in loops, otherwise not listed.
configurable
if true, the property can be deleted and these attributes can be modified, otherwise not.
let user = {
  name: "John",
  surname: "Smith",

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

alert(user.fullName); // John Smith
let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal;


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

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

// modifies rabbit.isSleeping
rabbit.sleep();

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)
let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

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

alert( rabbit.eats ); // true

function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}

alert(rabbit.constructor == Rabbit); // true (from prototype)

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false

Prototypes

obj.prototype
is used by children
obj.__proto__
links to parents

__proto__ is deprecated in favor of Object.getPrototypeOf / Reflect.getPrototypeOf and Object.setPrototypeOf / Reflect.setPrototypeOf

__proto__ getter function exposes the value of the internal Prototype of an object. For objects created using an object literal, this value is Object.prototype.

The proto setter allows the Prototype of an object to be mutated. The object must be extensible according to Object.isExtensible(): if it is not, a TypeError is thrown. The value provided must be an object or null. Providing any other value will do nothing.

x.__proto__ has a property setter, which takes the value as a prototype(null, or object), not the value itself. So, it's impossible to set the actual value to the property named __proto__. To do that, we should create a plain object(without default Object.prototype) as follows:

let obj = Object.create(null);

let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";

alert(obj[key]); // "some value"

Inheritance

// Same Animal as before
function Animal(name) {
  this.name = name;
}

// All animals can eat, right?
Animal.prototype.eat = function() {
  alert(`${this.name} eats.`);
};

// Same Rabbit as before
function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype.jump = function() {
  alert(`${this.name} jumps!`);
};

// setup the inheritance chain
Rabbit.prototype.__proto__ = Animal.prototype; // (*)

let rabbit = new Rabbit("White Rabbit");
rabbit.eat(); // rabbits can eat too
rabbit.jump();

Mixins

// mixin
let sayHiMixin = {
  sayHi() {
    alert(`Hello ${this.name}`);
  },
  sayBye() {
    alert(`Bye ${this.name}`);
  }
};

// usage:
class User {
  constructor(name) {
    this.name = name;
  }
}

// copy the methods
Object.assign(User.prototype, sayHiMixin);

// now User can say hi
new User("Dude").sayHi(); // Hello Dude!

Please note that the call to the parent method super.say() from sayHiMixin looks for the method in the prototype of that mixin, not the class.

Error handling

try {
  // code...
} catch (err) {
  // error handling
} finally {
  ... execute always ...
}

throw <error object>
window.onerror = function(message, url, line, col, error) {
  // ...
};
// The "pseudocode" for the built-in Error class defined by JavaScript itself
class Error {
  constructor(message) {
    this.message = message;
    this.name = "Error"; // (different names for different built-in error classes)
    this.stack = <nested calls>; // non-standard, but most environments support it
  }
}

window.location

Some keywords are optional, but there is a general guideline for compatibility:

window.location
is preferred over location
window.location.href = url
When redirecting, is preferred over window.location = url;

DOM

Page lifecycle

Promise

let promise = new Promise(function(resolve, reject) {
  // executor (the producing code, "singer")
});

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});
async function f() {
  return 1;
}

f().then(alert); // 1

// works only inside async functions
let value = await promise;

Events

<input value="Click me" onclick="alert('Click!')" type="button">

<!-- -->

<script>
  function countRabbits() {
    for(let i=1; i<=3; i++) {
      alert("Rabbit number " + i);
    }
  }
</script>

<input type="button" onclick="countRabbits()" value="Count rabbits!">

<!-- -->

<input id="elem" type="button" value="Click me">
<script>
  elem.onclick = function() {
    alert('Thank you');
  };
</script>
element.addEventListener(event, handler[, phase]);

elem.addEventListener('click', {
  handleEvent(event) {
    alert(event.type + " at " + event.currentTarget);
  }
});
  1. Capturing phase :: the event goes down to the element.
  2. Target phase :: the event reached the target element.
  3. Bubbling phase :: the event bubbles up from the element.

How-to

Parse URI

var parser = document.createElement('a');
parser.href = "http://example.com:3000/pathname/?search=test#hash";

parser.protocol; // => "http:"
parser.hostname; // => "example.com"
parser.port;     // => "3000"
parser.pathname; // => "/pathname/"
parser.search;   // => "?search=test"
parser.hash;     // => "#hash"
parser.host;     // => "example.com:3000"

Put <script>

<script src="path/to/script.js"></script>

Add breadcrumb

<div id="breadcrumb">
  <ol>
    <li><a href="/">Home</a></li>
  </ol>
</div>
let pathname = window.location.pathname;
let segments = pathname.split('/').filter(x => x != '');
let hrefs = segments.reduce((arr, seg) => {
  let prev = arr.length > 0 ? arr[arr.length-1] : '';
  let href = prev + '/' + seg;
  arr.push(href);
  return arr;
}, []);
let lis = segments.map((seg, i) => {
  let li = document.createElement('li');
  let a = document.createElement('a');
  let t = document.createTextNode(seg);
  a.setAttribute('href', hrefs[i]);
  a.appendChild(t);
  li.appendChild(a);
  return li;
});
let breadcrumb = document.getElementById('breadcrumb');
let ol = breadcrumb.getElementsByTagName('ol')[0];
lis.forEach(li => ol.appendChild(li));
let aTags = ol.getElementsByTagName('a');
aTags[aTags.length-1].removeAttribute('href');

Links