LearningJavaScript

From HerzbubeWiki
Jump to navigation Jump to search

On this page I keep notes about my first steps with JavaScript. I am motivated to learn this new language so that I can write automated UI tests for my iOS app Little Go.

Later: After a beauty sleep of a few years this is getting important again because I'm planning to get an education in web programming. In the context of this education programme, the next learning page after this one is LearningXML, and the previous page is LearningCSS. Companion pages to this one are LearningWebAPIs, LearningAngular and LearningPHP.


References

JavaScript


JSON


jQuery


Glossary

AJAX
Asynchronous JavaScript and XML.
Direct instance
See ex nihilo. W3Schools uses this term.
DOM
Document Object Model. A tree-shaped logical representation of the markup in an HTML or XML document.
ECMAScript
The standard upon which JavaScript is based
Ex nihilo
A term used by this Wikipedia article on "Prototype-based programming". It refers to the way how JavaScript objects can be created "from nothing", without cloning another object.
Hoisting
The mechanism used by JavaScript to make variables and functions available (variables: without initial value) before they have been declared.
JavaScript
Implementation of ECMAScript that is officially managed by the Mozilla Foundation
JS
Abbreviation for JavaScript
JScript
Microsoft implementation of ECMAScript in Internet Explorer
JSON
JavaScript Object Notation.
Object constructor
A special function that is used to initialize a new object.
Polyfill
A JavaScript solution (aka "shim") that manipulates an older browser in such a way that the manipulated browser starts to mimic support for some newer API that it doesn't know out of the box. Example: Add support for HTML5 elements to a browser that doesn't know these elements.
TypeScript
TypeScript is a superset of JavaScript which adds optional static typing to the language. TypeScript requires a compiler. The compiler output is plain JavaScript.
XHR
Short for XMLHttpRequest.


First impressions

  • Variables do not have a type, any value can be assigned to them
  • Only values have types
  • Everything is an object, there are no primitive types
  • There is no multiple inheritance - phew!
  • An object is a collection of properties
  • A method is a property of an object with a value that is a function
  • Properties can be dynamically added to an object
  • An object's properties are accessed like a dictionary where the keys are strings (the propery names)
  • obj.x = 10 and obj['x'] = 10 are equivalent, the dot notation is syntactic sugar
  • JavaScript code that is provided as a string can be executed using the eval function
  • Functions are objects
  • Functions can be nested
  • Variables are defined using the keyword var or let; you usually want to use let because its scoping rules behave similarly as in other programming languages
  • Semicolons to terminate statements are optional, they are inserted automatically if they are omitted
  • The language is case sensitive


Interactive shell

I use Node.js as a shell to test out things:

node

To run a script, just do this:

node /path/to/script


Basics

Comments

The two usual syntaxes for comments exist:

// Single line comment

/*
 * Multi-line comment
*/


Variables

Variables are declared using either the var keyword or the let keyword. The difference is in scope: let declares the variable only within the current block, statement or expression, whereas var is more lenient. See the section about scoping further down for details.

var x;
var y = 42;
let z = x + y;
var foo = "bar";

var y = 17;     // OK - variables declared with var can be re-declared in the same scope
let z = y + x;  // ERROR - variables declared with let cannot be re-declared in the same scope

As is obvious from the examples, variables can be initialized on the same line that they are declared.


Constants

Constants obey pretty much the same rules as variables, but they are declared using the const keyword:

const foo = "bar";

const baz;  // ERROR - constants must be assigned a value at declaration time
baz = 42;

Like let declarations, const declarations can only be accessed within the block they were declared, and they cannot be re-declared in that scope. In addition it's also not possible to assign a new value.


Variable scope

The scoping rules for variables declared with var are:

  • Variables declared outside of the scope of a function are global variables
  • Variables declared in the scope of a function are local to that function
  • A locally declared variable hides a global variable of the same name
  • A block of code inside braces "{}" does not provide scope. Thus a variable declared, for instance, within an if block is not local to that block, it is visible in the scope of the surrounding function, or globally.


The scoping rules for variables declared with let are:

  • Variables declared by let are valid within the block, statement or expression in which they are defined, as well as in any contained sub-blocks. So this is what we are used from other programming languages.


The scoping rules for constants declared with const are:

  • Constants declared by const are valid within the block, statement or expression in which they are defined, as well as in any contained sub-blocks. This is essentially the same as for let declarations.


Finally:

  • Omitting the keywords var and let when a variable is declared in the scope of a function causes the variable to become a global variable (roll eyes...).
  • Using var in the global context creates a property on the global object, i.e. var foo = 42; var bar = this.foo; works. Using let, however, does not create a property on the global object.
  • Although variables declared with let and constants declared with const are hoisted (see next section) they cannot be used before the point where they are initialized.


Hoisting

When JavaScript code is parsed, all var, let and const declarations of variables/constants as well as all function declarations are "magically" moved to the top of the scope in which they are declared. This process is called "hoisting". The result of hoisting is that variables, constants and - especially! - functions can be used in the source code before their declarations appear.

Important: Regarding variables and constants, only their declarations are hoisted, not their initialization!


Examples:

foo(varBar);    // prints "undefined"
foo(letBar);    // raises an exception because "letBar" is not yet initialized
foo(constBar);  // raises an exception because "constBar" is not yet initialized

function foo(param)
{
  console.log(param);
}

var varBar = 42;
let letBar = 42;
const constBar = 42;

foo(varBar);    // prints "42"
foo(letBar);    // prints "42"
foo(constBar);  // prints "42"


Strict mode

You can declare that your JavaScript code should be parsed in so-called "strict mode". This disables all manners of "flexible" things in your code that are allowed in "normal mode", such as using a variable before it has been declared.

Enable strict mode by adding the following line at the top of the entire script, or at the top of a function (if you want to enable strict mode only in that function):

"use strict";

Notes:

  • If the line appears anywhere else than at the top of a scope, it won't have any effect
  • Make sure to use the double quotes around the statement. The double quotes are a compatibility feature: They make the statement appear to older JavaScript parsers that don't understand strict mode as if this were a string literal. Strict mode was added to ECMAScript 5 in December 2009, so nowadays it should be present in all modern browsers.
  • See W3Schools for a list of things that are not allowed in strict mode


Numbers

JavaScript has only one type of number, i.e. there is no distinction between integer and decimal values. The following can be said:

  • All numbers are stored as 64-bit (8-bytes) base 10, floating point numbers
  • Integers are considered accurate up to 15 digits
  • The maximum number of decimals is 17, but floating point arithmetic is not always 100% accurate
  • Octal and hexadecimal literals are written in the usual way (octal = prefixing "0" (zero), hexadecimal = prefixing "0x")


There is an object type that can represent numbers, the type name is Number (reference).


Booleans

Literals and object type

Primitive boolean values are represented by the literals true and false. These can be used in any logical expression.

There is also an object type to represent boolean values, the type name is Boolean (reference). Boolean objects cannot be used interchangeably with primitive boolean values! For instance:

var x = new Boolean(false);
if (x)
{
  // this code is executed unexpectedly!!!
}
var y = new Boolean(x);   // y unexpectedly has an initial value of true


Truthy and falsy

Due to type conversion every value in JavaScript has an inherent boolean value of either true or false. The conversion rules are:

  • The following values evaluate as false:
    • false
    • null
    • undefined
    • 0 and -0 (zero)
    • Empty strings ("" or )
    • NaN (e.g. the result of 1/0)
  • Everything else evaluates as true. This includes an empty object ({}) and Infinity.


These rules are quite clear. What can be surprising is when they are applied and - even more so - when they are not applied. The vague terms "truthy" and "falsy" have been used to refer to the way how values of various types behave when they are evaluated in a logical context, sometimes with surprising results. This is especially true for the loose logical comparison operators == and !=.


One other conversion rules that can be important to understand "truthy" and "falsy": In a logical context, true evaluates to the numeric value 1 (one) and false evaluates to the numeric value 0 (zero).


Loose equality comparisons with operator ==

The loose comparison operators == and != have a strong "truthy" and "falsy" affinity because so many possibilities exist to get unexpected results. One must keep in mind that the operators do not necessarily convert their operands into booleans before they performs the comparison! This may sound logical, so let's have a look at these examples:

  • false == null results in false
  • false == undefined results in false
  • false == NaN results in false
  • false == {} (an empty object) results in false
  • true == 1 results in true


Was any of these results surprising? After the warning, probably not. But what about these?

  • false == 0 results in true
  • false == results in true
  • false == [] (an empty array) results in true
  • NaN == NaN results in false
  • true == 2 results in false (because true evaluates to number 1, which is not equal to 2)
  • true == "1" results in true (because the string is converted into the number 1, and because true evaluates to number 1)


I'm pretty sure these contained some surprises! The "References" section at the top of this page has links that provide more information on "truthy" and "falsy".


Strict equality comparisons with operator ===

The operator === includes the type in its operation and therefore has much more consistent and understandable results. However, NaN === NaN still results in false - use isNaN() instead.


Strings

String literals are either enclosed in single or double quotes. There is also an object type to represent string values, the type name is String (reference). Some basic examples:

var s1 = "foo";
var s2 = new String("bar");
var l = s1.length;
var firstOccurrence = s1.indexOf("o");  // result is 1
s1.toUpperCase();
s1.charAt(0);  // result is "f"

var s3 = s1 + s2 + " " 42;  // result is "foobar 42"
if (s1 == "foo")            // evaluates to true

console.log(typeof s1);  // logs "string"
console.log(typeof s2);  // logs "object"

// Conversion to integer/float with parseInt() / parseFloat()
var aNumber = parseInt("042");     // 42
var aNumber = parseInt("42", 16);  // 66 (uses radix 16)
var aNumber = parseInt("0xff");    // 255
var notANumber = parseInt("foo");  // NaN
var aNumber = parseInt("42.jpg");  // 42 (oops!)

// To prevent the surprising last result ("42.jpg" becomes 42) it might be
// preferrable to use some unary operator trickery
var aNumber = + "042";         // 42; + operator must be at the beginning to prevent string concatenation
var aNumber = "042" - 0;       // 42; - operator can be anywhere because there is no string operation that uses the - operator
var notANumber = + "42.jpg";   // NaN

Warning: There are other subtle distinctions between string literals and String objects.


Arrays

Array literals are made using brackets ("[]"). There is also an object type to represent array values, the type name is Array (reference). Array indices are zero-based. Some basic examples:

var names1 = ["Peter", "Sue", "Marc"];
var names2 = new Array("Peter", "Sue", "Marc");
var names3 = new Array();
names3[0] = "Peter";
names3[1] = "Sue";
names3[2] = "Marc";

names1.length;        // evaluates to 3
names1[10] = "Igor";  // array grows, in-between elements have value undefined
names1.length;        // evaluates to 11
names1.length = 20;   // array grows, added elements have value undefined

var names4 = ["Peter", "Sue", "Marc", ];  // last trailing comma is ignored

names4.push("Ted");     // add value to the end of the array
var index = names4.indexOf("Sue");  // get index position of an element

// Removing array elements works like this. We could also specify to remove
// more than one element. The return value is another array containing the
// removed elements. The array itself is modified in place.
var removedElements = names4.splice(index, 1);

var poppedElement = names4.pop();     // removes and returns the last element of the array
var shiftedElement = names4.shift();  // removes and returns the first element of the array

var newLength = names4.unshift("Huey", "Dewey", "Louie");  // add values to the beginning of the array and returns the new length

var newArray = names1.concat(names2);    // the original arrays are not changed; duplicates are not eliminated
var shallowCopyArray = names1.slice(0);  // we could also use 1) a loop to manually clone, 2) use concat(emptyArray), 3) use Array.from(), 4) possibly more


undefined

undefined is a primitive value. An uninitialized variable has this value, and a function that returns without an explicit return value instead returns the implicit value undefined.

var foo;
if (foo === undefined)
{
  // do something
}

The "===" operator is explained in the "Operators" section.


null

null is a primitive value. It is also a keyword. A variable can be emptied by assigning it the value null.

TODO: Provide more information.


NaN

NaN is a global property (or maybe better: a property of the global object) that is short for "not a number". NaN represents something that is, well, "not a number".

From MDN: [NaN] is the returned value when Math functions fail (Math.sqrt(-1)) or when a function trying to parse a number fails (parseInt("blabla")). [...] NaN compares unequal (via ==, !=, ===, and !==) to any other value - including to another NaN value..

Because of this, checking for NaN is not as straightforward as one might think. These possibilities exist:

  • Number.isNaN(aValue): Returns true only if the value currently is NaN. The value is not type coerced! For instance, Number.isNaN("foo") returns false.
  • isNaN(aValue): Returns true if the value currently is NaN, or if it becomes NaN after a type coercion. For instance, isNaN("foo") returns true.
  • aValue === aValue: This is the same as Number.isNaN(aValue), because NaN - and only NaN! - will compare unequal to itself.


Date

The Date type is not a primitive data type, it's an object type (see Object orientation). Because dates and times are so important, this page keeps its discussion of the Date type close to the discussion of primitive data types.

Date objects represent a moment in time. They are based on a time value that is - unsurprisingly - the number of milliseconds since 1 January 1970 UTC (aka "the epoch"). Date objects can represent the epoch +/- 100'000'000 days.

Date objects are always created with new. There are four ways of creating Date objects, but the following snippet shows a fifth way in case you want to specify the date components in UTC.

// Creates a Date object representing the current date/time
var date = new Date();

// Milliseconds since the epoch (leap seconds ignored). Note that this is different
// from date/time functions in many other languages/platforms where the base is
// SECONDS since the epoch.
var date = new Date(milliseconds);

// MDN says that using date strings
//   "[...] is strongly discouraged due to browser differences and inconsistencies."
// The date string must be in a format recognized by Date.parse().
var date = new Date(dateString);

// Parameters are specified in local time. The default value for missing parameters
// is the first possible value in the parameter's value range.
var date = new Date(year, month [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);

// Parameters for the Date.UTC() method are specified in UTC. Date.UTC() then returns
var date = new Date(Date.UTC(year, month [, day [, hours [, minutes [, seconds [, milliseconds]]]]]));

Notes:

  • Days are values in the range of 1-31
  • Months are values in the range of 0-11
  • Years are values starting from 0. TODO: What happens if a negative number is used? Years 0-99 are interpreted as 1900-1999 - if you need the literal year 1 AD, for instance, then construct the Date object as you normally would, then fix the year by invoking date.setFullYear(1). There really is no other way!
  • Hours, minutes, seconds and milliseconds are values in the ranges of 0-23, 0-59 and 0-999.
  • In the constructor with 2 or more parameters, or in the Date.UTC() method, if any parameter value exceeds its normal range the other parameter values are adjusted to allow for the specified number. Example: A month value of 15 causes the specified year to be incremented by 1 (year + 1), and the month value is set to 3.


Accessing the components of a Date object:

// This is shown because it's surprising - in the constructor we specify
// day as "day of month", but here "day" suddenly changes its meaning to
// "day of week".
var dayOfWeekLocaltime = date.getDay();
var dayOfWeekUTC = date.getUTCDay();

// Another surprise: Why is "date" returning a day?!? But I guess since
// "getDay" was already abused there is little harm in another "yikes!"
// moment...
var dayOfMonthLocaltime = date.getDate();
var dayOfMonthUTC = date.getUTCDate();

// Never, ever use getYear()! At least that method is marked a deprecated on MDN.
var yearLocaltime = date.getFullYear();
var yearUTC = date.getUTCFullYear();

var millsecondsLocaltime = date.getMilliseconds();
var millsecondsUTC = date.getMilliseconds();

// The value is negative for a Date prior to the epoch
var millisecondsSinceTheEpoch = date.getTime();

var localTimezoneOffsetInMinutes = date.getTimezoneOffset();

Notes:

  • In general there are always two methods to access a given date component: One that uses local time and one that uses UTC. The UTC variant always includes an "UTC" prefix in the method name. Example: getMonth() and getUTCMonth().
  • For every getter there is also a setter.


Other semi-useful Date methods:

// This is a convenient shortcut to dateEnd.getTime() - dateStart().getTime().
// Note: Performance.now() provides more reliable and precise measurements,
// but it's available only in browsers that support the Web Performance API.
var elapsedTimeInMilliseconds = dateEnd - dateStart;

// The following two methods should only be used if in dire need. In general
// it is far better to transfer the underlying "milliseconds since the epoch"
// value of a Date object than its string representation.
var iso8601DateString = date.toISOString();
// The result is intended for use by JSON.stringify(). Internally
// uses toISOString().
var jsonDateString = date.toJSON();


Functions

Basic usage

function computeSum(term1, term2)
{
  var sum = term1 + term2 + globalTerm;
  return sum;
}

var globalTerm = 42;
var sum = computeSum(3, 4);

Notes:

  • The keywords var and let are not used when arguments are declared
  • The return value is not declared at all
  • A function can return without an explicit return value. The function in this case returns the implicit value undefined.
  • Variables declared within the function have local scope, i.e. they are not visible outside of the function. Exception: If a variable is used without any keyword (i.e. neither var nor let), it becomes a global variable that is accessible anywhere!
  • You must not declare a variable with the same name as a function! For instance, var isFormValid = isFormValid(); will not work!


Parameters are optional

In the following example we use the arguments object to access the supplied parameters.

function foo(param1, param2, param3)
{
  if (param2 === undefined)
  {
    // do something
  }

  if (arguments.length > 3)
  {
    var param4 = arguments[3];
  }
}

// Omitting parameters will cause them to be filled with the default value
// undefined
foo(1, 2);

// Omitting only some parameters in the middle should be done by using the
// undefined value, to emulate the default 
foo(1, undefined, 3);

// Passing more parameters than those named in the function declarations is
// also possible. The additional parameters are accessible only via the
// arguments array variable.
foo(1, 2, 3, 4);


Rest parameters

A function can have a single "rest parameter" which collects an arbitrary number of parameter values into an array. In other programming languages, rest parameters are sometimes called variable arguments, or variable-length argument lists.

function foo(param1, param2, ...remainingParams)
{
  // remainingParams is an array which contains all
  // parameter values from the 3rd parameter onwards
}

// 3 and 4 are placed into the rest parameter
foo(1, 2, 3, 4);

// The ... syntax can be used to "explode" an array so that its elements
// are passed as distinct values.
let anArray = array(42, 17);
foo(1, 2, ...anArray);

// Unsurprisingly, the rest parameters receives the values 17 and 2
foo(1, ...anArray, 2);


Functions as objects

function foo()
{
  [...]
}

function bar(callback)
{
  [...]
  callback();
  [...]
}

// Regular function invocation
var fooResult = foo();

// Here we store a reference to a function
var fooReference = foo;

// Here we invoke the function via its reference
var fooReferenceResult = fooReference();

// Here we pass a function reference as parameter to another function,
// which will use it as a callback.
var barResult = bar(foo);


Anonymous functions

function bar(callback)
{
  [...]
  callback(42);
  [...]
}

// Here we pass a reference to an anonymous function that
// we declare "on the fly" as a parameter into another
// function.
bar(function(param1) {
  return param1 * 2;
});


Self-invoking functions

A function declaration can be made to execute the function immediately with the following convoluted syntax. This is called a "self-invoking" function.

var value = "foo";

(function(param) {
  console.log(param);
}) (value);

Notes:

  • The function declaration as a whole must be surrounded with parantheses
  • After the closing parantheses you must use the usual syntax for invoking a function, i.e. parentheses with any parameters, and a terminating semicolon


TODO: The practical value of self-invoking functions is not yet clear to me - I believe it's related to closures.


Arrow functions

Arrow functions provide a more concise way to write function expressions. It's just a different notation than the one where we use the function keyword.

Examples:

var value = (param1, param2) => {
  // do something
};

var value = () => {
  // do something
};

// These all do the same
var value = (param1) => { return param1 * param1; };
var value = param1 => { return param1 * param1; };
var value = param1 => param1 * param1;


But there's more! More important - I believe - than the shorter notation is that when you use an arrow function, its this value is lexically bound to the this value of the enclosing execution context. This means that in an object-oriented context this always refers to the object that uses the arrow function. For more details on the problem, see the section The this keyword further down on this page, and the MDN docs for arrow functions.


Operators

JavaScript has the usual operators:

  • Arithmetic operators: +, -, *, /
  • Comparison operators: ==, !=, >, >=, <, <=
  • Logical operators: &&, ||, !
  • Conditional operator: (condition)?value1:value2
  • Others: ++, +=, --, -=

Equality special cases:

  • === stands for "value and type are equal". Given that var x = 5;, then x === "5" is false, while x === 5 is true.
  • !== stands for "neither value nor type are equal". Given that var x = 5;, then x === 42 is false, while x === 5 is true.


The so-called "spread" operator is "..." prepended to an object. It expands the elements of an iterable (such as an array) into places where multiple elements can fit. Examples:

// Array examples
let numbers1 = ["one", "two", "three"];
let numbers1 = ["five", "six", "seven"];
let numbersCombined = [...numbers1, "four", ...numbers2];

// It also works with the properties of an object
let anObject = { a: "one", b: "two", c: "three" };
let anotherObject = { ...anObject, d: "four" };      // copy the properties of anObject into anotherObject


Flow control

if:

if (condition)
{
  // do something
}
else if (condition)
{
  // do something
}
else
{
  // do something
}


switch:

var foo = "bar";
switch(foo)
{
case "abc":  // fall-through
case "def":
  // do something
  break;
case "bar":
  // do something
  break;
default:
  // do something
  break;
}


for loop:

// Generic for loop
for (var index = 0, found = false; index < names.length || found ; ++index)
{
  if (condition)
    // do something
  else if (condition)
    continue;
  else
    break;
}

// Special for loop that iterates over the elements of a collection. You can
// use this for the following object types: Array, String, TypedArray, Map,
// Set, arguments (i.e. the arguments of a function), generators, but also
// for objects that implements the "iterable" protocol.
//
// IMPORTANT: This kind of for loop was defined in ECMAScript 2015 and is
// not supported in Internet Explorer!
for (let variable of iterable)
{
  // do something
}

// Special for loop that iterates over the properties of an object.
var object = getObjectFromSomewhere();
for (var propertyName in object)
{
  var propertyValue = object.propertyName;
  // do something
}


while:

while (condition)
{
  // do something
}

do
{
  // do something
}
while (condition)


forEach can be used on arrays, but it's a function of the array object, not a language keyword.

let anArray = ...;
anArray.forEach(function(currentValue, index, array) {
  // index and array parameters are optional

  // if this callback changes the array's content this will have
  // "interesting" effects:
  // - adding elements does not affect the loop because basically
  //   the range of elements is determined before the loop begins. 
  // - if the value of an existing element in the array is changed,
  //   the loop will still visit the original value
  // - deleting a not-yet-visited element from the array causes the
  //   element to not be visited by the loop
  // - deleting an already element from the array causes later
  //   elements to be skipped
}, thisArg);  // <-- optional, but should be supplied in object methods to retain the object's "this" value


Arrays revisited

The array type has a number of extremely useful functions, such as the following. Cf. the MDN docs for a complete reference.


Important: Make sure that when you use one of the array functions in an object context you specify the thisArg parameter. If you forget this, then the callback's this value will not be the object that executes the array function!


  • reverse() : Reverses the order in which elements appear in the array. Important: This changes the array!
  • join([separator] : Converts all elements in the array to a string and concatenates the results using a separator. The default separator is a comma (",").
  • indexOf(searchElement[, fromIndex]) : Returns the first index position where the specified value appears in the array, or -1 if the value is not in the array. An optional starting index can be specified. Comparison is made using ===.
  • includes(searchElement[, fromIndex]) : Returns true if the specified value appears in the array, false if not. An optional starting index can be specified.
  • find(callback[, thisArg]) : Returns the value of the first element in the array that satisfies the provided testing function. Returns undefined if no element is found.
  • findIndex(callback[, thisArg]) : Same as find, but returns the index of the found element. Returns -1 if no element is found.
  • slice([begin[, end]]) : Creates a new array with elements that are located between the specified begin/end index positions. The end position is not included!
  • map(callback(currentValue[, index[, array]]) { ... }[, thisArg]) : Creates a new array with the results of the supplied callback function.
  • filter(callback(currentValue[, index[, array]]) { ... }[, thisArg]) : Creates a new array with all elements for which the callback returns true.
  • every(callback(currentValue[, index[, array]]) { ... }[, thisArg]) : Returns true if the callback returns true for every element in the array. Returns false if the callback returns false for at least one element in the array.
  • some(callback(currentValue[, index[, array]]) { ... }[, thisArg]) : Returns true if the callback returns true for at least one element in the array. Returns false if the callback returns false for all elements in the array.
  • forEach : Iterates over all elements in an array. This has been already shown in the "Flow control" section.
  • reduce. Reduces an array to a single value by comparing the value of every element in the array to the result of the previous iteration, returning a new result in each iteration. The callback is more complicated than the ones for the previous functions. Check out the MDN docs for details.


Object orientation

No classes

JavaScript does not have the concept of "classes" from which objects can be created. Instead JavaScript uses an approach that is called "Prototype-based programming" (Wikipedia page).

TODO: Link the terms "ex nihilo" (employed by Wikipedia) and "direct instance" (W3Schools) to the stuff presented in the following sections.


Object literals

An object can be constructed using an object literal (aka "object initializer"). Object literals are made using braces ("{}").

Example:

let person =
{
    firstName : "John",
    lastName : "Doe",
    id : 5566
};

let empty = {};


Object constructor

Functions can act as object constructors:

function Person(firstName, lastName, id)
{
  this.firstName = firstName;
  this.lastName = lastName;
  this.id = id;
}

var aPerson = new Person("John", "Doe", 5566);

Notes:

  • The keyword this is used to access the newly constructed object
  • The object constructor has a special property named prototype which links together all objects created with the constructor. More on this in a later section.
  • The object constructor does not need to return a value


The new keyword

The new keyword is used to indicate that a function is to to be executed and that it will return a newly constructed object. Compare this to what happens if you omit the new keyword:

function Point(x, y)
{
  this.x = x;
  this.y = y;
}

// Without the "new" keyword, JavaScript performs a regular function call.
// Because the function does not actually return a value, JavaScript uses
// the default return value undefined.
var undefinedValue = Point(12, 34);

// Because we use the "new" keyword JavaScript creates an object and
// uses a reference to the object as the return value of the function call.
var aPoint = new Point(12, 34);


Accessing an object's properties

A property of an object is accessed using the property's name, which is a string. I imagine an object's properties to form a dictionary whose keys are strings, and whose values can be anything. The following two ways for accessing a property are equivalent:

s["length"];
s.length;     // convenient, but mere syntactic sugar

A generic way to access all properties of an object is this:

var anObject = [...]

for (var key in anObject)
{
  var value = anObject[key];
}

// This is equivalent
Object.keys(anObject).forEach(key =>
{
  var value = anObject[key];
});


Methods are function properties

A method is a property of an object that is a function. Since functions are objects, too, the following can be written:

function f1()
{
  console.log("f1 invoked");
}

function Person(firstName, lastName, id)
{
  this.firstName = firstName;
  this.lastName = lastName;
  this.id = id;
  this.f1 = f1;
  this.f2 = function () { console.log("f2 invoked"); };
}

var aPerson = new Person("John", "Doe", 5566);
aPerson.f1();
aPerson.f2();

Notes:

  • The name of a function and of a method do not need to be the same


Everything is visible

JavaScript does not support hiding a property or method by making it "private". JavaScript has no language equivalent for the usual public, protected, private visibility declarations that are common in other programming languages.


Conceptually you can try to guide the users of your class by only providing documentation for the class' public interface. Also common is to prefix a non-public property with an underscore character ("_").


The this keyword

Functions can use the this keyword to access the object in whose context they are executed.

  • If a global function is executed, its this value is the global object. In a browser context, the global object is the browser window, usually referenced via the global variable window. TODO: What's the global object in Node.js?
  • If a function is executed as a constructor (by using the new keyword), its this keyword refers to the object being constructed.
  • If a function is used as an event handler, its this value is the object on which the event fires.
  • If a function is used as a callback, its this value might be anything, including undefined.


The last two bullet points are important when the event handler function is also an object method: You would normally assume that this inside an object method refers to the object, but for the reason stated above this is not true. Here's an example that demonstrates the problem and provides two solutions.

function Person(firstName, lastName, id)
{
  this.firstName = firstName;
  this.lastName = lastName;
  this.identifyYourself = function () { console.log("My name is " + this.firstName + " " + this.lastName); };

  var button1 = [...]
  var button2 = [...]
  var button3 = [...]

  // This does NOT work, when the function is invoked "this" will not be the
  // Person object, it probably will be the empty object {}. Consequently the
  // properties this.firstName and this.lastName accessed by the function will
  // be undefined.
  button1.onclick = this.identifyYourself;

  // Solution 1: Preserve the proper calling context in a local variable that
  // is passed to an anonymous event handler function. This creates a closure.
  var that = this;
  button2.onclick = function(event) {
    that.identifyYourself(event);
  }

  // Solution 2: Use the bind() function added to the language in ECMAScript 2015.
  // This creates a wrapper function around identifyYourself() that invokes
  // identifyYourself() with the supplied calling context.
  // This works in virtually all browser, except IE 8 and older.
  button3.onclick = this.identifyYourself.bind(this)
}


Important: Arrow functions do not have this problem, their this value is lexically bound to the this value of the enclosing execution context.


Prototypes

So far we have merely seen how to construct object literals out of nowhere. Prototypes can be used in a manner that is similar - but not at all the same - as classes in other programming languages. A prototype is an object that serves as the "template" that other objects can build upon.


Why does the following work?

let empty = {};
console.log(empty.toString());


The solution are prototypes:

  • In addition to their set of properties, objects also have a prototype. In the example, the empty object does not have any properties, but it has an unseen, implicit prototype.
  • A prototype is another object that is used as a fallback source of properties. When an object gets a request for a property that it does not have, its prototype will be searched for the property, then the prototype's prototype, and so on.


To get the prototype of an object:

let anObject = ...
Object.getPrototypeOf(anObject);


Prototypes build upon each other, forming a "chain" that eventually ends in a root prototype. Different kinds of "things" have different root prototypes:

  • Object.prototype is the root prototype of all objects, such as the empty object in the initial example of this section. The prototype of Object.prototype is null.
  • Function.prototype is the root prototype of all functions. The prototype of Function.prototype is Object.prototype, which on Node.js prints as {}.
  • Array.prototype is the root prototype of all arrays. The prototype of Array.prototype is Object.prototype, which on Node.js prints as {}.


Object.create is used to create an object that is based on a prototype. Example:

let anObject =
{
  aProperty : "foo",

  // This notation is the one we are used to for defining a method
  aFunction : function() { console.log(this.aProperty); },

  // This notation is a shorthand for defining a method
  aFunctionWithParameter(param) { console.log(param); }
};

let anotherObject = Object.create(anObject);

anotherObject.aFunctionWithParameter("foo");  // prints "foo"
anotherObject.aFunction();                    // prints "foo"

// We change the prototype
anObject.aProperty = "bar";
anotherObject.aFunction();                    // prints "bar"

// We change the derived object
anotherObject.aProperty = "baz";
anotherObject.aFunction();                    // prints "baz"
anObject.aFunction();                         // still prints "bar"


It's possible to create objects with no prototype. This can be useful if you want to stow only your properties in a plain data object, without getting any "inherited" function or other properties in the way.

let noPrototypeObject = Object.create(null);
console.log(noPrototypeObject.toString());    // Error! The object has no prototype and does not "inherit" the usual toString() function.


Let's create an object factory method that uses Object.create:

let protoFoo = ...;

function createFoo(param)
{
  let foo = Object.create(protoFoo);
  foo.aProperty = param;
  return foo;
}

let aFoo = createFoo(42);


Revisiting the new keyword and object constructor functions, we can now combine those with the prototype approach:

function Point(x, y)
{
  // We don't have to use Object.create(), the "new" keyword automatically causes
  // a new object to be created based on the function's prototype (Point.prototype).
  // The object is bound to the "this" keyword.

  // Now we initialize the properties of the object
  this.x = x;
  this.y = y;
  // As we have seen before, methods are merely function properties
  this.printCoordinates = function() { console.log(this.x + " / " + this.y); };

  // We don't have to return a value, the "new" keyword automatically causes
  // the newly constructed object to be returned to the caller.
}

// We can fiddle with the constructor function's prototype from outside the
// the function!
Point.prototype.translate = function(x, y) {
  this.x += x;
  this.y += y;
};

let aPoint = new Point(1, 2);
aPoint.printCoordinates();    // prints "1 / 2"
aPoint.translate(42, 17);
aPoint.printCoordinates();    // prints "43 / 19"

Notes:

  • A function's default prototype is a plain, empty object {}.
  • A function's prototype can be modified (as demonstrated in the example), or even replaced.
  • Constructor function names are capitalized by convention to give them the touch of a class name (class names in other programming languages are capitalized), and to distinguish them from regular functions.


The class keyword

At the end of the previous section we've seen a full example of how a prototype-based object constructor method is declared and defined. With the introduction of ECMAScript 6 in 2015, a new keyword class was added to the language which can be used to simplify the declaration. Note: Internet Explorer does not support the class keyword.

class Point
{
  constructor(x, y)
  {
    this.x = x;
    this.y = y;
  }

  printCoordinates
  {
    console.log(this.x + " / " + this.y);
  }

  translate(x, y)
  {
    this.x += x;
    this.y += y;
  }
}

Notes:

  • A class declaration may contain any number of functions
  • The special function name constructor is used to define the object constructor function
  • The class name defines the name to which the object constructor function is bound
  • All other functions are bound to the prototype of the object constructor function
  • A class declaration cannot contain properties. Properties must be defined either within the object constructor function, or added later by fiddling with the prototype. In the example: Point.prototype.name = "foo";.


A class declaration can also be made in an expression without a name:

let aLogger = new class { logIt() { console.log("hello"); } };
aLogger.logIt();

let loggerConstructor = class { logIt() { console.log("hello"); } };
let anotherLogger = new loggerConstructor();
anotherLogger.logIt();


Inheritance

Together with the class keyword, ECMAScript 6 also added the concept of inheritance to the language: The keyword extends can be used to derive one class from another. Note: Internet Explorer does not support the extends keyword.

class ThreeDPoint extends Point
{
  constructor(x, y, z)
  {
    super(x, y);

    this.z = z;
  }

  translate(x, y, z)
  {
    super.translate(x, y);

    this.z += z;
  }
}

Notes:

  • The use of the keyword extends indicates that a class shouldn't be based on the default Object prototype, but on some other class.
  • The use of the keyword super within a class method lets the method call a method in the superclass.


The call() built-in function

The call() function is a built-in JavaScript function that can be used to invoke a function that "belongs" to object A, but specify a different object B as the owner. The function's this value will be set to object B instead of object A.

var objectA =
{
  value: 1;

  add: function(summand) {
    return this.value + summand;
  }
}

var objectB =
{
  value: 42;
}

objectA.add(7);               // returns 8
objectA.add.call(objectB, 7);  // returns 49


The apply() built-in function

The apply() built-in function works almost the same as the call() built-in function. The difference is that apply() requires that parameters are passed as an array.

Using the same example as in the previous section, we would write this:

objectA.add.apply(objectB, [7]);

The example is contrived and doesn't seem to make much sense. To see the practical value of apply() you merely have to think of a scenario where you do not have individual values at hand to pass as parameters, but an array. If you were forced to use call() you would have to extract the array elements into separate variables that you could pass to call() - a lot of messy code! And it gets worse: What if the array does not have a pre-determined fixed number of elements?


Getter / Setter functions

var o =
{
  a: 7,
  get b() { return this.a + 1; },
  set c(value) { this.a = value / 2}
};

o.a;  // evaluates to 7
o.b;  // evaluates to 8
o.c = 50;
o.a;  // evaluates to 25


Adding/removing properties and functions to objects

New properties and functions can be added to objects dynamically at any time. Actually, this is the same mechanism that is used by object constructors.

var person = new Object();
person.firstName = "John";
person.lastName = "Doe";
person.id = 5566;

function f1()
{
  // do something special here
}
person.f1 = f1

Properties and functions can also be removed using the delete operator:

var person = new Object();
person.firstName = "John";
delete person.firstName;


Adding/removing properties and functions to types

Instead of adding a property/function to just a single object, the property/function can also be added to all objects of a given type. This is done using the special prototype property; the property is available only from the constructor function of the type:

var aPerson = new Person("John", "Doe", 5566);
aPerson.age;                  // evaluates to undefined
Person.prototype.age = null;  // all objects of type Person now gain the property "age" with the initial value null
aPerson.age;                  // evaluates to null


Reflection

// This also checks whether Foo is a superclass
if (anObject instanceof Foo) { ... }


Advanced concepts

Exception handling

try
{
  throw "42";  // any object can be thrown
}
catch (exception if exception instanceof TypeError)
{
}
catch (exception if exception instanceof RangeError)
{
}
catch (exception if exception instanceof EvalError)
{
}
catch(exception)
{
  throw exception // re-throw
}
finally
{
  // do something
}


Closures

This Wikipedia article gives a basic example of what closures are. A simple definition taken from howtonode.org:

“  A closure is a function defined within another scope that has access to all the variables within the outer scope.  „

So, nothing spectacular. Here are some additional notes of mine:

  • Closures are all about where the code of a function is placed in the source code - the so-called "lexical scope". If an inner function is moved outside the enclosing function, it ceases to be a closure. It no longer has access to the local variables of the enclosing function.
  • Closures can be used to preserve the this context, making it possible to write better OO code. I still need to research how exactly this is done, though.
  • What I have understood in regard to callbacks is this: When a callback is made, someone needs to pass data to the callback function. If the data comes from the calling agent, fine. However, what happens if we have gathered some data (= state) at the time when we call an asynchronous function and give up control? If that data/state is no longer needed by the callback, again fine. But what if the data/state is needed by the callback? Who is giving the data to the callback? We could pass the data to the asynchronous function, who would then be responsible for preserving the data and passing it to the callback. This is a bad solution, though, for a number of reasons:
    • It imposes the burden of preserving state to the asynchronous function. If a second, third, etc. asynchronous function is called, state preservation must continue, making the task more and more difficult.
    • The asynchronous function has no way of knowing how the data it preserves is structured, so all that can be done is to pass around the data as one big blob of data. When the callback receives the blob back from the asynchronous function, it must "unpack" individual parts of data from the blob. Very unwieldy and also prone to errors.
  • Closures to the rescue: Not only are variables in the outer scope available to the inner function, they also keep their values! This is a much better way to preserve state.
  • Unfortunately, after a few levels of nesting it starts to look like shit.


Modules

Improvised modules hide internals from the outside world behind a function expression such as this:

const Foo = function() {
  // Here are some internals that we want to keep private and inaccessible

  // Here we return the object with the public interface
  return {
    doThis(aParameter) { ... },
    doThat(aParameter) { ... }
  };
}();

// Here we use the module
Foo.doThis(42);


CommonJS modules are an improved module system which the JavaScript world is currently moving away from, but it's still the most widely used format by packages on NPM. It looks like this:

// ------------------------------------------------------------
// Module definition
// ------------------------------------------------------------
const dependencyModule = require("dependencyModule");

exports.foo = function(aParameter) {
  // do something
  return 42;
};

// ------------------------------------------------------------
// Use the module
// ------------------------------------------------------------
const {foo} = require("foo");

fooResult = foo("bar");

Notes:

  • TODO: Who defines the require() function?
  • TODO: Why do we use braces when we require "foo", but not when we require "dependencyModule"?


ECMAScript modules were introduced in ECMAScript 2015. Here the module system was integrated into the language proper. The JavaScript world is in a transitional state where the new module system is slowly adopted. The basic syntax looks like this:

// ------------------------------------------------------------
// Module definition
// ------------------------------------------------------------
import dependencyModule from "dependencyModule";

export function foo(aParameter) {
  // do something
  return 42;
};

// ------------------------------------------------------------
// Use the module
// ------------------------------------------------------------
import foo from "foo";

fooResult = foo("bar");


Node.js

See separate page LearningNodeJs.


Integration into HTML documents

Embedded script

The script element can be used to embed an entire JavaScript program right inside the HTML document. The element can appear almost anywhere in the document, but people typically place the script at the very end of the body part because when the browser has finished reading the HTML document it will also have the DOM tree ready.

Example:

<body>
  [...]
  <script>
    alert("Hello world!");
  </script>
</body>

Notes:

  • The <script> element must appear inside either the head or the body part
  • The script code executes at the time it is encountered by the browser. See the "Script execution" section how this can be changed for external scripts.
  • In HTML 4.x and XHTML 1.x the style element needs an additional attribute type. It looks like this: type="text/javascript".
  • In XHTML 1.x the content of the script element must be further "adorned" with a CDATA declaration. The declaration has a start and end tag which go on separate lines. In one line it looks something like this: <script type="text/javascript"> // <![CDATA[ ... // ]]> </script>.


External script

You can omit the content of the script element and instead specify an URL to declare a reference to an external JavaScript file. The advantage obviously is that you can reference the same script from multiple documents. The disadvantage is that the browser needs an additional HTTP request to fetch the script contents.

Example:

<head>
  [...]
  <script src="foo.js" defer="defer"></script>
  [...]
</head>

Notes:

  • The src attribute defines the location of the file that contains the script code. It is customary to use the filename extension .js.
  • Multiple scripts can be referenced.
  • Because the script code is located in an external file, the script element must be empty. I'm not sure whether it is a Firefox-specific issue, but it seems as if you are not allowed to use the empty-element syntax, i.e. you must specify a closing tag.
  • The optional defer attribute specifies that the browser must wait with executing the script until it has finished parsing the document. This allows placing the script element in the document head and still have a finished DOM tree ready when the script finally executes.


Event handler

The browser knows many events that fire in reaction to some interaction with the user, or are triggered by some other mechanism. Using HTML attributes it is possible to specify an event handler - a named JavaScript function that should be called when the event fires.

Example:

<body onclick="onclickHandler();">
   [...]
</body>


Note: Try to avoid defining event handlers in this way because it creates a dependency in the HTML markup to the JavaScript code that defines the event handler function. It's better to add event handlers programmatically, typically using the addEventListener function.


noscript element

The noscript HTML element can be used to display something in case that JavaScript has been disabled on the page.


Script execution

By default the browser executes a JavaScript program when it encounters the script code during parsing of the HTML document. This means that if a script is located not at the end of the body it will only have a partial DOM tree available. If the script is an external script, the browser stops processing the document until it has loaded and executed the external script.


External script execution can be modified with the following attributes:

  • If the attribute defer is specified (the value is also "defer"), the browser first finishes parsing the document and executes the script only after that. Multiple scripts that are deferred are executed in the order in which they appear in the document.
  • If the attribute async is specified (the value is also "async"), the browser continues parsing the document and executes the script in parallel as soon as it has finished loading the external script code. This can be very fast in case the script is still cached, or it can be very slow in case the script must first be fetched from the net. Generally async scripts run at an upredictable time. Multiple async scripts are executed in parallel and in an unpredictable order.


The way how a script is executed that is dynamically inserted into the HTML document by another script is browser-specific.


Also see this StackOverflow answer.


JavaScript in the web browser

Browser object

The browser is represented by the global object

navigator

The navigator object is frequently the starting point for accessing various web APIs (e.g. the geolocation API).


Browser window object, functions and properties

The browser window is referenced using the global variable

window


Properties of the window object:

history
The URLs the user has visited. TODO: What's this exactly? An array?
location
The URL in the browser's address bar.


Functions of the window object:

TODO
TODO


Global functions

alert(), confirm()
Displays an alert message with either only an OK button, or an OK + Cancel button.


The console

JavaScript has a console that can be accessed with the global variable

console

To write something to the console, use

console.log()


Event handlers

Add a named event handler (aka event listener) like this:

var element = ...
element.addEventListener("click", myFunction);


An anonymous event handler can be added like this:

var element = ...
element.addEventListener("click", function(e) {
  [...]
});

Notes:

  • The event is "click", not "onclick"
  • Events are not restricted to elements, you can also add event handlers to the window object
  • addEventListener allows multiple event handlers to be added
  • The same function can be added only once. Adding the same function again simply has no effect.


A more simple approach that allows specifying only a single event handler is this:

var element = ...
element.onclick = myFunction

Notes:

  • Assigning to the onclick property in this way will overwrite a handler that was previously assigned in the same way
  • Handlers that were added by addEventListener remain in place and are unaffected by this assigning technique


Event handlers can be removed like this:

var element = ...

// Remove named event handlers
element.removeEventListener("click", myFunction);

// Remove single event handler
element.onclick = null;


Event propagation

An event that occurs for a child element also occurs for the parent element. For instance, a click on a p element also occurs for its containing div element. There are two ways how events can propagate:

  • The "bubbling" method: The event occurs first for the child element and then propagates "upwards" (like bubbles in water) towards the hierarchy root.
  • The "capturing" method: This is the opposite of the "bubbling" method. The event occurs first for the parent element and then propagates "downwards" towards the child element.


When an event handler is added, the propagation method can be specified as a boolean parameter to the addEventListener function. The default is the "bubbling" method.

var element = ...
element.addEventListener("click", myFunction, false);  // bubbling, (the default)
element.addEventListener("click", myFunction, true);   // capturing


Event handler function signature

All event handlers except the one for the error event have this function signature:

foo(event)

The event handler for the error event has this function signature:

foo(event, source, lineno, colno, error)

Notes:

  • this is bound to the DOM element on which the event handler is registered
  • For the error event, the event object actually is the error message as string
  • For all other events, the event object is of type Event. See the MDN docs for a list of all types of events.
  • The event object's target property refers to the object that originally dispatched the event. The object has the type EventTarget, but the object actually is a DOM element. TODO: Is this the same as the event handler's this?
  • The handler's return value determines if the event is canceled. The specific handling of the return value depends on the event type.


Events

The event names in the following lists omit the "on" prefix. For more details see DOM Events over at W3Schools.


Mouse events:

mouseenter, mouseleave, mousemove
Events that occur when the mouse enters or leaves or moves around the screen area occupied by an element
mouseover, mouseout
Events that occur when the mouse enters or leaves the screen area occupied by an element or one of its child elements
mousedown, mouseup, click, dblclick
Events that occur - in order - when the user presses and releases the mouse button. The click event occurs after mouseup. Note: Events occur for all mouse buttons, not just the left button.
contextmenu
Event that occurs when the user right-clicks on an element
wheel
Event that occurs when the mouse wheel rolls up or down over an element


Keyboard events:

keydown, keyup, keypress
Events that occur - in order - when the user presses and releases a key. The keypress event occurs after keyup.


Touch events:

touchstart, touchmove, touchend
Events that occur when a finger is placed on a touch screen, is dragged across or removed from the screen.
touchcancel
Event that occurs when a touch gesture is interrupted


Other events:

load
TODO
focus
TODO
change
TODO
submit
Event that occurs when a form is submitted. A typical way how this event is triggered is when the user clicks a button whose "type" attribute has the value "submit", but there may also other methods.


DOM objects, functions and properties

Objects:

document
The HTML (or XML) document. This is a global variable.
style
The CSS styles of an element. A reference to this object is obtained via the style property of an element node. The style object exposes all CSS properties via dedicated JavaScript properties. For instance, the CSS property "color" is accessible like this: var element = ...; element.style.color = "#fff";. Hyphenated CSS property names are converted into camel case (e.g. backgroundColor represents the CSS property "background-color"). TODO: What is the difference of this to getComputedStyle? A useful article that might help answer the question is this one.


Functions of the document object:

getElementById(elementId)
Returns the single element whose id attribute has the specified value. TODO: What is the return value if no element is found? Where is the search performed (e.g. below the node object on which the method is invoked)? How is the search performed (e.g. recursively)? What happens if several elements exist?
getElementsByTagName(elementName)
Returns a collection of nodes that are elements with the specified name. The search is performed recursively. TODO: See getElementById for open questions. In addition: What is the order of the collection (if there's any)?
getElementsByClassName(className)
Returns a collection of nodes whose class attribute has the specified value. TODO: See getElementsByTagName for open questions.
querySelectorAll(cssSelector)
Returns a collection of nodes that the specified CSS selector matches. TODO: See getElementsByTagName for open questions.
querySelector(cssSelector)
Returns the first node that the specified CSS selector matches. Returns null if no node matches.
createElement(elementName)
Creates and returns a new element with the specified name. The element is not yet part of the document. Invoke this method on the global document object.
createTextNode(text)
Creates and returns a new text node with the specified text. The node is not yet part of the document. Invoke this method on the global document object.
write(text)
Replaces the content of the document with the supplied text. This is usually used as a debugging aid to quickly


Properties of the document object:

docType
The document's doctype
documentElement
The root (html) element
head
The head element
title
The title element
body
The body element
anchors
All a elements that have a name attribute
embeds
All embed elements
forms
All form elements
images
All img elements
links
All area and a elements that have a href attribute
scripts
All script elements
URL
The complete URL of the document
documentURI
The URI of the document
baseURI
The absolute base URI of the document
domain
The domain name of the document server
inputEncoding
The document's encoding (character set)
lastModified
The date and time the document was updated
referrer
The URI of the referrer


Functions of node objects:

getAttribute(attributeName)
Returns the value of the attribute with the specified name. See getElementsByTagName for open questions.
setAttribute(attributeName, attributeValue)
Sets the value of the attribute with the specified name to the specified value. If the attribute exists its value is overwritten, otherwise the attribute is created.
appendChild(nodeObject)
Adds the specified node object as a child to the parent node on which the method is invoked. The specified child node becomes the new last child of the parent node. Use this also to add a text node as a child to an element node.
insertBefore(nodeObject, siblingObject)
Adds the specified node object as a child to the parent node on which the method is invoked. The specified child node is inserted in front of the specified sibling object (which must also be a child of the parent node)
replaceChild(nodeObject, nodeToReplace)
Almost the same as insertBefore, but the first specified node object replaces the second specified node object.
removeChild(nodeObject)
Removes the specified node object from the parent node on which the method is invoked. The specified object must already be a child of the parent.

Properties of node objects:

innerHTML
The content of the node including HTML markup.
innerText (probably)
The content of the node.
firstChild, lastChild
The first/last child node of the node. This can be a text node or a comment node or an element node, depending on the markup.
childNodes
A collection of all child nodes of the node, in the order in which they appear in the markup. The collection is a mixture of text, comment and element nodes.
nextSibling, previousSibling
The next/previous sibling node of the node. This can be a text node or a comment node or an element node, depending on the markup.
parentNode
The parent node of the node. TODO: Can it be said that this is always an element node? Is it possible that text and comment nodes have children?
firstElementChild, lastElementChild, children, nextElementSibling, previousElementSibling, parentElementNode
These are variants of the above properties which return only element nodes. These are extremely useful if you want to ignore the uncertainty of text nodes which may or may not be present depending on whether the HTML markup contains whitespace.
style
Returns an object with all the CSS styles of an element. TODO: What happens when this is invoked on a node that is not an element?


CSS objects, functions and properties

Functions:

getComputedStyle(element, pseudoElement)
A window function. Gets all the actual (computed) CSS properties and values of the specified element. The pseudo-element parameter is optional, specify null if it is not needed. Invoke the function getPropertyValue("css-property-name") on the resulting object to get the value of an individual property. Note: This returns values in absolute units, such as px or rgb().


JSON

Format summary

Example snippet:

{
    "foo": "bar",
    "numberOfSeconds": 12345,
    "isValid": false,
    "anObject": {
        "key1": "value1",
        "key2": "value2"
    },
    "anArrayOfObjects": [
        {
            "key1": "value1",
            "key2": "value2"
        },
        {
            "key1": "value3",
            "key2": "value4"
        }
    ],
    "keyWithNullValue": null
}

Notes:

  • The JSON consists of key/value pairs
  • Keys are always strings
  • Values can have different types
    • String
    • Number: Integer or floating point with optional exponential notation
    • Boolean
    • Null
    • Object: An unordered list of key/value pairs. Objects represent associative arrays.
    • Array: An ordered list of values. Elements can have different types.
  • Key/value pairs are separated with a comma (",")
  • The last key/value pair in a list does not have a comma
  • Usually keys are unique within their scope, although the JSON format does not require this
  • Any type of whitespace is allowed (space, tab, CR, LF)
  • No support for comments


A JSON data fragment can be checked for validity using a JSON linter such as https://jsonlint.com/.


Parsing

The global object

JSON

can be used to parse any JSON data fragment using the parse() function:

var jsonData = ...;
var javascriptObject = JSON.parse(jsonData);

The parse result is a JavaScript object that represents the JSON data.


TODO: Where does JSON come from?


Stringifying

The reverse function of parsing a JSON data fragment (JSON.parse()) and getting a JavaScript object out of this, is to turn a JavaScript object into a JSON data fragment. This is done with the JSON.stringify() function:

var javascriptObject = ...;
var jsonData = JSON.stringify(jsonObject);

The JSON data fragment is a string without any whitespace in it, so it takes up as little space as possible.


jQuery

Where to obtain jQuery?

code.jquery.com lists the CDN links that you can use to obtain a copy of jQuery if you don't want to host it yourself (not hosting it yourself is advisable). You can get jQuery in several variants, the following list shows KB values for jQuery 3.2.1:

  • Uncompressed: 94.53 KB
  • Slim: 75.73 KB
  • Minified: 34.3 KB
  • Slim minified: 27.1 KB

TODO: What's the difference between "slim" and "minified"? Why would anyone not want "slim minified"?


For instance, the standard snippet to include the "slim minified" variant of jQuery 3.2.1 is this:

<script
  src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
  integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
  crossorigin="anonymous">
</script>

TODO: What is integrity? What is crossorigin?


The jQuery object

The global variable

$

is the factory method for the jQuery object.


Invoking a function that does not operate on the jQuery object itself (I think of this as a "static" function):

$.foo(...)


Working with the jQuery object itself:

$(...)


Basic syntax

The basic syntax when you work with jQuery is this:

$(selector).action()

Explanation:

  • As mentioned previously, the character '$' accesses the jQuery object
  • The selector is used to "query" the DOM tree in order to find HTML elements. Among other options, the same selector syntax can be used as for CSS.
  • The action is a jQuery function that is performed on the elements that were the result of the query


A few examples:

$("p").hide();     // Hides all "p" elements
$(".foo").hide();  // Hides all elements with class="foo"
$("#foo").hide();  // Hides the element with id="foo"
$("*").hide();     // Hides all elements
$(this).hide()     // Hides the current element. See example below for concrete use.

The w3schools tutorial on jQuery has a pretty long list of available selectors.


Initial code after document is ready

The following snippet will run foo() as soon as the document has finished loading:

function foo()
{
}

$(foo);

The snippet above is available with modern jQuery versions, but it's actually just an abbreviation for the following syntax that registers an event handler:

function foo()
{
}

$(document).ready(foo);


Chaining

Usually jQuery functions (or "commands") return another jQuery object, which allows to chain function calls like this (example from the jQuery Wikipedia page):

$('div.test')
  .add('p.quote')
  .addClass('blue')
  .slideDown('slow');


DOM tree actions

Inner text/HTML:

// Get or set the inner text
var bar = $("#foo").text();
$("#foo").text("Bar!");

// Get or set the inner HTML
var bar = $("#foo").html();
$("#foo").html("<p>Bar!</p>");


Set attributes:

// Adds the specified classes to the current value of the "class" attribute
$("#foo").addClass("class1 class2");
$("#foo").addClass(function(int indexOfElementInSet, string currentClassValue) { ... });


Retrieve elements from the DOM tree:

// Get the direct child elements of the selected elements
$("#foo").children();

// Get the sibling elements of the selected elements. The result set contains all
// siblings on the same hierarchy level, i.e. all previous and all next siblings.
// The selected element itself is NOT part of the result set.
$("#foo").siblings();
// Get the next sibling elements of the selected elements
$("#foo").next();
// Get the previous sibling elements of the selected elements
$("#foo").prev();

// Get the direct parent elements of the selected elements
$("#foo").parent();
// Get all ancestor elements of the selected elements
$("#foo").parents();


Modify the DOM tree:

// Inserts the specified content as the first child or last child of the
// selected container element. content can be any of the following:
// - a DOM element
// - a text node
// - an array of elements and/or text nodes
// - a jQuery object that references things from the DOM tree
// - a literal HTML string (e.g. <p>Test</p>)
//
// If you specify an element or text node that already exists in the DOM tree,
// the element or text node is moved:
// - If the selector matches exactly one container, the element or text node
//   is moved into that container.
// - If the selector matches more than one container, the element or text node
//   is moved into the last of these containers, for the others a clone of the
//   element or text node is inserted.
$("#foo").prepend(content [, content, ...]);
$("#foo").prepend(function(int index [, string oldHtmlString) { ... });
$("#foo").append(content [, content, ...]);
$("#foo").append(function(int index [, string oldHtmlString) { ... });

// Same as for prepend/append, but insertion occurs as either the
// previous or next sibling of the selected elements
$("#foo").before(content[, content, ...]);
$("#foo").before(function(int index [, string oldHtmlString) { ... });
$("#foo").after(content[, content, ...]);
$("#foo").after(function(int index [, string oldHtmlString) { ... });

// Removes all child content from the selected elements
$("#foo").empty();


Access the actual DOM elements behind the jQuery object:

// Retrieves all selected DOM elements as an array
$("#foo").toArray();
// Retrieves a single DOM element which is at the specified index position
// in the list of selected DOM elements
$("#foo").get(int index);


Adding event handlers

To add an event handler, use the event's name as the action and supply the event handler as the argument. The following example hides any "p" element that the user clicks on. The example also demonstrates the use of the special this selector.

$("p").click(function() {
  $(this).hide();
});


Or use the on() action to achieve the same result:

$("p").on("click", function() {
  $(this).hide();
});


The on() action can be used to specify several event handlers:

// Specify several event names separated by a space character.
// The same event handler applies to all events.
$("p").on("click hover", function() {
  $(this).css("background-color", "red");
});


// Specify different event handlers for different events
$("p").on({
  click: function() {
    $(this).css("background-color", "red");
  },
  dblclick: function() {
    $(this).css("background-color", "blue");
  }
});


The w3schools tutorials have a pretty long list of available HTML DOM events. jQuery also provides a few special "treats". Notably, the "hover" action is a combination of the "mouseenter" and "mouseleave" events and therefore takes two function arguments:

$("p").hover(
function() {
    $(this).css("background-color", "red");
},
function() {
    $(this).css("background-color", "blue");
});


Form interaction

use the val() action to obtain values from form fields. The action returns the value of the first element in a set of matched elements!

// Retrieves the value of a text input control
$("input[type=text][name=foo]").val();

// Retrieves the value of the selected option in a dropdown
$("select#foo option:checked").val();
 
// Retrieves the value from a dropdown select directly
$("select#foo").val();
 
// Retrieves the value of a checked checkbox
$("input[type=checkbox][name=bar]:checked" ).val();
 
// Retrieves the value of the selected radio button in a set of radio buttons
$("input[type=radio][name=baz]:checked" ).val();


Effect and animation actions

jQuery has a number of simple effect actions:

// Both speed and callback are optional.
// Possible speed values are: "slow", "fast", a millisecond value.
// The callback executes when the hide or show effect are complete.
$(selector).hide(speed, callback);
$(selector).show(speed, callback); 

// Toggle switches between shown/hidden
$(selector).toggle(speed, callback);

// Not exactly sure what the difference is between show/hide and fadeIn/fadeOut.
$(selector).fadeIn(speed, callback);
$(selector).fadeOut(speed, callback); 
$(selector).hide(speed, callback);
$(selector).fadeToggle(speed, callback); 

// Both speed and opacity are required, callback is optional.
// Opacity values must be in the range 0-1.
$(selector).fadeTo(speed, opacity, callback); 

// Parameters are optional, and the usual speed values are possible
$(selector).slideDown(speed, callback); 
$(selector).slideUp(speed, callback); 
$(selector).slideToggle(speed, callback); 


You can also create custom animations, best shown with examples:

// General syntax
$(selector).animate({params}, speed, callback);

// Run an animation where several properties are changed at the same time
$("button").click(function() {
  $("div").animate({
    left: '250px',       // set an absolute value
    height: '+=150px',   // set a relative value
    width: 'toggle'      // set a pre-defined value; also possible are "show" and "hide"
    marginRight: '1em',  // CSS property names that contain "-" must be camel-cased
  });
});

// Run several animation one after the other. jQuery queues the animations.
$("button").click(function() {
  var div = $("div");
  div.animate({left: '250px', "slow");
  div.animate({height: '+=150px', "fast");
  [...]
});


Effects / animations can be stopped:

// Both parameters are optional.
// stopAll: If true the entire animation queue is cleared. If false, only the
//          current animation is stopped. The default is false.
// goToEnd: If true the current animation is completed immediately and without
//          any more delay. If false the current animation is stopped. The
//          default is false.
$(selector).stop(stopAll, goToEnd);

The w3schools tutorials have a pretty long list of available effects.


Iterating over arrays

The .each utility function can be used to iterate over arrays, like this:

var array = ...;
$(array).each(function(index, value) {
    console.log(value);
});


AJAX

The w3schools tutorials have a list of available AJAX function.


A basic function is load which loads data from a specified URL and places the result in the element(s) specified by the selector. Optionally data to send with the request can be specified, as well as an optional callback.

$(selector).load(url, dataToSend, function(response, status, xhr));

A more generalized variant to fetch data from a server is the get action, which must be invoked on the jQuery object itself, i.e. without a selector. Note that GET might return cached data.

$.get(url, function(data, status)); 

If you also need to send data with the request, use post.

$.get(url, dataToSend, function(data, status)); 


Requesting data from the network

XMLHttpRequest, basic

The following code snippet (taken from this YouTube video) shows how to send an HTTP request and receive a resulting JSON data fragment.

var xmlhttprequest = new XMLHttpRequest();

xmlhttprequest.onreadystatechange = function() {
    if (xmlhttprequest.readyState !== 4)
        return;
    if (xmlhttprequest.status !== 200)
        return;

    var statusText = xmlhttprequest.statusText;  // human-readable
    var contentType = xmlhttprequest.getResponseHeader("content-type");

    var jsonData = xmlhttprequest.responseText;
    var javascriptObject = JSON.parse(jsonData);

    // [...]
};

// Configure the request - no network connection is opened here yet
var httpMethod = "GET";
var url = "foo.json";
var isAsyncRequest = true;
xmlhttprequest.open(httpMethod, url, isAsyncRequest);

xmlhttprequest.setRequestHeader(...);

var requestBody = null;  // null is fine for GET requests
xmlhttprequest.send(requestBody);

Notes:

  • According to Eloquent JavaScript, XMLHttpRequest is an ancient interface designed by Microsoft for its Internet Explorer in the 1990ies. Netscape later adopted XMLHttpRequest for its own Mozilla browser, using the same name. Today the interface still exists because with Netscape's blessing it became the de-facto standard.
  • The "XML" prefix of XMLHttpRequest comes from the fact that when the response to the HTTP request contains XML, some special processing is performed that parses the XML data and makes the parse result available as a DOM object - much like the document object that represents the HTML document.
  • If the request were not sent asynchronously, then code execution would block after the send() method was invoked
  • The send() method can also be invoked without any parameter at all (instead of specifying a dummy "null")
  • Request headers are not usually set, unless you know what server you're talking to and what headers it will accept
  • Request and response header names are case insensitive
  • The readyState value 4 corresponds to "the request is complete" (cf. this SO answer). Note that this does not mean that the request was successful.
  • The status value 200 is the HTTP status "OK"


These days it is more common to use other events than "readystatechange":

progress
Is periodically invoked to indicate progress of the request
load
Is invoked if the transfer is complete. Check the HTTP status code if the request was successful. 2xx = OK, 3xx = Redirect, 4xx = Client error, 5xx = Server error.
error
Is invoked if the transfer failed
abort
Is invoked if the transfer was cancelled by the user
loadend
Is invoked in addition to "load", "error" and "abort"


jQuery

This short and sweet snippet basically does the same as the code in the previous section. Note that the result of the getJSON function is an object, not a JSON data fragment.

$.getJSON("foo.json", function(javascriptObject) {
  // do something with javascriptObject
});

TODO: What does getJSON do if the request fails?


The following snippet again does the same, but with a little bit more control over the request:

$.ajax({
    url: "foo.json",
    dataType: "json",
    type: "get",
    cache: false,
    success: function(javascriptObject) {
        // do something with javascriptObject
    }
});


XMLHttpRequest, basic, XML data

This is a lot like the first XMLHttpRequest example, except that here we fetch an XML instead of a JSON file.

var xmlhttprequest = new XMLHttpRequest();

xmlhttprequest.onreadystatechange = function() {
    [...]

    var domObject = xmlhttprequest.responseXML;
    var allBarElements = domObject.querySelectorAll("bar");

    [...]
};

var httpMethod = "GET";
var url = "foo.xml";
var isAsyncRequest = true;
xmlhttprequest.open(httpMethod, url, isAsyncRequest);

xmlhttprequest.send();


XMLHttpRequest, elaborate

Here's a code snippet that shows the 4 events that you can listen to if you want complete control over what happens in a XMLHttpRequest:

updateProgressText("Loading data ... ");

var xhr = new XMLHttpRequest();

xhr.addEventListener("progress", updateProgress);
xhr.addEventListener("load", transferComplete);
xhr.addEventListener("error", transferFailed);
xhr.addEventListener("abort", transferCanceled);

var isAsyncRequest = true;
xhr.open(
    "GET",
    "https://jsonplaceholder.typicode.com/photos",
    isAsyncRequest);

xhr.send();

function updateProgressText(progressText)
{
    var progressElement = document.getElementById("progress");
    progressElement.innerText = progressText;
}


This snippet shows how to handle the various HTTP status codes once the HTTP request is complete:

function transferComplete(progressEvent)
{
    var xhr = progressEvent.target;

    if (xhr.status <= 199)
    {
        // TODO: Do we really get here?
        // According to Wikipedia, 1xx codes are "issued on a
        // provisional basis while request processing continues". What
        // does XHR do when it encounters an 1xx code? Alas, the specs
        // are silent on this (https://xhr.spec.whatwg.org).
        updateProgressTextLoadFailed("Informational response " + xhr.status);
    }
    else if (xhr.status >= 200 && xhr.status <= 299)
    {
        hideProgressText();

        var jsonData = xhr.responseText;
        var parsedData = JSON.parse(jsonData);

        addDataToDocument(parsedData);
    }
    else if (xhr.status >= 300 && xhr.status <= 399)
    {
        // The XHR specs imply (although they are not too clear about it)
        // that XHR automatically follows redirects. We still keep this
        // conditional block to handle infinite redirects. See
        // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate.
        updateProgressTextLoadFailed("Redirect error " + xhr.status);
    }
    else if (xhr.status >= 400 && xhr.status <= 499)
    {
        updateProgressTextLoadFailed("Client error " + xhr.status);
    }
    else if (xhr.status >= 500 && xhr.status <= 599)
    {
        updateProgressTextLoadFailed("Server error " + xhr.status);
    }
    else
    {
        updateProgressTextLoadFailed("Unknown HTTP status code " + xhr.status);
    }
}

function hideProgressText()
{
    var progressElement = document.getElementById("progress");
    progressElement.style.display = "none";
}

function updateProgressTextLoadFailed(reasonText)
{
    var progressElement = document.getElementById("progress");
    progressElement.style.color = "red";

    var progressText = "Loading photos failed - " + reasonText;
    updateProgressText(progressText);
}


This snippet updates the progress indictor with a text that shows a percentage. Obviously, whether or not this actually shows anything useful depends on the size of the data that is transferred and the speed of the network connection.

function updateProgress (progressEvent)
{
    if (progressEvent.lengthComputable)
    {
        var percentComplete = progressEvent.loaded / progressEvent.total;
        updateProgressText("Loading photos ... " + percentComplete + "%");
    }
    else
    {
        // There's nothing we can add to the default progress
        // text
    }
}


Last but not least, a snippet with the handlers in case XMLHttpRequest fails completely (TODO: When does this happen?) or is cancelled by the user:

function transferFailed(progressEvent)
{
    var xhr = progressEvent.target;
    updateProgressTextLoadFailed("HTTP status code is " + xhr.status);
}

function transferCanceled(progressEvent)
{
    updateProgressTextLoadFailed("Request cancelled by user");
}


Promises

Promises were added to the ECMAScript standard in June 2015 (ECMAScript 6). This version of the standard is implemented in all major browser engines, except for Microsoft Internet Explorer who is still at ECMAScript 5.1. Microsoft Edge apparently has implemented the majority of features of ECMAScript 6, including promises.


A Promise is an object representing the eventual completion or failure of an asynchronous operation, and - in case of successful completion - its resulting value. See also this MDN page.


A few terms up-front that are important when discussing promises:

Fulfilled
A promise is fulfilled when the asynchronous operation completes successfully
Resolved
Same as "fulfilled"
Rejected
A promise is rejected when the asynchronous operation fails
Pending
A promise is pending if the asynchronous operation is still running
Settled
A promise is settled if it is either fulfilled or rejected (but not pending)
Executor function
The function that starts the asynchronous operation
Resolve handler
The function that the promise executes when the asynchronous operation completes successfully, i.e. when the promise becomes fulfilled.
Reject handler
The function that the promise executes when the asynchronous operation fails, i.e. when the promise becomes rejected.


Here's a code snippet that shows how a Promise object is used:

var url = ...
var completionHandler = ...
var failureHandler = ...

getDataFromUrlAsync(url)
    .then(completionHandler)
    .catch(failureHandler);

Discussion:

  • The function getDataFromUrlAsync returns a Promise object. How this is done is shown in the next code snippet further down.
  • The caller of getDataFromUrlAsync can use the then and catch methods on the Promise object to run either a completion handler or a failure handler
  • The then method actually takes two parameters: then(completionHandler, failureHandler). Both parameters are optional, that's why the code snippet above omits the failure handler parameter when it calls then. Also, the catch method is short for then(null, failureHandler.
  • The completion handler function may return another Promise object that represents another asynchronous operation. If that is the case the caller can chain several calls to then like this: getDataFromUrlAsync(url).then(completionHandler1).then(completionHandler2).then [...] .catch(failureHandler).


Here's the code snippet that shows how a Promise object is instantiated:

function getDataFromUrlAsync(url)
{
    return new Promise(
        function(resolveHandler, rejectHandler)
        {
            var xhr = new XMLHttpRequest();

            var isAsyncRequest = true;
            xhr.open(
                "GET",
                url,
                isAsyncRequest);

            xhr.addEventListener(
                "load",
                function()
                {
                    if (xhr.status >= 200 && xhr.status <= 299)
                    {
                        resolveHandler(xhr.responseText);
                    }
                    else
                    {
                        var errorMessage =
                            "Unable to fetch data from " + url +
                            ". Request status code = " + xhr.status +
                            ", status text = " + xhr.statusText
                        rejectHandler(errorMessage);
                    }
                }
            );

            xhr.addEventListener(
                "error",
                function()
                {
                    var errorMessage =
                        "Unable to fetch data from " + url +
                        ". A network error occurred."
                    rejectHandler(errorMessage);
                }
            );

            // For simplicity's sake we don't add listeners for the
            // "progress" and "abort" events

            xhr.send(null);
        }
    );
}

Discussion:

  • The function that is passed as a parameter to the Promise constructor is called the "executor" function.
  • The executor function starts the actual asynchronous operation.
  • The Promise constructor immediately calls the executor function, passing in two functions as parameters.
  • The first function that the Promise constructor passes ...
    • ... is the resolve handler.
    • The executor function must call the resolve handler when the asynchronous operation completes successfully.
    • The executor function must pass the results of the asynchronous operation as a single value to the resolve handler.
    • When the resolve handler executes, it will set the state of the promise to "fulfilled" and call the first function that was passed to the promise's then() method (see code snippet further up).
  • The second function that the Promise constructor passes ...
    • ... is the reject handler.
    • The executor function must call the reject handler when the asynchronous operation fails.
    • The executor function must pass the reason for the failure (typically an error object) as a single value to the reject handler.
    • When the reject handler executes, it will set the state of the promise to "rejected" and call the second function that was passed to the promise's then() method, or the function that was passed to the promise's catch() method (see code snippet further up).


XMLHttpRequest, POST

Obviously the "POST" method must be specified instead of "GET":

xhr.open(
    "POST",
    url,
    isAsyncRequest);

The "content-type" header must be set to specify what kind of data we are sending. Note that in this example we're specifying JSON which is only allowed to be encoded in one of the many Unicode character sets. UTF-8 is one of them, but we couldn't, for instance, specify ISO-8859-1 (Latin-1).

xhr.setRequestHeader("content-type", "application/json; charset=utf-8");

Finally, we must specify the data as the request body to the send() function:

xhr.send(jsonData);


async/await

A function marked with the keyword async always returns a promise. If the code does not do this explicitly, the return value is automatically wrapped in a resolved promise. Example:

// This ...
async function fooAsync1()
{
  return 42;
}

// ... is the same as this ...
async function fooAsync2()
{
  return Promise.resolve(17);
}

/// ... and both can be called in the same way
fooAsync1().then(alert);  // 42
fooAsync2().then(alert);  // 17

The keyword await can be used inside async functions to suspend code execution until a Promise has resolved and returns a value. Example:

async function barAsync()
{
  let result = await fooAsync1();
  alert(result);
}

Discussion:

  • Actually await merely requires that an object has a callable then() method - the object may not be an actual Promise, but it may be Promise-compatible. I've read the term "thenable" to describe such objects.
  • If the Promise fails, then await throws the object returned by reject(), just as if there had been a throw statement.


CORS

CORS
Cross-Origin Resource Sharing. Wikipedia link. MDN link.

CORS is a web standard that defines mechanisms how a browser client can interact with a server in order to determine whether a client-side script is allowed to access a resource on the server. Typically this is necessary for scripts that don't follow the same-origin policy, i.e. scripts whose origin is not the same server as the one that the script requests the resource from.

Basically the server communicates to the browser client via a standardized set of HTTP headers which origins are allowed to access a restricted resource on the server. A browser client that understands CORS can evaluate these headers and allow or forbid a script to make a HTTP request, accordingly. Under some circumstances the browser client that understands CORS is supposed to make a so-called "pre-flight check" before it allows or forbids a script to make a request. For instance, if a script wants to POST to some resource the browser client will first make an OPTIONS request to the server to find out if the POST request is allowed. If the answer is negative, then the browser client prevents the POST request to even be made.

If a browser client does not understand CORS it will ignore any CORS headers sent by the server and happily let through any requests made by a script. The server in this case is free to deny the request with a 4xx status.


TypeScript

See separate page LearningTypeScript.


Web Components

References

These articles look useful:


Overview

A Web Component is a custom HTML element, such as <hello-world>6lt;/hello-world>. The element name must contain a dash ("-") to never clash with elements officially supported in the HTML specification.

The element is then controlled by a piece JavaScript code. The main part of that is an ES2015 class that extends the HTMLElement interface.

Example:

<foo-component></foo-component>

class FooComponent extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<h1>Hello world</h1>`;
  }
}
    
customElements.define('foo-component', FooComponent);


Shadow DOM

A fragment of "the" DOM can be isolated so that it is no longer accessible via document.querySelector(). The isolated DOM fragment becomes a so-called "shadow DOM". The regular DOM is sometimes also called the "light DOM".

Example how to create a shadow DOM:

<div>
  <style>button { color: green; }</style>
  <div id="example"></div>
  <button id="button">Uses green color</button>
</div>

const shadowRoot = document.getElementById("example").attachShadow({ mode: "open" });
shadowRoot.innerHTML = "<style>button { color: red; }</style><button id="button">Uses red color</button>";


Templates

The HTML element "template" can be used to define a piece of HTML content that is not rendered on its own, but that JavaScript code can use to create other, actual content from by filling in copies of the template with actual data.

Example:

<template id="book-template">
  <li><span class="title"></span> — <span class="author"></span></li>
</template>

<ul id="books"></ul>

const fragment = document.getElementById('book-template');
const books = [
  { title: 'Foo', author: 'An Author' },
  { title: 'Bar', author: 'Another Author' }
];

books.forEach(book => {
  // Create an instance of the template content
  const instance = document.importNode(fragment.content, true);

  // Add relevant content to the template
  instance.querySelector('.title').innerHTML = book.title;
  instance.querySelector('.author').innerHTML = book.author;

  // Append the instance ot the DOM
  document.getElementById('books').appendChild(instance);
});


Recipes

Submitting forms

Remember HTML? A form's action and method attributes define the URL that should be used in the HTTP request when the form is submitted, and the HTTP request type (= the method) that should be used on form submission. If you want to perform your own HTTP request in JavaScript, then leave these two attributes empty.

But how do you know when a form is submitted? Simply register an event handler for the form element's submit event.

Finally, when is the submit event triggered?

  • A form can have a submit button. This takes either the shape of an input element or a button element, for both of which the type attribute must have the value "submit". Activating the submit button in any way (clicking it, hitting the Space key while it has the focus, possibly other methods) will trigger form submission.
  • Alternatively you can omit the submit button and "roll your own". In that case your JavaScript code must invoke the form element's submit() function.


Note: Regardless of how a button is activated, apparently this always triggers the button element's click event. This means that you can rely on click to fire even if the button was not actually clicked on by a mouse or other pointing device. For instance, click also fires if the button was tapped by a gesture on a mobile device, or if it was activated by a keyboard key (Enter or Space key, or a keyboard shortcut defined in the accesskey attribute).


Introducing a delay

This snippet shows a function that delays code execution for some time. It uses a Promise and the async and await keywords. It is very possible that this can be written more economically - the example was adapted from a TypeScript snippet used in an Angular environment.

async waitMilliseconds(numberOfMilliseconds)
{
  return new Promise(resolve =>
  {
    setTimeout(() =>
    {
      resolve()
    }, numberOfMilliseconds);
  });
}

await waitMilliseconds(5000);  // only usable in some async context


Debugger breakpoint

Adding this line to JavaScript/TypeScript code

debugger:

causes the Chrome developer tools to break when the code is executed.