JavaScript Basics

Table of Contents
Variables
Anonymous Function
Control Structures
Strings
Numbers
Arrays
Events
Objects
Classes
Json
Asynchronous JavaScript
DOM API

Variables

Two types of relevant declarations: let and const. The former is for changing variables, the latter for arrays, objects and constants.

Anonymous Functions

Functions without a name. Useful for separating large functions into blocks and passing blocks to functions expecting a callback.

001  function use_callback(function (x, y) {
002    console.log(x+y);
003  });

A shorthand called arrow functions also exists.

001  function use_callback((x, y) => {
002    console.log(x+y);
003  });
  • If anonymous function is on one line, curly brackets may be omitted
  • If it has only one parameter, the parentheses may be ommited
  • Given a return value and only one, the keyword return may be omitted

The shortest possible form therefore yields

001  function use_callback( x => x * 2);

Control Structures

For the most part JavaScript control structures behave like they do in C.

Conditionals

001  if (condition) {
002  } else if (condition) {
003  } else {
004  }
005  
006  // shorthand for single line expressions
007  if (condition)
008    _expression_

Triple equals are used to also compare types. Double equals perform implicit type conversions.

001  (0 ==  "0") // true
002  (0 === "0") // false

Looping

001  for (let i = 0; i < Collection.length;i++) {
002    console.log(Collection[i]);
003  }
004  
005  const a = [0,1,2,3]
006  
007  for (const value of a) {
008    console.log(value) // 0 1 2 3
009  }
010  
011  break;    // jumps out of a loop
012  continue; // skips ahead to next iteration of a loop

Switch Statements

001  switch(value) {
002  case 0:
003    break;
004  case 1:
005    break;
006  default:
007    return undefined;
008  } 

Strings

Delimited by either " " or ' '. Both representations are the same internally. Neither supports interpolation. Strings are objects and possess methods.

001  "MyString".length // pre-computed property
002  "MyString".toUpperCase()
003  "MyString".toLowerCase()

String characters are accessible by indices.

001  "String"[0] // 'S'
002  "String".at(-1) // 'g' // String.at() supports negative indices to access from the end of a string

Substrings

001  "JavaScript".substring(0, 4) // "Java"
002  "Test".substring(1) // "est"

Index for the end of the substring is not required. If given, the last character included is the one right before the given index.

Concatenation

Strings are concatenated with the + operator.

001  "str1" + "_" + "str2" // "str1_str2"

Template Strings

Support newlines and interpolation. Delimited by backticks `.

001  let str = `This 
002             string 
003             keeps its
004             newlines`
005  
006  let var = "World";
007  `Hello ${var}!` // "Hello World!"

Numbers

JavaScript supports the numeric separator _ and features Number.toString for explicit string conversions.

001  let num = 1_000
002  num.toString // "1000"

When a non-numeric type is given, but a number is expected, the interpreter throws a NaN error.

001  "string" * 4 // NaN

Also supports the increment and decrement operators:

001  let i = 0      // 0
002  i++            // 0
003  console.log(i) // 1
004  --i            // 1
005  console.log(i) // 0

Conversion of Strings to Numbers

Often needed when working with user input.

001  Number.parseInt(str, radix) // radix: base of the required number system (usually 10, 2, 16)

The radix parameter is optional, but does not always default to 10. Therefore it shouldn't be omitted. The root-level function parseInt() still works to support legacy code. It shouldn't be used anymore.

Remainder

The modulo operator % finds the remainder of a division.

001  8 % 2 // 0
002  3 % 2 // 1

Exponent Operator

001  5 ** 2 // 25

Other Important Function

001  Math.round()
002  Math.floor()
003  Math.ceil()

Arrays

Arrays may contain values of any and different types.

001  const my_array = []; // declares an empty array
002  my_array.length      // pre-computed property

Changing the value of the Array.length property expands or truncates the array, depending on whether the new value is higher or smaller then the previous one.

Accessing Array Values

Array members can be accessed the same way string characters are accessed.

001  my_array[i] 
002  my_array.at(-1)

Appending Values

Array.push(<data>) appends a value to the end of an array and returns its new length.

001  const my_array = [1,2,3]
002  let val = 9
003  my_array.push(val) // 4

Arrays and Function

Function may return empty arrays. Function arguments are not references to the original arrays. When a function modifies an array, it has to return the modified data.

001  function func() {
002    return []
003  }
004  
005  function mod(array) {
006    array.push(array.length + 1)
007    return array
008  }

Iteration

001  my_array.forEach(function(member) {
002    // member holds value of the current element
003    console.log(member)
004  });
005  
006  // modern syntax
007  // callback functions have access to the scope they are contained in
008  const numbers = [1,2,3,4,5,6,7]
009  let sum = 0;
010  numbers.forEach((n) => {
011    sum += n
012  });
013  sum // 28

Sparse Arrays

Array may contain empty members that may not always be considered to be of type undefined.

001  const a = Array(2) // [<two empty members>]
002  a = [1,2,,,4,5]    // [1,2,<two empty members>,4,5]
003  a[a.length+2] = 7  // an empty member is inserted before 7
004  a.length++         // pushes an empty member to the array

The callback function passed to Array.forEach() is not invoked for empty members.

Useful function

001  const array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
002  // returns an array with all members that satisfy a condition (shallow copy)
003  array.filter(n => n > 10 );
004  
005  // returns the first member that satisfies the condition
006  array.find(n => n == 5)
007  
008  // returns index of some value, or -1 if it wasn't found
009  array.indexOf(6) // 5
010  array.indexOf("hello") // -1
011  
012  // transforms each item and returns a new array with modified values
013  const array2 = array.map(n => n * 2)
014  array.length == array2.length // true
015  
016  // returns true or false depending on whether the value is present in the array
017  array.includes(1) // true
018  
019  // returns a string with members separated by the argument
020  array.join('-') // "1-2-3-..."

Events

Events fire when the user interacts with the webpage. Examples among others:

  • by clicking on or hovering over elements
  • resizing or closing browser windows
  • when websites are loaded

Event listerners (or event handlers) are blocks of code that run when their associated events are triggered.

001  element.addEventListener("type", () => { ... });

Event types include among others:

  • click
  • dblclick
  • focus
  • blur (element loses focus)
  • mouseover
  • mouseout

If a handler is registered with a named function it can be removed from the element and trigger:

001  element.removeEventListener(<"type">, <callback-name>)

Abort signals facilitate another way to remove event listeners:

001  const controller = new AbortController()
002  element.addEventListener(<type>, <callback>, { signal: controller.signal })
003  controller.abort(); // removes and or all event listeners associated with the controller

Alternative Handlers

Handlers may be registered via node properties. However this approach is inflexible because only one handler can be registered per property. They may also be registered inline in HTML, but handlers are easier to maintain in JavaScript.

001  element.onclick = function()
002  <button onclick="function()" />

Event Objects

001  el.addEventListener("keydown", (event) => event.key )

Events can pass objects to handlers. event.target for example refers to the node object that fired the event. event.preventDefault() can prevent default behavior – for example it prevents form submit elements from automatically sending requests on click.

Event Bubbling

By default event listeners that are registered on some node propagade down to all of its child nodes. Useful for delegation (see example below).

001  // an event listener registered on the <div> element
002  // will trigger when a user clicks on any of its buttons
003  <div>
004    <button></button>
005    <button></button>
006    <button></button>
007    <button></button>
008  </div>
009  document.querySelector("div").addEventListener("click", (event) => );

This behavior is disabled by element.stopPropagation()

Objects

Empty JavaScript objects are declared in a similar way to arrays. Their properties may be data or functions.

001  const obj = { }; // declares an empty object
002  obj = {
003    name: "Ken",
004    age: 22,
005    walk: function () {
006    },
007  }; 

Objects can be nested in other objects.

001  const obj = {
002    obj2 = {
003      property: value
004    }
005  }

A shorthand syntax for methods is supported.

001  const obj = {
002    func() { }
003  }
004  obj.func() // calls the method

Objects created in this static way are called object literals. They're useful for passing options to functions and sending data over HTTP.

Accessing Properties and Methods

001  obj.property
002  obj.method()
003  
004  // bracket notation faciliates dynamic acces
005  let prop = 'name'
006  obj[prop] // obj.name
007  
008  obj['obj2']['name'] // also works with nested objects

Object literals can always be extended with new properties and methods.

this

Object methods may refer to their objects by this.

Constructors

JavaScript features two ways to implement object constructors for dynamic creation of objects.

001  function createObject(prop) {
002    const obj = {};
003    obj.prop = prop;
004    return obj;  
005  } 
006  
007  const my_object = createObject("object_a")
008  
009  // 'Object' here could be any other word starting with a capital letter
010  function Object(prop) {
011    this.prop = prop
012    this.method = function { }
013  }
014  
015  const my_other_object = new Object("object_b")

Prototypes & The Prototype Chain

Each object in JavaScript inherits from another object, called its prototype.

  • handled by the __proto__ property
  • the last object in a chain has its prototype set to null

The __proto__ property is hidden and accessed by Object.getPrototypeOf(my_object).

JavaScript searches throughout the whole prototype chain when a property or method is called on an object.

Setting Prototypes

The argument of Object.create(obj) becomes the prototype of the otherwise empty object returned by the function.

001  // setting the prototype of objects created by constructors
002  function Decimal(value) {
003    value: value
004  }
005  Object.assign(Decimal.prototype, Number) // Number is now prototype of Decimal

Properties

Object.hasOwn(<object>, <property>) returns true when the property is found on object directly. Properties not meant to be accessed outside of its own methods are sometimes marked with a _ prefix.

Classes

Classes in JavaScript are built on the prototype chain. They provide a notation that mimics the syntax of popular object-oriented programming languages.

001  class Car {
002    // properties (optional)
003    brand;
004    #steeringWheel = 0; // default value and private property
005  
006    constructor(brand) {
007      this.#brand = brand
008    }
009  
010    startEngine() {
011    }
012  }

Inheritance

001  class Car extends Vehicle {
002    constructor(id) {
003      super(id); // calls the same method defined in parent class 
004    }
005  }

Sub classes can overwrite behavior of their super class by defining a method that's defined by their super class.

JSON

Notation for storing and transmitting data. Resembles JavaScript object literals.

  • only holds properties, not methods
  • only double quotes encapsulate strings
  • property names are always qyoted strings

JavaScript's JSON object is responsible for handling json data.

001  JSON.parse(json)    // returns an object that represents the json data
002  JSON.stringify(obj) // returns a json representation of some object

Asynchronous JavaScript

Used for slow operations that would otherwise block the execution of JavaScript.

Legacy API

Works like event handlers.

001  const xhttp = new XMLHttpRequest()
002  xhttp.addEventListener('loaded' () => {
003    // runs when the request has completed
004  }
005  xhttp.open(method, url)
006  xhttp.setRequestHeader() // e.g. "Content-type","application/x-www-form-urlencoded" for forms
007  xhttp.send() // begins the request, may be passed a data string for post request (x=y&z=a...)

Promises

Modern API to prevent hard to maintain code with nested async calls.

001  const promise = fetch(url)
002  
003  promise.then((response) => { });

Unlike the old API promises can be chained by having callbacks return promises.

001  promise.then((response) => {
002    const promise = reponse.json()
003    promise.then((data) => {});
004  });
005  
006  // or better yet
007  promise
008    .then((response) => response.json())
009    .then((data) => {});

To confirm that a single request was successfull:

001  if (!response.ok)
002    throw new Error(`HTTP error: ${response.status}`);
003  return response.json()

And to handle errors:

001  // attach to the end of a promise chain
002  .then(() => {})
003  .catch((error) => {
004    // is called whenever an error occurs anyplace in the promise chain
005    console.log(error)
006  }

Terminology

Possible statuses of promises:

  • pending
  • fulfilled (then() handler is called)
  • rejected (catch() handler is called)

A promise is settled when it's either fulfilled or rejected, and resolved when it's settles or its then() handler finished with returning another promise.

Combined Promises

When one handler is meant to run after a set of promises is fulfilled:

001  const p1 = fetch(url)
002  const p2 = fetch(url2)
003  Promise.all([p1,p2]).then((response) => {});

And if only one of many needs to be fulfilled:

001  Promise.any([p1,p2]).then((response => {});

async / await

Keywords that allow a function use promises and appear like synchronous code.

001  async function fetchData() {
002    try {
003      const response = await fetch(url)
004      if (!response.ok)
005        throw new Error(`HTTP error: ${response.status}`)
006  
007      const data = await response.json() 
008    } catch (error) {
009      console.log(error)
010    }
011  }

Note that an async function's return data is always wrapped inside a Promise object.

DOM API

The JavaScript object representation of a webpage is a tree of nodes representing the page's HTML elements.

  • root node: <html>
  • child node: a node that is a direct descendant of another
  • descendant node: a descendant anywhere in the ancestor's structure
  • parent node: the direct parent of a node
  • sibling nodes: nodes that share the same parent

Fetching Nodes

001  // match first result by CSS selector
002  document.querySelector(string)
003  
004  // match all results by CSS selector
005  document.querySelectorAll(string)
006  
007  // legacy selectors, supported by archaic browsers
008  document.getElementById(str)
009  document.getElementsByClassName(str)
010  document.getElementsByTagName(str)

Node Lists

Internal objects representing lists of nodes. May be static or live.

  • static lists: dom changes have no effect on the list
  • live lists: changes to the dom are synchronized with the list

Node.childNodes is always a live list. document.querySelectorAll() always returns a static list.

001  // iterating node lists 
002  for (let i = 0; i < NodeList.length; i++)
003    NodeList.item(i) // or NodeList[i]
004  for (const e of NodeList.entries)
005    e
006  NodeList.forEach(() => {})

Manipulation

001  Node.textContent = "new text"
002  const new Node = document.createElement('p')
003  Node.appendChild(newNode)
004  
005  const textNode = document.createTextNode("some text")
006  Node.appendChild(textNode) // also moves a node from some parent to the end of another's children list
007  
008  // node objects always reference one html element
009  Node.cloneNode() // creates a new html element from another node
010  
011  // deleting nodes
012  parent.removeChild(node) 
013  Node.remove() // not supported by legacy browsers
014  
015  // insertion of HTML
016  Node.innerHTML(str) // overrides all child nodes and contents
017  Node.insertAdjacentHTML(<position>, <html>) // see (1) for position values

(1)https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML

Style Manipulation

Styles of elements are manipulated via Node.style.<property>. However this adds styles inline. The better alternative is to adjust a node's classes via Node.setAttribute("class", <class>).