LearningJavaScript
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
- Wikipedia article about JavaScript
- W3Schools tutorial (this is focused on the usage of JavaScript embedded in HTML)
- JavaScript Guide by Mozilla
- JavaScript reference maintained by Mozilla
- Truthy and Falsy: When All is Not Equal in JavaScript. An interesting article about the concept of "truthy" and "falsy". However, you should also read the Boolean part of the Wikipedia article about JavaScript to get a more in-depth explanation of what's going on.
JSON
- Wikipedia article about JSON
- JSON linter
jQuery
- Wikipedia article about jQuery. This also includes a super-short but useful primer on how to use the library.
- Main website
- API docs
- CDN links where to obtain jQuery
- GitHub repository
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
andobj['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
orlet
; you usually want to uselet
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 forlet
declarations.
Finally:
- Omitting the keywords
var
andlet
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. Usinglet
, however, does not create a property on the global object. - Although variables declared with
let
and constants declared withconst
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 of1/0
)
- Everything else evaluates as
true
. This includes an empty object ({}
) andInfinity
.
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 falsefalse
==undefined
results in falsefalse
==NaN
results in falsefalse
=={}
(an empty object) results in falsetrue
==1
results in true
Was any of these results surprising? After the warning, probably not. But what about these?
false
==0
results in truefalse
==results in true
false
==[]
(an empty array) results in trueNaN
==NaN
results in falsetrue
==2
results in false (becausetrue
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 becausetrue
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 isNaN
. The value is not type coerced! For instance,Number.isNaN("foo")
returns false.isNaN(aValue)
: Returns true if the value currently isNaN
, or if it becomesNaN
after a type coercion. For instance,isNaN("foo")
returns true.aValue === aValue
: This is the same asNumber.isNaN(aValue)
, becauseNaN
- and onlyNaN
! - 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 invokingdate.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()
andgetUTCMonth()
. - 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
andlet
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
norlet
), 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;
, thenx === "5"
is false, whilex === 5
is true. - !== stands for "neither value nor type are equal". Given that
var x = 5;
, thenx === 42
is false, whilex === 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. Returnsundefined
if no element is found.findIndex(callback[, thisArg])
: Same asfind
, 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 variablewindow
. TODO: What's the global object in Node.js? - If a function is executed as a constructor (by using the
new
keyword), itsthis
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, includingundefined
.
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 ofObject.prototype
isnull
.Function.prototype
is the root prototype of all functions. The prototype ofFunction.prototype
isObject.prototype
, which on Node.js prints as{}
.Array.prototype
is the root prototype of all arrays. The prototype ofArray.prototype
isObject.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 defaultObject
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 thebody
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 attributetype
. 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 thescript
element in the documenthead
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, theevent
object actually is the error message as string - For all other events, the
event
object is of typeEvent
. See the MDN docs for a list of all types of events. - The
event
object'starget
property refers to the object that originally dispatched the event. The object has the typeEventTarget
, but the object actually is a DOM element. TODO: Is this the same as the event handler'sthis
? - 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 aftermouseup
. 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 afterkeyup
.
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. Thestyle
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 togetComputedStyle
? 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
anda
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, specifynull
if it is not needed. Invoke the functiongetPropertyValue("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 adoptedXMLHttpRequest
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 thedocument
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 aPromise
object. How this is done is shown in the next code snippet further down. - The caller of
getDataFromUrlAsync
can use thethen
andcatch
methods on thePromise
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 callsthen
. Also, thecatch
method is short forthen(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 tothen
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'scatch()
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 callablethen()
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 byreject()
, just as if there had been athrow
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 abutton
element, for both of which thetype
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.