"You have no idea how the situation of the movement and H has literally changed overnight... We've suddenly become 'acceptable' in polite society. People who would previously have given H a wide berth now suddenly have to talk with him."

— Rudolph Hess, quoted in Volker Ullrich's Hitler: Ascent, 1889-1939, writing about the Nazi's first large electoral success. 'H' is Adolf Hitler.

JavaScript Notes

Language notes — ECMAScript 2019

JavaScript Notes

These are my JavaScript notes, covering ES2019. They are here for my convenience, but you are welcome to use them, subject to the terms of the Creative Commons Attribution-ShareAlike 4.0 International License. If you find a mistake, please let me know.

The example code uses a new notation I am developing. More on that soon.

This page includes a two-column print style. For best results, print in landscape, apply narrow margins, and change the Scale setting in your browser's print options to 60%. Firefox and Chrome each have their own set of printing bugs, but one of them usually works.

Contents

Syntax

It is possible to end some lines without semicolons. If a line does end without one, and if the next line begins with a token that is not valid for the start of a line, the preceding line will be interpreted as if a semicolon had been provided. This is called automatic semicolon insertion or ASI. It should be avoided outside of minifiers.

As in other C-style languages, single-line comments in Javascript begin with two slashes:

// Single-line comment

Multi-line comments are surrounded by slash/asterisk pairs:

/* Multi-
   line
   comment */

Identifiers

Identifiers must begin with letters, underscores, or dollar signs. After the first letter, digits can be added.

When strict mode is enabled, JavaScript keywords are forbidden for use as identifiers. The following reserved words are also forbidden:

  • abstract
  • arguments
  • boolean
  • byte
  • char
  • double
  • enum
  • final
  • float
  • implements
  • int
  • interface
  • long
  • native
  • package
  • private
  • protected
  • public
  • short
  • synchronized
  • throws
  • transient
  • void
  • volatile

Primitive types

Primitive types include boolean, number, BigInt, string, and symbol, plus the 'special' types that implement undefined and null. These are copied and compared by value.

Most primitives contain properties, but, unlike objects, they do not allow these to be modified, nor can new properties be added. Primitives also have constructors, and these are typically used for explicit conversions:

let oNum = Number("10.0");

JavaScript does allow primitive constructors to be invoked within new expressions. Doing so creates an object, however, not a primitive instance. The object will have some qualities of the primitive, but it will not be strictly equivalent:

let oObjNum = new Number(10.0);
fAssert(oNum !== oObjNum);

There is generally no reason to use primitive constructors with new.

Booleans

Boolean values are represented with true and false. When evaluated as booleans, falsy values like these are also considered to be false:

  • undefined
  • Zero
  • NaN
  • The empty string
  • null

All other values are considered to be truthy.

Numbers

Number values are represented with double-precision IEEE 754 floats. Until ES2019, JavaScript did not provide an integer type, but all integer values between -253 and 253 retain full precision in the number type. JavaScript defines global variables Infinity and NaN to represent infinite values and undefined numeric results. As in other languages, NaN is usually unequal to every value, including itself. However, Object.is performs a strict comparison that returns true if both arguments are NaN.

Values can be checked for Infinity or NaN with one of several global functions:

isNaN(num)
Returns true if num is NaN, or something other than a number or boolean. Type conversion causes isNaN('') to return false.
isFinite(num)
Returns true if num is neither positive nor negative infinity, and if it is not NaN.

Numbers can be specified with scientific notation. The mantissa and the exponent are separated by E or e:

const gTol = 1.1E-3;

A different base can be specified by prefixing the number:

Prefix Base
0b or 0B Binary
0o or 0O Octal
0x or 0X Hexadecimal

However, non-decimal literals cannot have fractional components.

Strings can be converted to number instances with several global functions:

parseFloat(text)

Trims text of leading whitespace and trailing non-numeric characters, then converts it to a number. Returns NaN if the text is not a valid number. Unlike parseInt, this function does interpret scientific notation.

The Number constructor, by contrast, trims leading and trailing whitespace, but not other non-numeric characters.

parseInt(text)
parseInt(text, base)

Trims text of leading whitespace and trailing non-numeric characters, discards any fractional component, then converts it to a number. Accepts hexadecimal input if the string begins with 0x or 0X, or decimal input if it begins with a non-zero number, or another base between two and 36, if specified. When no base is provided, some implementations interpret strings beginning with zero as octal, while others treat them as decimal. Returns NaN if the text is not a valid number.

Because they mark the exponent with a letter, strings containing scientific notation are not converted properly.

Number class

Number primitives are wrapped by the Number class. Its static members include:

MIN_VALUE
MAX_VALUE
The least and greatest values that can be represented in a double-precision float.
MIN_SAFE_INTEGER
MAX_SAFE_INTEGER
The least and greatest integers that can be represented in a double-precision float without losing precision.
isInteger(num)
Returns true if num is an integer.
isSafeInteger(num)
Returns true if num is an integer and if it can be represented in a double-precision float without losing precision.

Non-static members include:

toString()
toString(base)
Returns a string representation of the number, in decimal, or in the specified base.
toFixed(ct)
Returns a string representation that rounds the number to ct digits after the decimal point.
toPrecision(ct)
Returns a string representation that rounds the number to ct significant digits.
toExponential()
toExponential(ct)
Returns a string representation of the number, in scientific notation. Optionally rounds the mantissa to ct digits after the decimal point.

Math class

The static Math class contains many constants and methods, including those that handle rounding:

floor(num)
ceil(num)
Returns num rounded toward negative or positive infinity.
trunc(num)
Returns num rounded toward zero.
round(num)
fround(num)
Returns num rounded toward the nearest integer or the nearest single-precision float.

Exponents and roots:

E
Euler's number e.
SQRT1_2
SQRT2
The square roots of one-half and two.
exp(exp)
pow(base, exp)
Returns e or base to the power of exp.
sqrt(num)
cbrt(num)
Returns the square or cube root of num.
log(num)
log2(num)
log10(num)
Returns the natural, base-2, or base-10 logarithm of num.

Trigonometry:

PI
cos(rad)
sin(rad)
tan(rad)
Returns the cosine, sine, or tangent of an angle, measured in radians.
acos(num)
asin(num)
atan(num)
Returns the inverse cosine, sine, or tangent of num, in radians, or NaN if num is out of range.
atan2(y, x)
Returns the inverse tangent, in radians, of the slope specified by y and x.

and other functions:

sign(num)
Returns negative one if num is negative, zero if it is zero, and one if it is positive.
abs(num)
Returns the absolute value of num.
min(num, ...)
max(num, ...)
Returns the minimum or maximum of one or more numbers.
random()
Returns a random number greater than or equal to zero, and less than one.

BigInt

In ES2019, true integer values can be represented with BigInt, which has arbitrary length. A BigInt literal is created by appending n to the value:

const oGoogol = 10n ** 100n;

BigInt values cannot be mixed with numbers without explicit conversion, using the Number and BigInt constructors:

let oVal = Number(oGoogol) / 100.0;
return BigInt(oVal);

A BigInt value may lose precision when converted. If it is outside the number range, an infinite value will result.

Strings

Every JavaScript string is a sequence of UTF-16 code points. There is no character type.

String literals can be surrounded by single or double quotes. Quote characters are escaped with a single backslash. Long strings can be split across lines by ending each line with a backslash inside the string:

const gWarn = "No people ever recognize \
their dictator in advance";

Single backslashes in other positions have no effect, and are excluded from the string. String instances can be concatenated with the addition operators + and +=.

Array syntax can be used to read characters, just like charAt. String instances are immutable, however, so the characters cannot be modified:

let oCh = gWarn[0];

Other types define toString methods that are used during string conversions. Overridding this method allows custom output to be produced when the String constructor is used to convert the type:

tPt.prototype.toString = function () {
  return "[" + this.X + ", " + this.Y + "]";
}

let gPt = new tPt(1, 2);
let gText = String(gPt);

Special characters

The usual escape sequences are supported:

Sequence Character
\0 Null
\b Backspace
\f Form feed
\n Newline
\f Form feed
\r Carriage return
\t Tab
\v Vertical tab
\\ Backslash
\' Single quote
\" Double quote

An arbitrary character can be specified by combining \x or \u with a number of hex digits:

Sequence Character
\x XX An element from the Latin-1 character set
\u XXXX A Unicode code point

If \0 is followed by a digit, the digit sequence will be interpreted as an octal number, producing an error if strict mode is enabled, or a possibly unexpected result if it is not. It is safer to specify the null character with \x00.

Template literals

Introduced in ES6, template literals are strings surrounded by backquotes. They function much like interpolated strings in C#:

let oCt = cfRead_Int();
let oText = `Max value: ${(1 << oCt) - 1}`;

Within the literal, a placeholder is defined by surrounding a JavaScript expression with curly braces, and prefixing these with a dollar sign. The expressions are evaluated when the string is defined, their results are converted to strings, and they are inserted within the text.

Template literals can themselves be nested within placeholder expressions:

let oCd = `U${
  (oCt == 1)
  ? "U"
  : `X${(oCt < 0) ? "N" : oCt}`
}`;

Backquote characters can be included by prefixing each with a single backslash. Template literals can also include tabs and line breaks, which are then stored within the string:

const oQuote = `So little pains
do the vulgar take
in the investigation
of truth`;

Unlike C#'s verbatim strings, escape sequences are processed as usual. This processing can be partially avoided by tagging the string with String.raw():

let oPath = String.raw`C:\Temp`;

This does not allow a trailing backslash to be included, however, as that would be interpreted as an attempt to escape the closing backquote.

Tagged literals

A template literal is tagged by prefixing it with the name of a function:

function ofAdd_Dist(aStrs, ...aVals) {
  let oSqs = 0.0;

  let oText = aStrs[0];
  for (let o = 0; o < aVals.length; ++o) {
    oText += (aVals[o] + aStrs[o + 1]);
    oSqs += Math.pow(aVals[o], 2.0);
  }

  oText += `  DIST: ${Math.sqrt(oSqs)}`;
  return oText;
}

let oX = 3, oY = 4;
let oLine = ofAdd_Dist`X: ${oX}  Y: ${oY}`;

This function is invoked when the template literal is evaluated. The function's first argument is an array containing the static portions of the literal, before and after each placeholder. If there are n placeholders, this array will contain n+1 elements, some of them possibly empty strings. Following the array, the function receives n arguments representing the placeholder results before they are converted to strings. The tag function can use this data to generate its result, which need not be a string.

The tag function's first argument also contains a raw property that shows the string content before escape sequences are processed.

String class

String primitives are wrapped by the String class. Its properties and methods include:

length
Returns the number of code units in the string. Most characters are represented with a single code unit, but some require more.
charAt(index)
Returns a string containing the character at the specified index.
substr(start, len)

Returns the substring that begins at start and has length len. If start is negative, it wraps back once around the end of the string.

Note that this method specifies the substring length, unlike the similarly-named substring.

substring(start)
substring(start, next)

Returns the substring that begins at start and stops at the end of the string, or just before next. If either argument is negative, it is considered to equal zero. If either argument is greater than the length, it is considered to equal the length. If start is greater than end, substring acts as if the arguments were reversed.

Note that these methods specify the substring end point, unlike the similarly-named substr. They differ from slice in that arguments do not wrap around the end of the string, and may be reversed. slice is generally preferred.

slice(start)
slice(start, next)
Returns the substring that begins at start and stops at the end of the string, or just before next. If either argument is negative, it is wrapped back once around the end of the string. The extracted string never wraps forward past the end, so arguments greater than the length are treated as if they were equal to the length.
includes(sub)
includes(sub, start)
Returns true if sub is found within the string, searching from the start of the string, or from index start.
startsWith(sub)
endsWith(sub)
Returns true if the string starts or ends with sub.
indexOf(sub)
indexOf(sub, start)
Returns the index where sub first occurs, searching from the start of the string, or from index start. Returns -1 if it is not found.
lastIndexOf(sub)
lastIndexOf(sub, start)
Returns the index where sub last occurs, searching from the end of the string, or from index start. Returns -1 if it is not found.
search(regex)
Returns the index of the first regular expression match within the string, or -1 if no match is found. The global search flag in the expression is ignored.
match(regex)
Returns an array containing substrings matched by a regular expression. If the global search flag is set in the expression, all matches are returned in the array. If the flag is not set, the first array element contains the first match, if any, and the following elements contain substrings matched by capturing parentheses in the expression, if such are defined. If no match is found, the method returns null.
split(delim)
split(delim, max)
Returns an array containing all substrings delimited by delim, which may be a substring or a regular expression. If max is specified, no more than that number of elements are returned.
replace(orig, new)

Returns a new string that replaces string or regular expression orig with substring new. If orig is a string, only the first match is replaced. If it is a regular expression, and if the expression's global flag is set, all matches are replaced.

It is also possible to specify a replacement function for new. The function accepts the matching substring, the substrings matched by capturing parentheses, if any, the match position, and the original string as arguments, and returns the replacement string.

repeat(ct)
Returns a new string that repeats the original ct times.
trim()
trimStart()
trimEnd()
Returns a new string with whitespace removed from one or both ends.
toLowerCase()
toUpperCase()
Converts to a new string containing all lowercase or uppercase characters.
padStart(len)
padStart(len, pad)
padEnd(len)
padEnd(len, pad)
Returns a new string that extends the original's length to len by adding spaces or iterations of pad to the beginning or end. If pad is two or more characters in length, the last iteration may be truncated to fit.

Symbols

ECMAScript 2015 provides the new symbol primitive type. A symbol can be created by invoking the Symbol function:

let gFlagRun = Symbol("Flag");

Note that this is not a constructor, and it cannot be called with new. A description string can be passed to the function, and if it is, that string will be included in the output when the symbol itself is converted to a string. The description has no other effect.

A symbol created this way is unequal to every other symbol, even one with the same description:

let gFlagCache = Symbol("Flag");
fAssert(gFlagRun !== gFlagCache);

Whereas the Symbol function always returns a new symbol, the static Symbol.for method fetches one from the global symbol registry. This method accepts a string parameter to be used as a key within the registry. If a symbol with that key is found, that symbol is returned. If not, a new symbol is created for the key, it is added to the registry, and then returned:

let oCkA = Symbol.for("Ck");
let oCkB = Symbol.for("Ck");
fAssert(oCkA === oCkB);

The static Symbol.keyFor method returns the key that was used to create a registry symbol, or undefined if the symbol is not in the registry.

Additionally, the Symbol class statically defines a number of well-known symbols that are used by JavaScript itself.

Unlike most types, symbols are not converted automatically to strings. Such conversions must be performed explicitly, with the String constructor, or with the Symbol.prototype.toString method. Nor can they be converted to numbers, even explicitly. They can be converted to booleans, but their value is always true.

Symbol-keyed properties

Symbols can be used to add symbol-keyed properties to an object. Each symbol is placed within square braces:

let oMsg = {
  [gFlagRun]: true,
  [gFlagCache]: false
};

The property is also dereferenced with the array syntax:

let oCk = oMsg[gFlagCache];

Because the symbol is unique in value, the resulting property is unique within the object. Assuming the symbol is not shared, this ensures that the symbol-keyed property will never collide with another property, even if the object is handled by a second party. If the symbol is reused in the same object, the second value will take precedence, as would happen if an ordinary property name were reused.

Symbol-keyed properties are not enumerated by for/in loops, or by methods like Object.keys. The static Object.getOwnPropertySymbols method returns an array containing the symbols that have been used to define properties in a particular object.

Special primitive types

undefined and null are unique instances of their own, dedicated types.

undefined is assigned to variables that have been declared but not initialized, among other things. If an attempt is made to read a variable that has not been declared, the browser will produce a ReferenceError that describes the variable as 'not defined'. This should not be taken to mean that the variable is undefined. Instead, the variable does not exist at all.

null represents the absence of a value, as it does in languages with pointers. null is equal to undefined when compared with the == operator.

Objects

An object is a set of zero or more properties. Technically, every instance that is not a primitive type is an object. Even functions are objects, and these can define their own properties, including other functions. Object variables are references, and objects are copied and compared by reference.

Many global values are members of the global object, which is created when the interpreter starts. This includes global properties like undefined and NaN, functions like isNaN, constructors like String, and globals defined in the script with var. Within a browser, the Window instance is the global object. In ES2019, globalThis can be used from any scope to reference the global this value, which often points to the global object.

An object can be serialized by passing it to JSON.stringify, which returns a string containing a JSON representation. The object can be deserialized with JSON.parse. JSON represents data with a subset of the JavaScript object literal syntax.

Properties

Superficially, properties resemble class or structure members in other languages. Each property associates a string or symbol key with a value:

let oRack = {
  Num: oNumNext,
  Cap: 8
};
...
let oCtAvail = oRack.Cap - oCtUsed;

However, an entirely new property can be added to the object just by assigning to it:

oRack.Name = "WEST";

Property names need not qualify as identifiers. In fact, if it is quoted, any string can serve as a name, even the empty string. Other strings can be quoted in the definition, or not:

let oLook = {
  "Num": 10,
  "Site E": 108,
  "Site F": 90,
  "": 0
};

In this sense, objects are more like associative arrays than traditional class or structure instances. If a particular name is not a valid identifier, it must be dereferenced with the array syntax:

let oCd = oCdsFromName["Site E"];

This syntax also allows the name to be specified with a variable:

let oKey = "Num";
let oNum = oRack[oKey];

When using the array syntax, nested objects:

let gPrefs = {
  Def: { Name: "New", LenMax: 10 }
};

are accessed by concatenating dereference operators, just as a nested array element would be:

gPrefs["Def"]["Name"] = "OBSOLETE";

Because arrays and functions are objects, properties can be added to them in the same way. Adding a property to an array does not change its length unless the name is a valid index that is outside the current index range.

If a property is defined with a variable, the name can be omitted. When this is done, the property takes its name from the variable:

let oPos = { X, Y };
fOut(oPos.X);

Accessor properties

JavaScript can define getters and setters that are read and written like ordinary class variables, but are backed by functions. Properties defined this way are known as accessor properties.

Accessors are declared in object literals by prefixing the backing functions with get and set. Although overloading is normally not supported in JavaScript, both functions are named after the property they define:

let oRef = {
  Num: 0,
  get Cd() { return "F" + this.Num; },
  set Cd(a) { this.Num = a.substring(1); }
}

The setter accepts a single parameter that represents the r-value in a property assignment:

oRef.Cd = "F10";

Omitting the getter produces a read-only property, and omitting the setter produces one that is write-only. Accessors are inherited like other methods. If a setter is called from a child object, any property it sets will be added to the child, thus hiding the parent value.

Accessors can be added to existing objects with Object.defineProperty or Object.defineProperties.

Property attributes

Properties have attributes that determine whether they are enumerable, whether they can be reconfigured or deleted, and whether their values can be changed. Attributes are also used to add accessor properties to existing objects.

The attributes for a single property can be set with Object.defineProperty, which accepts the object, the name of the property, and a property descriptor:

Object.defineProperty(oRef, "Cd", {
  get: function () { return "F" + this.Num; },
  set: function (a) { this.Num = a.substring(1); },
  enumerable: true,
  configurable: true
});

If the property already exists, and if it is configurable, it will be modified. If it does not exist, it will be created.

Multiple properties can be configured by replacing the property name and descriptor with a second object that associates names with descriptors:

Object.defineProperties(oRef, {
  Cd: {
    get: function () { return "F" + this.Num; },
    set: function (a) { this.Num = a.substring(1); },
    enumerable: true,
    configurable: true
  },
  Rank: {
    get: function () {
      return (this.Num <= 3 ? "A" : "B")
    }
  }
});

A descriptor can be retrieved by passing the object and property name to Object.getOwnPropertyDescriptor. The object must be the one that originally defined the property, not a descendent.

A descriptor is an object with up to four properties, each defining a specific attribute. A data descriptor configures an ordinary, non-accessor property:

Attribute Value
value The property's starting value.
writable Set to true if the value can be changed. When false, the only way to change the value is to reconfigure it with Object.defineProperty or Object.defineProperties. Even setters in the same object cannot change read-only values. Normally, writing to an inherited property creates a new property in the child, leaving the parent unchanged, but even this is disallowed for read-only inherited properties.
enumerable Set to true if the property can be enumerated by for/in loops or functions like Object.keys.
configurable Set to true if the property can be configured by another call to Object.defineProperty or Object.defineProperties, or if it can be deleted. Attempting to reconfigure a non-configurable property produces a TypeError.

An accessor descriptor configures an accessor property:

Attribute Value
get The accessor's getter implementation.
set The accessor's setter implementation.
enumerable Controls enumerability, as above.
configurable Controls configurability, as above.

When creating new properties, value, get, and set default to undefined, while writable, enumerable, and configurable default to false. If neither value, writable, get, nor set are specified, the object is assumed to be a data descriptor.

When reconfiguring properties, unspecified attributes are left unchanged.

Testing properties

The existence of a property can be tested in several ways. The property can be strictly compared with undefined:

let oCkCd = (aParams.Cd !== undefined);

but this fails to distinguish undeclared properties from those that have been explicitly set to undefined.

The in operator accepts a string operand on the left and an object on the right. It returns true if the string is the name of a property, inherited or otherwise, whether its value is undefined or not:

let oCkCd = "Cd" in aParams;

Because array elements are essentially properties, it also returns true if the number is a valid index for the array:

let oCk = ax in aVals;

The Object.hasOwnProperty method returns true if its string argument names an own property, rather than one that is inherited. The Object.propertyIsEnumerable method also returns true for own properties, as long as they are also enumerable.

Enumerating properties

The for/in loop iterates the names of enumerable properties within some object, including those of inherited properties. The static Object.keys method also identifies enumerable properties, but it returns an array of names, and it excludes inherited properties, while the Object.values method returns an array containing the associated values. The static Object.getOwnPropertyNames method returns a similar array, but non-enumerable properties are included. None of these methods return symbol-keyed properties.

Deleting properties

The delete operator removes a property from an object. The property can be indicated with the dot notation:

delete aParams.Cd;

or the array notation:

delete aParams["Cd"];

The operator can also target an array element:

delete oEls[10];

Deleting an element does not change the array size, at least not as reported by length; it merely sets the element to undefined. However, if the array is iterated with a for/in loop, the deleted element will be skipped.

If the targeted property or element does not exist, delete will fail silently. Inherited properties cannot be deleted through the child. They must instead be deleted through the parent to which they were added.

with

Passing an object to with causes identifiers in the statement or block that follows it to be interpreted as properties of that object, if possible:

with (oData) {
  St = "ACT";
  fAlloc(++xAct);
  ...
}

Using with is generally discouraged, and it is disallowed in strict mode.

Creating objects

Object literals

Objects can be initialized with object literals, which contain property/value pairs within curly braces. Each property is separated from its value by a colon. Property/value pairs are separated by commas:

let oRt = {
  Region: gRegionDef,
  Zone: 0
};

Omitting the properties produces an empty object:

let oRack = {};

Creating an object this way causes Object.prototype to be assigned as the object's prototype, and Object as its constructor.

Constructors

A constructor is a special function that initializes new objects. It is invoked much like any function, but it is preceded by the new keyword:

let oRg = new tRg(2, 8);

This causes an empty object to be created and passed to the constructor, where it is referenced with this. Unlike other functions, if the constructor has no parameters, its parentheses can be omitted during the call.

Two more values are automatically assigned in the new object: its prototype, which is hidden, and its constructor property. Like other functions, the constructor has a prototype property, and that value is used as the object's prototype. Furthermore, the instance referenced by prototype has, by default, a constructor property that refers back to the constructor function. That value is copied to the new object's constructor.

Note that a constructor's prototype property does not reference the constructor's own prototype! Like other objects, functions have hidden prototypes that are accessible through __proto__, and these directly or indirectly reference Function.prototype. This prototype chain implements functionality specific to the constructors, however. The prototypes referenced by the prototype properties (and ultimately by the class instances) implements functionality specific to the class.

Constructors are not meant to return values. If an object is returned, the first constructed object will be replaced with the returned value. If a non-object value is returned, it will be ignored.

Object.create

Objects can also be instantiated with the Object.create method. This method accepts an argument that specifies the object's prototype:

let oData = Object.create(tData.prototype);

The new object's constructor property will be set to the constructor of the specified prototype, but that function will not be called. As shown below, this is useful when defining subclasses.

An optional second argument can be used to define one or more properties. Like Object.defineProperties, this parameter accepts an object that maps property names to property descriptors:

oRef = Object.create(Object.prototype, {
  Cd: {
    get: function () { return "F" + this.Num; },
    set: function (a) { this.Num = a.substring(1); },
    enumerable: true,
    configurable: true
  },
  Rank: {
    get: function () { return (this.Num <= 3 ? "A" : "B") }
  }
});

Object attributes

A non-extensible object is one that does not allow new properties to be added. A sealed object is one that is non-extensible, with properties that are non-configurable as well. A frozen object is sealed and contains only read-only properties.

These qualities are checked with functions like Object.isExtensible, Object.isSealed, and Object.isFrozen. They are applied with Object.preventExtensions, Object.seal, and Object.freeze. They can also be applied by manually configuring property attributes. A non-extensible object cannot be made extensible again. Neither can sealed or frozen objects be unsealed or unfrozen.

Classes

JavaScript uses prototypal inheritance, which resembles traditional OOP inheritance only vaguely. In other languages, classes are compile-time abstractions, to be instantiated at run time as concrete instances. In JavaScript, inheritance for a given object is determined by its prototype, which references a concrete instance, or null if the object does not inherit. A class in JavaScript is simply the set of objects that share a given prototype.

An object's prototype is not directly accessible. However:

  • It can be retrieved by passing the object to Object.getPrototypeOf. It can be set by calling Object.setPrototypeOf; however, replacing a prototype by any means is discouraged for performance reasons;
  • Most browsers support the non-standard __proto__ accessor property, which allows it to be read or written;
  • The same prototype can be retrieved from the prototype property of the class constructor.

When a property is accessed, it is first sought in the referencing object. If the property has not been added to the object, it is sought within the object's prototype. The prototype is another object; if it does not contain the property, its prototype is checked, and so on, until the property is found, or until a null prototype is encountered. In this way, every object inherits all the properties of its ancestors throughout the prototype chain, while also allowing properties to be overridden in derived classes. Therefore, a function is shared among class instances by adding it to the prototype. Properties that are not inherited are called own properties.

Note that the child does not inherit copies of the ancestor properties; it links in the most literal sense to the parent and its current state. If a property changes in the parent, the same change will be observed in every child. However, assigning to that property in the child creates a new property that hides the original, unless the inherited property is read-only. Similarly, if the property is an inherited accessor with a setter, that setter will be called, but it will produce a new property in the child that hides the parent value.

Every object also starts with a constructor property, and, typically, all objects in a given class will reference the same constructor instance. This allows class-static variables and methods to be defined in and accessed through the constructor. Static values cannot be added to the prototype, as that would cause them to be inherited, and modifying them would produce different values in different instances.

To summarize:

  • Non-static variables are added to the object, and this is typically done inside the constructor;
  • Non-static methods are added to the constructor prototype, which becomes the object prototype;
  • Static variables and methods are added to the constructor.

A type subclasses another when its prototype inherits from the prototype of the superclass. The relationship between a non-function object and its prototype is akin to that between a concrete instance and its most-derived type in a traditional OOP language. The links between the various prototypes define the inheritance hierarchy that would be found in a traditional language.

Manual class setup

In versions before ES6, class setup is a largely manual process. An object's prototype is assigned when the object is created. Every function has a prototype property that references a default prototype object. When the function is used as a constructor, the new object is automatically made to reference the prototype object:

function tRg(aMin, aMax) {
  this.Min = aMin;
  this.Max = aMax;
}
let oRg = new tRg(100, 105);

Because every function has this property, every function can theoretically be used as a constructor, though seldom to useful effect.

Along with its prototype, every object has a constructor property that is meant to reference the constructor that created it. The default prototype for a given function contains a non-enumerable constructor property that references the function itself, and this value is copied to the new object. Replacing the function's prototype object can break this link:

tRg.prototype = {
  fLen: function () {
    return this.Max - this.Min;
  },
  ...
};

The constructor property can be restored manually, or new properties can be added to the original prototype instance:

tRg.prototype.fLen = function () {
  return this.Max - this.Min;
};

Static members are added directly to the constructor:

tRg.sfFromUnord = function (aL, aR) {
  if (aL < aR) return new tRg(aL, aR);
  return new tRg(aR, aL);
}

Subclasses require additional setup. A new subclass prototype stores properties to be shared by the subclass instances:

function tStore(aName) {
  this.Name = aName;
}

function tStoreFile(aName, aPath) {
  tStore.call(this, aName);
  this.Path = aPath;
}
tStoreFile.prototype = Object.create(tStore.prototype);
tStoreFile.prototype.constructor = tStoreFile;

tStoreFile.prototype.fWrite = function (aData) {
  ...
}

Instantiating the prototype with Object.create assigns the superclass prototype without invoking its constructor, which would add unwanted properties to the subclass prototype. Instead, superclass properties are added to subclass instances by the subclass constructor, which uses call to invoke the superclass constructor. Because the default prototype is overwritten by Object.create, the constructor property must be restored manually.

Class declarations and expressions

Starting with ES6, classes can be defined with a syntax that resembles other languages. This is called a class declaration:

class tRg {
  constructor(aMin, aMax) {
    this.Min = aMin;
    this.Max = aMax;
  }

  fLen() {
    return this.Max - this.Min;
  }

  * Vals() {
    yield this.Min;
    yield this.Max;
  }

  get Ck() {
    return !isNaN(this.Min) && !isNaN(this.Max);
  }

  static sfFromUnord(aL, aR) {
    if (aL < aR) return new tRg(aL, aR);
    return new tRg(aR, aL);
  }
}
tRg.sTol = 0.001;

The constructor is defined with the constructor keyword, which stands in for the class name in an ES5 constructor. The constructor can be omitted from the class if it is not needed.

All code within the class is executed in strict mode. Methods are defined with ordinary function declarations, but the function keyword is not included. Generator methods are prefixed by an asterisk. Accessor properties are defined by prefixing their definitions with get or set. Static methods and properties are prefixed with static. As expected, non-static methods are added to the class prototype, while static methods are added to the constructor. Static variables must be defined outside the class declaration, just as they always were.

Classes can also be defined with class expressions, which work something like function expressions:

const tPt = class {
  constructor(aX, aY) {
    this.X = aX;
    this.Y = aY;
  }
  fLen() {
    const oSqs = (this.X * this.X) + (this.Y * this.Y);
    return Math.sqrt(oSqs);
  }
};

let oPt = new tPt(3, 4);

The constructor function is returned by the expression and assigned to a variable. That variable then serves as the constructor, so its name becomes the name of the class. It is also possible to specify a name after the class keyword:

const tPt = class PtClass {
  ...
};

When this is done, the second name is assigned to the constructor's name property. Otherwise, the variable name is assigned.

A subclass is defined by specifying a parent class with the extends keyword:

class tStore {
  constructor(aName) {
    this.Name = aName;
  }
}

class tStoreFile extends tStore {
  constructor(aName, aPath) {
    super(aName);
    this.Path = aPath;
  }

  fWrite() {
    ...
  }
}

The child constructor must invoke the parent constructor with super before using this.

When this syntax is used to define a subclass, the subclass constructor prototype is made to reference the superclass constructor, rather than Function.prototype, as most functions do. This produces a constructor prototype chain that parallels the chain used to implement the class functionality. This constructor inheritance relationship is not typically implemented when subclasses are defined manually.

The new class syntax can also be used to derive from classes that were defined manually:

function tStore(aName) {
  this.Name = aName;
}

class tStoreFile extends tStore {
  ...

Testing inheritance

An object's class is determined by its prototype. The instanceof operator accepts an instance on the left and a constructor on the right:

let oCkStore = aStore instanceof tStore;

It returns true if the instance is an object, and if it inherits from the object referenced by the constructor's prototype property, whether directly or indirectly. Adding non-class properties to the tested object does not change this result. By extension, the operator typically returns true if the right operand is Object, since all objects inherit from Object.prototype by default. Similarly, arrays can be identified by setting the right operand to Array. However, an instance occasionally originates in a different realm, this being a frame or other context within which JavaScript code executes. If the instance is from another realm, instanceof will not identify its class correctly.

The isPrototypeOf function also indicates whether one object is the ancestor of another, though it is invoked on the prototype, and receives the child as an argument:

let oCkStore = tStore.isPrototypeOf(aStore);

Type conversion

JavaScript is permissive about type conversions, and most values are automatically converted to other types when required:

  • undefined produces NaN when converted to a number, while null produces zero. Both produce false when converted to a boolean;
  • true produces one and false produces zero when converted to a number;
  • Numbers are automatically converted to strings. Zero and NaN values produce false when converted to booleans, while other numbers produce true;
  • The empty string is converted to zero or false, as required. Strings that contain valid decimal numbers are converted to those numbers automatically, while other strings produce NaN. The empty string produces false when converted to a boolean, while non-empty strings produce true;
  • Empty arrays produce the empty string when converted to strings, while non-empty arrays produce comma-delimited lists of their elements. Nested arrays are not represented correctly, however. Empty arrays produce zero when converted to a number. Arrays containing a single number or numeric string produce that number when converted to a number, while multi-element arrays and those containing non-numeric elements produce NaN. All arrays produce true when converted to booleans;
  • Objects produce their toString result when converted to strings, or their valueOf results when converted to numbers. Overriding toString allows custom string output to be produced when the conversion is performed with the String constructor. Objects always produce true when converted to booleans.

These conversions are used when comparing values with the == operator, so undefined is equal to null, "1" is equal to one, and "0" is equal to false. When a string is added to any other type with the addition operator +, the non-string type is converted to a string. In some cases, string conversion occurs even when neither operand is a string. Adding two arrays with the addition operator, for instance, causes both to be converted to strings and then concatenated.

Explicit conversions are performed by passing values to the Boolean, Number, String, or Object constructors, or with functions like parseFloat and parseInt. For values other than undefined and null, a string can also be produced by invoking the value's toString method.

Variables

JavaScript variables have no type, so a value of one type can be overwritten with another type at any time.

In strict mode, a variable must be declared before it is assigned, or an error will result. In sloppy mode, assigning an undeclared variable automatically declares it within the global object, even if the assignment occurs in a function. Reading from an undeclared variable always produces an error. Undeclared variables can also be deleted from the global object, while declared variables cannot.

Variables can be declared with var, let, or const.

var

In ES5, variables are declared with the var keyword. Multiple variables can be declared and optionally initialized in the same line by separating them with commas:

var oX = 0.0, oY;

Uninitialized variables have the undefined value. Redeclaring a variable that was created with var has no effect. If the new declaration also initializes the variable, it is simply assigned with the new value.

When declared outside of any function, a var variable is created as a property of the global object.

When declared inside a function, such a variable is hoisted, giving it function scope. This makes it accessible outside the containing block, and even before the variable is declared, at which point its value is undefined:

function fExec(aCkCalc) {
  gWgt = oWgt;
  if (aCkCalc) {
    var oWgt = 0.0;
    ...
  }
}

This also applies when var is used within a for loop:

for (var ox = 0; ox < oCt; ++ox) {
  ...
}
let oxMatch = ox;

let index variables, by contrast, cannot be accessed before or after the loop.

var should be avoided in modern JavaScript.

let and const

Variables declared with let behave like those in other languages. When declared in a function, they have block scope, so they are inaccessible outside the containing block, and they cannot be accessed before the declaration. They have global scope when declared outside a function, but they do not add properties to the global object.

When using let, redeclaring a variable in the same scope always produces a SyntaxError. Nothing prevents the same variable from being declared in a contained block, however, and when this is done, the inner declaration hides the outer one.

const behaves as let, but it creates read-only variables that produce errors when reassigned. Objects referenced by const variables can be modified as usual. const variables must be initialized where they are declared.

Destructuring

Starting with ES6, destructuring allows one or more values to be extracted simultaneously from an iterable or an object. This is accomplished by defining a pattern that matches some or all of the source instance's structure. When the source is an array or other iterable, the pattern resembles an array literal:

const oCds = ["AA", "BB"];
const [oCd1, oCd2] = oCds;

When the source is an object, the pattern resembles an object literal:

const oTag = { Name: "BASE", Ct: 10 };
const { Name: oName, Ct: oCt } = oTag;

In both cases, destination variables are placed where values would be found within the source. Property names can be quoted in the pattern, just as they are sometimes quoted in object literals:

const { "Name": oName, "Ct": oCt } = oTag;

Property names can also specified indirectly, with the array syntax:

const oKeyName = "Name";
const oKeyCt = "Ct";
const { [oKeyName]: oName, [oKeyCt]: oCt } = oTag;

If a variable name matches the property name in a source object, that property name can be omitted:

const oShelf = { Loc: "C10" };
const { Loc } = oShelf;

Destructuring can be used when initializing new variables, or when assigning existing ones. If an object pattern is used within an assignment, the entire statement must be placed within parentheses. This prevents the pattern from being interpreted as an object literal:

let oCity = null;
if (oCkUpd) {
  ({ City: oCity } = oPlace);
  ...

Destructuring can also be used to extract function parameters:

function fUpd({Name: aName, Ct: aCt}) {
  ...
}

const oTag = { Name: "BASE", Ct: 10 };
fUpd(oTag);

The pattern can ignore properties or elements that are not needed. In particular, holes can be left in an array pattern to extract specific elements while ignoring others:

const oIDs = ["01", "22", "30", "31", "33"];
const [oID0, , oID2] = oIDs;

Alternatively, because an array is itself an object, it can be matched with an object pattern that uses array indices as property names:

const { 0: oID0, 2: oID2 } = oIDs;

If the pattern attempts to extract a property or element that does not exist, the corresponding variable will be undefined, unless a default value is assigned in the pattern:

oAct = {};
const { Path: oPath = "/", Exec: oExec = null } = oAct;

Much like a rest parameter, the last pattern variable can be prefixed with an ellipsis to extract trailing values. If the source is an iterable, the extra values will be returned as an array:

const oIts = [ 1, 1000, 1010, 1011 ];
const [oItPref, ...oItsEx] = oIts;
const oCtItsEx = oItsEx.length;

If the source is an object, the extra values will be returned as an object:

const oRt = { Zone: "F80", Drive: 1008, CtOrd: 12 };
let { Zone: oZone, ...oDtl } = oRt;
fUpd(oDtl.Drive, oDtl.CtOrd);

Patterns can be nested to extract values from complex types:

const oFig = {
  ID: 102,
  Pts: [
    { X: 0, Y: 0 },
    { X: 10, Y: 12 }
  ]
};
const { Pts: [, { X: oX1 }] } = oFig;

Among other things, destructuring can be used to exchange values without temporary variables:

let oxBase = 0, oxAux1 = 8, oxAux2 = 10;
[oxBase, oxAux1, oxAux2] = [oxAux1, oxAux2, null];

Control structures

if

The if statement automatically converts its argument to a boolean. Because undefined and null are both falsy, this allows the validity of an object reference to be checked very simply:

if (aObj) ...

switch

switch statements can branch on conditional values of any type. case values can be defined with run-time expressions, and the two are compared using strict equality.

case blocks that do not break or return fall through to the next block.

for loops

Basic for loops work as they do in other languages:

for (let ox = 0; ox < oCt; ++ox) {
  ...

The index variable should not be declared const. If it is, the loop will fail after the first iteration.

for/of

Introduced in ES6, the for/of loop iterates any iterable object. This includes strings, arrays, maps, and sets, among others:

const oMsg = "EX23A";
for (const oCh of oMsg) {
  ...

Unlike basic for loop variables, for/of variables can be declared const. The loop variable is copied from the container, so if it is declared with let, assigning to the variable will leave the container unchanged.

Destructuring can be used to extract several loop variables with each iteration:

for (const [ox, oEl] of oEls.entries()) {
  let oLine = `${ox}: ${oEl}`;
  ...

for/in

The for/in loop iterates the indices of an array:

for (const ox in oEls) {
  if (fCk(oEls[ox])) ...

for/in can also iterate the enumerable properties of an object, including those that were inherited. As before, it iterates keys rather than values:

for (const oName in oData) {
  const oLine = oName + ": " + oData[oName];
  ...

for/in variables can be declared const.

Labels

Normally, the break statement causes the innermost loop or switch to end, while continue causes the innermost loop to iterate. An outer loop can be targeted by prefixing the loop statement with a label, consisting of a label name followed by a colon:

zMain: while (true) {
  zBls: for (let oxBl in oBls) {
    for (let oxMsg in oMsgs)
      switch (oMsgs[oxMsg]) {
        case "HOLD": continue zBls;
        case "DONE": break zMain;
        ...

If break is followed by a label name, the specified loop will end. If continue is followed by a name, that loop will iterate.

Operators

Logical operators

JavaScript offers the usual logical operators:

Operator Effect
! Logical complement
&& Logical AND
|| Logical OR

Neither && nor || necessarily returns a boolean value. If the left && operand is falsy, the operator immediately returns that value. If the operand is truthy, it returns the right operand, whether that happens to be truthy or falsy. Conversely, if the first || operand is truthy, it returns that value, otherwise it returns the second operand.

Because undefined is falsy, the || operator can be used to select the first defined value from a set of identifiers:

function fExec(aCt) {
  let oCt = aCt || this.CtDef || 0;
  ...

Because the ! operator always returns a boolean, !! can be used to convert truthy or falsy values to boolean equivalents.

The ternary conditional operator ?: works as in other languages, though it is also capable of returning two different types.

Bitwise operators

The bitwise operators work as they do in other languages, but the operands are treated as 32-bit integers. Integer bits outside this range are discarded, as are fractional components:

Operator Effect
~ Bitwise complement
& &= Bitwise AND
| |= Bitwise OR
^ ^= Bitwise XOR
<< <<= Left shift
>> >>= Right shift with sign
>>> >>>= Right shift with zeros

Right shift with sign conserves the high-order bit, so it effectively divides by powers of two, even if the left operand is negative. Right shift with zeros inserts zeros instead. The right operand of all shift operations must be between zero and 31. Negative right operands cause the operator to return zero, while operands greater than 31 are treated as operand % 32.

JavaScript supports the compound assignment operators found in other languages. These include bitwise operators &=, |=, ^=, <<=, >>=, and >>>=,

Arithmetic operators

The arithmetic operators function mostly as expected:

Operator Effect
+ += Addition
- -= Subtraction
* *= Multiplication
\ \= Division
% %= Modulus
** **= Exponentiation (ES2016)

However:

  • Because of JavaScript's aggressive approach to type conversion, the unary plus operator + can be used to convert non-numeric types to numbers;
  • The modulus operator % also works with float values, and, when it produces a remainder, its sign matches that of the first operand;
  • Unary expressions cannot used on the left side of the exponentiation operator. For this reason, negative literals must be parenthesized:

    let oMask = (-2) ** oExp;
    

JavaScript also offers the increment ++ and decrement -- operators found in other languages. They can be used as prefix or postfix operators.

Equality and comparison operators

The loose equality operators == and != check for general equivalence, so various type conversions are allowed. By contrast, the strict equality operators === and !== return false if the operands have different types. When applied to arrays, functions, and other objects, both varieties compare references, so distinct but otherwise identical instances are not equal. There is no operator that tells whether distinct objects or arrays contain the same properties and values.

Like the loose equality operators, the comparison operators automatically convert their operands.

Other operators

void

The void operator accepts a single operand, which it evaluates. It then discards the result and returns undefined.

Sequence operator

As in other languages, the sequence operator evaluates both its operands and returns the value on the right. Because this operator has the lowest possible precedence, the sequence expression must be parenthesized if its result is to be assigned:

let oYOrig = (++oX, ++oY);

typeof

The typeof operator returns a string that gives the general type of its operand, whether "undefined", "boolean", "number", "bigint", "string", "symbol", "object", or "function". Note that the values are always lowercase. null variables are considered to have the "object" type.

Functions

A function declaration consists of the function keyword, the function name, and a list of untyped parameters. Any type can be returned by any function, so no return type is specified:

function xReset(aPos, aCkSync) {
  ...

Defining the function within an expression produces a function expression. Though a function name can be provided, that name will be inaccessible outside the function itself, so these definitions are typically anonymous:

let fReset = function (aPos, aCkSync) {
  ...

As of ES6, JavaScript supports lambda functions, which it calls arrow functions. They are defined much like function expressions, but the function keyword is not used, and the arrow token is placed between the parameter list and the body:

let ofAdd = (aL, aR) => {
  return aL + aR
};

Arrow functions can also be expression-bodied:

let ofAdd = (aL, aR) => aL + aR;

If there is only one parameter, the parameter list parentheses can be omitted:

let ofSq = a => a * a;

If there are no parameters, however, they must be included:

let ofRnd10 = () => Math.ceil(Math.random() * 10);

Unlike other functions, this in an arrow function matches the value in the containing scope. An arrow function also has no arguments property of its own, so the property in the containing scope will referenced instead.

Function instances can also be created with the Function constructor:

let ofPow2 = new Function("aExp", "return 1 << aExp;");

The last argument is a string that gives the implemention for the new function. The preceding arguments, if any, are strings containing the names of the new function's parameters. Multiple parameters can also be specified in a single string by delimiting them with commas:

let ofExp = new Function(
  "aBase, aExp",
  "return aBase ** aExp"
);
let oVal = ofExp(2, 3);

The code is executed as if it were part of a function defined in the global scope, so closures cannot be created this way. It also uses sloppy mode.

JavaScript does not allow functions to be overloaded. If a second function is declared with the same name, that function replaces the first.

Function declarations can be nested within other functions, but, in strict mode, they can be placed only at the top level of the containing function, or within another block. Function expressions can appear anywhere. Strict mode also gives block scope to nested functions, rather than function scope. Like var instances, nested function declarations are hoisted, allowing them to be called before they are declared, if they are accessible. Unlike hoisted variables, which are undefined before their initializations, hoisted functions are usable immediately.

A method is a function that has been assigned to a property in some object. Functions assigned to array elements are also treated as methods. In JavaScript, functions are themselves objects that can contain their own properties, including additional methods. Like global var variables, global functions are created as properties of the global object.

Parameters

JavaScript allows functions to be called without specifying all or any of their arguments. When this is done, the parameters are undefined within the function. Conversely, when a result is read from a function that has not returned a value, undefined is produced.

If a function is called with extra arguments, they are ignored. Within the function, the array-like arguments object can be used to access these and other arguments. arguments was originally presented as a property of the Function prototype, but that has been deprecated; it is now a local variable within the function. arguments can be used to implement variadic functions. arguments has a length property that gives the actual argument count. The function also has a length property, and this gives the parameter count.

Default parameters

Starting with ES6, default parameters can be defined by assigning default values in the parameter list. Unlike many languages, JavaScript does allow non-default parameters to follow default parameters:

function fWait(aMilli = 1000, aCkSpin) {
  ...

A given default is applied if its argument is missing when the function is called, or if it is explicitly undefined. In earlier versions, parameters would be checked for undefined values and then set within the function body.

Default value expressions can reference parameters defined earlier in the parameter list:

function fReady(aID, aName = "User " + aID) {
  ...

Defaults can also be assigned to parameters that have been destructured from an array:

function fSet_Orig([aX, aY, aZ] = [0.0, 0.0, 0.0]) {
  ...

or from an object:

function fExec({aCt = 1, aOptRenew = true} = {}) {
  ...

In this example, the object itself is given a default so that the function can be called with no parameters.

Rest parameters and spread syntax

ES6 supports the rest parameter, which is defined by prefixing the last parameter with an ellipsis:

function fRecalc(aBin, ...aWgts) {
  ...

When the function is invoked, any arguments following the non-rest parameters are passed as an array through the rest parameter.

ES6 also introduces the spread syntax, which is invoked by prefixing an iterable argument with an ellipsis. When this is done, the elements in the iterable are transformed into discrete arguments. This can be used to call a function:

function fUpd(aID, aName, aCt) {
  ...
}

let oArgs = ["01B", "Northgate", 6];
xUpd(...oArgs);

or to populate an object or array:

let oArgsEx = [...oArgs, "POST"];

Because strings are iterable, this can be used to split a string into characters:

let oChs = [..."0123456789ABCDEF"];

Because objects are iterable, it can also be used to produce a shallow copy of an object:

let oPosStart = { X: 0.0, Y: 0.0 };
let oPosCurr = { ...oPosStart };

If the syntax is applied to a non-iterable, no arguments will be produced.

To summarize, prefixing a parameter with an ellipsis produces a rest parameter, which converts a number of discrete arguments into an array. Prefixing an argument with an ellipsis invokes the spread syntax, which converts an iterable (such as an array) into a number of discrete arguments.

this

this is a keyword, not a variable, and its meaning changes in different contexts. Within the global scope, it always references the global object. Its meaning within a function depends on the type of that function:

Function type Mode Referent
Constructor (any) The new object
Method (any) The containing object
Arrow function (any) this from the containing scope
All others Strict undefined
Sloppy The global object

As a result, if a method is copied directly to a local variable and invoked, its this value will no longer reference the object that contained it. This can be remedied with the bind method, which is inherited by all functions:

let oRg = new tRg(100, 102);
let ofLen = oRg.fLen.bind(oRg);
let oLen = ofLen();

This creates a bound function, which wraps the original. Within the new function, this is set to the value of the first bind argument. If additional arguments are passed to bind, those are forwarded to the original function every time the bound function is invoked. If arguments are passed to the bound function, those are also forwarded to the original, after the permanently bound arguments, if any.

The problem can also be fixed with an arrow function:

let ofLen = () => oRg.fLen();

Every function also inherits call and apply methods that invoke the function directly. If an argument is passed to either method, that value is referenced by this when the function is executed. When strict mode is enabled, this can be made to reference a primitive type, null, or undefined. Before ES5, or in sloppy mode, primitive types are replaced with wrapper objects, while null and undefined cause this to reference the global object.

When call is invoked with more than one argument, the additional arguments are forwarded to the function. The second apply argument is expected to be an array. If that argument is specified, its elements are passed as arguments to the function.

Closures

A closure binds one or more functions to a persistent copy of the context in which they were defined. Returning a nested function produces a closure that can access variables or call functions in the containing scope, even after the program has left that scope. The containing function can also return an object that itself defines a number of closures, allowing the data to be manipulated by different operations. This is another way that function scope can be used to limit access to functions and data. All functions within a closure share the same function-scope data, but each invocation of the containing function creates a new context with a distinct copy of that data.

Currying

Closures can be used to curry a function that would otherwise accept multiple arguments. The first parameter is accepted by an outer function, which returns an inner function that accepts the second argument, et cetera, until one function is defined for each parameter:

function ofPosAbs(aOrig) {
  return (aOff) => aOrig + aOff;
}

The operation as a whole can then be performed by chaining the function invocations:

let oPos = ofPosAbs(1.0)(-3.0);

More importantly, a subset of the first arguments can be provided to create a new function that implicitly applies them when it is invoked with the remaining arguments:

let ofSysOne = ofPosAbs(1.0);
let oPos = ofSysOne(-3.0);

Generators

ES6 supports generator functions, which resemble iterator methods in C#. These are defined like other functions, but an asterisk is added to the function keyword. They can be structured as declarations:

function* fEls() {
  ...
}

or expressions:

const fEls = function* () {
  ...
}

When defined as methods, function is omitted:

class tMgrEl {
  * fEls() {
    ...
  }
  ...

Generator functions return generator objects that implement the iterable and iterator protocols. No generator code is executed when the object is created; instead, the program counter pauses before the first line until the object's next method is called. This runs the generator until it reaches a yield statement, a return statement, or the end of the function:

function* fCds() {
  yield "A";
  yield "B";
  return 100;
}

If the function stops at a yield, its state is conserved until next is called again. Because they are iterable, generators can be used in for/of loops, their values can be spread into arguments, and they can be destructured:

let oyCds = fCds();
for (const oCd of oyCds)
  fExec(oCd);

As with any iterator, the next method returns an object that contains a value property, a done property, or both. If the generator stops at a yield, that statement's argument will be assigned to value, and done will be set to false. If the generator reaches the end of its function, or if it executes a return statement with no argument, value will be undefined, and done will be set to true:

for (let o = oyCds.next(); !o.done; o = oyCds.next())
  fExec(o.value);

If the generator executes a return statement with an argument, value will be set to that argument, and done will be true. This is not the pattern followed by other iterators. If a generator explicitly returns a value instead of yielding it, that value will be ignored by for/of loops, when spreading arguments, and when destructuring.

The yield* statement yields a sequence of values from an iterable:

function* fVowels() {
  yield* ["a", "e", "i", "o", "u"];
}

With each call to next, one element is extracted. If an ordinary yield were used, the iterable itself would be returned.

Because generator objects are themselves iterable, yield* can be used to chain generator output:

class tNode {
  constructor (aVal, aChildren = []) {
    this.Val = aVal;
    this.Children = aChildren;
  }

  * [Symbol.iterator]() {
    yield this.Val;
    for (const oChild of this.Children)
      yield* oChild;
  }
}

This allows complex data structures to be iterated recursively:

const oNodeRoot = new tNode("R", [
  new tNode("A", [
    new tNode("A1"),
    new tNode("A2")
  ]),
  new tNode("B", [
    new tNode("B1"),
  ])
]);

for (const oNode of oNodeRoot)
  fExec(oNode);

Input to generators

Generators can also accept data from their callers. If a value is passed to the next method, that value will be returned from the yield statement at which the generator is paused:

function* fAdd() {
  const oL = yield;
  const oR = yield;
  fExec(oL + oR);
}

const oyAdd = fAdd();
oyAdd.next();
oyAdd.next(2);
oyAdd.next(3);

As always, the generator waits before the first line when it is created. Because there is no yield there, it is impossible for it to receive a value from the first next. Each next causes the generator to run until yield is encountered, where it pauses, or until the function returns. After waiting at the last yield, the function runs to completion before returning to next, so that next result sets done to true.

It is possible to send input to and receive output from the generator with the same next invocation. When that is done, the input is sent to the yield at which next starts, while the output is drawn from the yield at which it ends. In these situations, the yield token represents an input value, yet it also accepts an output argument. This unusual combination necessitates that yield be parenthesized (along with its arguments, if any) if it is part of an expression that uses the yield input:

function* fWork() {
  ...
  const oText = "In: " + (yield oOut);
  ...
}

The generator object also implements methods named return and throw. The return method causes the generator to act as if a return statement had been found at the current yield. Like next, the method also returns an object containing value and done properties, with value set to the argument that was passed to the return method, if any, and done set to true. In like manner, the throw method throws its argument from the current yield.

Asynchronous programming

JavaScript programs are cooperatively multitasked. User input adds elements to a message queue, much the way messages are processed in a Win32 program. The queue is serviced by an event loop, which invokes the callback or task associated with each event. There are no threads; every task runs to completion before returning to the loop. This ensures that no task is ever interrupted, but it also allows long-running tasks to block the loop.

Promises

ES6 introduces promises, which allow tasks to be performed asynchronously. A promise is created by passing a work function to the Promise constructor:

function wfReq(aKey) {
  return new Promise((afResolve, afReject) => {
    if (!aKey)
      throw Error("Key not set");
    const oURL = "http://localhost:8080/svc/" + aKey;

    const oReq = new XMLHttpRequest();
    oReq.open("GET", oURL);
    oReq.onload = function () {
      if (this.status === 200)
        afResolve(oReq.responseText);
      else {
        const oErr = new Error("STAT " + this.status);
        afReject(oErr);
      }
    }
    oReq.onerror = () => {
      const oErr = new Error("Cannot connect");
      afReject(oErr);
    }
    oReq.send();
  });
}

The work function is invoked immediately, so that the asynchronous operation can be started. Though JavaScript does not allow programs to create threads, the platform could create one to support the asynchronous work. The function itself contains only short synchronous operations, so it does not block the current task.

Promise settlement

Every promise has one of three states:

  • Pending: The asynchronous operation is in progress. The promise starts in this state;
  • Fulfilled: The asynchronous operation completed successfully, and the promise has been resolved;
  • Rejected: The asynchronous operation failed or could not be started, and the promise has been explicitly rejected.

A promise that has been resolved or rejected is said to be settled. Neither its state nor its value can change after it is settled.

The work function accepts two functions from the Promise implementation as parameters. The first resolves the promise, while the second rejects it. These are meant to be called when the outcome of the operation is known. They can be called directly, from the current task, or from handlers assigned by the work function to the asynchronous system. Even if the operation finishes immediately, neither function will be invoked until after the current task is complete.

Both functions accept one argument. The resolving function accepts the result of the operation. The rejecting function accepts a reason instance of any type. Throwing from the constructor automatically rejects the promise with the thrown object as the reason. Therefore, throwing and rejecting the same type (most likely Error) allows reason data to be handled consistently.

If the work function throws an exception, the promise will be rejected.

then, catch, and finally

As will be seen, promises are chained so that tasks can be started and handled in sequence:

wfReq("F10")
  .then(aVal => {
    ...
    fExec(aVal);
  })
  .finally(() =>
    fTerm()
  );

Just as the Promise constructor associates result handlers with events in some asynchronous process, the Promise class provides then, catch, and finally methods that associate settlement handlers with one promise in the chain. then and catch also extract results and reasons from ancestor promises:

then()
then(handResolve)
then(handResolve, handReject)

Accepts up to two handlers, one of which may be invoked when the promise is settled. handResolve is used if the promise is resolved. That function accepts a single argument that gives the result of the parent operation. handReject is used if the promise is rejected, and its argument gives the reason that was thrown or otherwise provided.

A non-function value can be provided in place of either handler. When this is done, the value is replaced with a simple function that returns the value.

No promise is ever settled in the task in which it was created, so the handler will be invoked in a task that follows the current one. In the meantime, then stores the handlers and returns a new promise. If no handlers are provided, the promise will settle when the parent settles, with the same state and value.

catch(handReject)
Accepts a rejection handler. This is the same functionality provided by the second then parameter, and catch in fact forwards its argument to that method. Like then, catch stores the handler and returns a new promise.
finally(handFinal)
Accepts a handler that will be invoked if the promise is resolved or rejected. Like the other methods, finally stores the handler and returns a new promise. The handler does not receive an argument, however.

Chaining promises

Each then, catch, or finally adds a new promise to the chain. When a parent promise is settled, one of its settlement handlers may be invoked. Whether this happens depends upon the nature of the settlement:

  • If the child promise was created with then, a resolution handler was probably registered. If so, the handler will be invoked if the parent was resolved. If a rejection handler was registered, it will be invoked if the parent was rejected;
  • If the child was created with catch, the handler will be invoked if the parent was rejected;
  • If the child was created with finally, the handler will be invoked regardless of the parent outcome.

If the parent is resolved, and if there is no resolution handler, the child will be resolved. This allows the program to continue past a catch promise when the preceding promise is a success.

If the parent is rejected, and if there is no rejection handler, the child will be rejected. This causes successive promises to be rejected until a catch, finally, or rejection-handling then promise is encountered.

If a handler is invoked, another asynchronous process will be started that eventually causes the child to be settled. The way in which it is settled depends upon the output of the handler:

  • If it returns a non-promise value, the child promise is resolved with that value as its result;
  • If it finishes without returning a value, the child promise is resolved with an undefined result;
  • If it throws an object, the child promise is rejected with that object as its reason;
  • If it returns a settled promise, the child promise is settled the same way, with the same result or reason;
  • If it returns a pending promise, the child promise retains its pending state. It will be settled automatically when the returned promise is settled.

Note that these rules apply to catch and finally, just as they apply to then. This means that the promise returned from a catch is resolved, not rejected, unless the catch handler throws or returns a rejected promise.

Within the chain, promises are settled sequentially and asynchronously. The result of each successful promise is passed through the resolution handler argument to its child. The chain itself is instantiated without blocking. As a whole, it is represented by the promise at its end.

A set of asynchronous processes can be made to run concurrently by starting them separately, and then joining their results with then:

const owReq10 = wfUpd(10)
const owReq12 = wfUpd(12)
const owReq14 = wfUpd(14)
const oReqs = owReq10.then(aIdx10 =>
  owReq12.then(aIdx12 => 
    owReq14.then(aIdx14 =>
      [aIdx10, aIdx12, aIdx14]
    )
  )
);

Because throwing from any handler causes the associated promise to be rejected, placing catch at the end of a sequence allows exceptions thrown by then handlers to be managed in the same place with rejections generated by the original promise:

wfUpd(80)
  .then(aIdx => {
    if (aIdx < 0)
      throw new Error("Invalid index");
    fCache(aIdx);
  })
  .catch(aErr =>
    fOut(aErr);
  );

Throwing from the work function automatically rejects the promise that is being constructed. Throwing from other points within an asynchronous function could cause the exception to leave the function, which would prevent it from reaching the catch handler. For this reason, it is often better to catch exceptions and return a rejected promise instead:

function wfUpd(aBatch) {
  try {
    fPrep(aBatch);
  }
  catch (aErr) {
    return Promise.reject(aErr);
  }
    
  return new Promise((afResolve, afReject) => {
    ...
  });
}

If it does not throw, or return a rejected promise of its own, a catch handler resolves its promise. When this happens, a then handler that was added after the catch will be invoked whether the parent promise was rejected or not:

wfUpd(90)
  .then(aIdx =>
    fCache(aIdx);
  )
  .catch(aErr =>
    fOut(aErr);
  )
  .then(aErr =>
    fTerm();
  );

Promise class

The Promise class includes static methods such as:

resolve(result)
Returns a new promise that is resolved with the specified result, if result is a non-promise value. If result is a promise, the new promise will be settled when result is settled, and in the same way.
reject(reason)
Returns a new promise that is rejected with the specified reason.

Chaining promises causes them to be settled sequentially. The Promise class also includes static methods that allow promises to be executed in parallel:

all(proms)
Causes all promises in iterable proms to run concurrently, then returns a new promise that is resolved when all the promises are resolved, or rejected when any is rejected. If the new promise is resolved, its result will be an array containing the results of the other promises. If the new promise is rejected, its reason will be that of the first promise that was rejected.
allSettled(proms)
Causes all promises in iterable proms to run concurrently, then returns a new promise that is resolved when all the promises are settled, successfully or not. The result of the new promise is an array containing the results or reasons produced by the promises in proms.
race(proms)
Causes all promises in iterable proms to run concurrently, then returns a new promise that is resolved or rejected when any of the promises is settled, successfully or not. The result of the new promise is the result or reason produced by the first settled promise in proms.

async and await

ES2017 adds the async and await keywords, which greatly simplify promise syntax. They work much as they do in C#.

async functions

An async function wraps asynchronous code. It is defined by prefixing a function declaration with the async keyword:

async function wfRecFromID(aID) {
  ...
}

async can also be applied to function expressions:

let wfSvcDef = async function () {
  ...
}

to arrow functions:

const owf = async (aNum) => wfReq("F" + aNum);

and to methods:

class tMgrConn {
  async wfOpen(aURL) {
    ...
  }
  ...

As will be seen, async functions are often paused at one or more points by await. The first time the function pauses, it returns a new promise that represents the function's work as a whole. When the code inside the function returns, that promise may be settled, with the outcome depending on what was returned:

  • If a non-promise value is returned, the promise will be resolved with that value as its result;
  • If the function finishes without returning, the promise will be resolved with an undefined result;
  • If the function throws an object, the promise will be rejected with that object as its reason. The throw will not leave the function;
  • If a settled promise is returned, the original promise will be settled in the same way, with the same result or reason;
  • If a pending promise is returned, the original promise will retain its pending state. It will be settled automatically when the returned promise is settled.

These are the same rules that determine the state of a chained promise when the associated then, catch, or finally handler returns.

If the async function does not await, it will return immediately. However, the returned promise will not be settled until a later task.

async functions are usable within promise chains, just like other promise-returning functions. Unlike those function, however, an async function ensures that a promise is returned. A non-async function could throw, for instance, or return a non-promise value. Either of those actions would settle the promise returned by an async function.

Awaiting promises

The await keyword can be used only within async functions. It cannot be used in a nested function unless that function is also async.

await accepts one argument. If that argument is a promise, the containing function stops at the await:

oRec = await wfRecFromID(oIDRec);

The async function's promise is returned to the calling code, and the current task continues its work outside the function. At some point in a later task, when the awaited promise has settled, the program resumes the function where it was stopped. If the awaited promise was resolved, await returns its result. If the promise was rejected, await throws the reason for the rejection. This allows a promise chain:

function fUpdByName(aName) {
  fOut(`Fetching batch '${aName}'...`);
  wfBatchFromName(aName)
    .catch(aErr => {
      fOut("Reverting to default...");
      return wfBatchDef();
    })
    .then(aBatch => {
      fOut(`Updating batch ${aBatch}...`);
      return wfUpd(aBatch);
    })
    .then (aIdx => fCache(aIdx));
}

to be replaced with something resembling synchronous code. In particular, where asynchronous results were forwarded as settlement handler arguments, they can now be stored in local variables:

async function wfUpdByName(aName) {
  let oBatch;
  try {
    fOut(`Fetching batch '${aName}'...`);
    oBatch = await wfBatchFromName(aName);
  }
  catch (aErr) {
    fOut("Reverting to default...");
    oBatch = await wfBatchDef();
  }

  fOut(`Updating batch ${oBatch}...`);
  const oIdx = await wfUpd(oBatch);

  fCache(oIdx);
}

Non-promise arguments can also be passed to await. When that is done, the containing function does not pause; await simply returns the argument, and the function continues.

There may be no need to await the last promise in some function; returning it directly will cause the function's promise to be settled in the same way. Awaiting such a promise allows its rejection to be handled locally, however.

If there is a need to await a promise in the global scope, the asynchronous code can be wrapped in an async IIFE:

(async () => {
  const oBatch = await wfBatchDef();
  fOut(oBatch);
})();

Asynchronous iterables

ES2018 adds features that generate iteration results asynchronously.

The asynchronous iterable protocol is similar to the synchronous version, but the iterator method is keyed with the well-known asyncIterator symbol:

class tAccts {
  constructor(aType) {
    this.Type = aType;
  }

  [Symbol.asyncIterator]() {
    return new tyAcct(this.Type);
  }
  ...

The iterator is expected to return a promise that wraps the iteration result:

class tyAcct {
  constructor(aType) {
    this.Type = aType;
    this.IDPrev = "";
  }

  async next() {
    try {
      const oAcct = await wfAcctNext(
        this.Type, this.IDPrev
      );
      this.IDPrev = oAcct.ID;
      return { value: oAcct };
    }
    catch (aErr) {
      return { done: true };
    }
  }
}

This ensures that iteration results can be awaited:

async function fExecArch() {
  const oAccts = new tAccts("ARCH");
  const oyAccts = oAccts[Symbol.asyncIterator]();
  const o = await oyAccts.next();
  if (o) {
    fExec(o.value.ID);
    ...

for await/of

ES2018 also adds the for await/of loop, which extracts a promise from an asynchronous iterable, awaits it, and then executes the loop code if the promise was resolved. Like await, it can be used only within async functions:

async function wfExec_Accts(aType) {
  const oAccts = new tAccts(aType);
  for await (const oAcct of oAccts)
    fExec(oAcct.ID);
}

This sequence repeats until a promise is rejected. The loop can also accept an iterable of promises, or an ordinary synchronous iterable. In all cases, successive iterations are processed within different tasks.

Asynchronous generators

Generators can also be declared async, and these allow await and yield to be used together:

async function* wfPrepRead_Svc() {
  const oSvc = await wfSvc();
  yield oSvc.fRead("PREP");
  yield oSvc.fRead("READ");
}

As expected, they implement the asynchronous versions of the iterable and iterator protocols:

async function wfRead() {
  const owReads = wfPrepRead_Svc();
  for await (const oRead of owReads)
    fOut(oRead);
}

Exceptions

Any type can be thrown, but the JavaScript interpreter only throws Error and its subclasses. Error describes the exception with its name and message properties. The Error function can be used as a constructor:

throw new Error("fExec: Invalid name");

but it also creates and returns an instance without new:

throw Error("fExec: Invalid name");

A try block is followed by a catch block, a finally block, or both. If an exception is thrown, and both are provided, the catch will be executed before the finally. The finally is always executed, however, even if the catch returns from the containing function.

A given catch collects all exceptions in the try block, regardless of type. The catch must define an exception variable, even if it is not needed:

try {
  ...
}
catch (oErr) {
  ...
}
finally {
  ...
}

Modules

Modules are used to avoid the name collisions that can result when objects are placed in the global scope. They are also used to package and encapsulate library code.

IIFE

In older JavaScript, functions are often used as private namespaces. A global function is created, functions and variables are defined and used within it, and the containing function is called immediately after. This pattern is known as the Immediately-Invoked Function Expression or IIFE:

(function () {
  ...
}());

Note that the IIFE is surrounded by parentheses. Without them, the interpreter would read it as a function declaration, which is required to specify a name. Only expressions are allowed within parentheses.

An IIFE can export functions and variables by returning an object that contains the interface to the module:

var gAvg = (function () {
  function fAlg(aVals) {
    ...
  }

  function fGeom(aVals) {
    ...
  }

  function ofCkValid(aVals) {
    ...
  }

  return {
    fAlg,
    fGeom
  };
}());

function fExec(aVals) {
  var oAvg = gAvg.fGeom(aVals);
  ...
}

Elements that are not explicitly exported remain inaccessible outside the IIFE.

Containers

Iterables

In C# and Java, interfaces are defined in code, and their requirements are enforced by the compiler. JavaScript does not support interfaces per se, but it does document a number of protocols, these being informal requirements defined outside the code and enforced only by the developer.

One such protocol is iterable, which ES6 uses to implement for/of and argument spreading. To implement iterable, an object must provide a function keyed with the well-known iterator symbol:

class tNums {
  constructor(aMax) {
    this.Max = aMax;
  }

  [Symbol.iterator]() {
    return new tyNum(this.Max);
  }
}

This function should return an object that implements another protocol named iterator. This object must provide a function next that returns iteration result objects:

class tyNum {
  constructor(aMax) {
    this.Next = 0;
    this.Max = aMax;
  }

  next() {
    if (this.Next > this.Max)
      return { done: true };
    return { value: this.Next++ };
  }
}

Each result should contain a value property, a done property, or both. If the iterator is able to return an element, it should set value to reference that element. If done is defined, it should be set to false. If the iterator is not able to return an element, it should set done to true. The value property need not be set if the iterator is done.

An iterator can be manually iterated by calling its next method within a loop:

const oNums = new tNums(3);
const oyNums = oNums[Symbol.iterator]();
for (let o = oyNums.next(); !o.done; o = oyNums.next())
  fExec(o.value);

The iterable is a factory for the iterator. Iterators can also be created directly, by returning a closure from some function:

function fMake_Nums(aMax) {
  let oNext = 0;
  return {
    next: function () {
      if (oNext > aMax) return { done: true };
      return { value: oNext++, };
    }
  }
}
const oyNums = fMake_Nums(3);

However, this does not implement iterable itself, so the function cannot be used where an iterable is expected.

An iterable can also be created by defining the iterator function as a generator:

class tServs {
  constructor(aCkMain) {
    this.CkMain = aCkMain;
  }

  * [Symbol.iterator]() {
    if (this.CkMain) yield "MAIN";
    else {
      yield "ALPHA";
      yield "BETA";
      yield "GAMMA";
    }
  }
}

In fact, the returned generator object is both an iterator:

let ofServs = function* (aCkMain) {
  if (aCkMain) yield "MAIN";
  else {
    yield "ALPHA";
    yield "BETA";
    yield "GAMMA";
  }
};

let oyServs = ofServs(false);
for (let o = oyServs.next(); !o.done; o = oyServs.next())
  fExec(o.value);

and an iterable:

let oServs = ofServs(false);
for (const oServ of oServs)
  fExec(oServ);

However, a single generator object can be iterated only once. Therefore, it is necessary to obtain a new instance to iterate the generator again.

Arrays

JavaScript arrays inherit from Array.prototype. They can be instantiated with array literals, which are comma-delimited sequences inside square braces:

let oInsPend = [ "A11", "B04", "CXX" ];

When commas are listed without intervening values, elements are indexed (and the array length set) as though values had been provided. The last trailing comma before the closing brace is ignored, however:

let oInsMark = [ , "NUL", , ];
fAssert(oInsMark[1] === "NUL");
fAssert(oInsMark.length === 3);

The missing values are sometimes called holes. Though they are counted in the array length, they do not produce real elements. In particular, those indices will not be iterated by for/in loops. They can be dereferenced to produce undefined, but that is true for any invalid index.

Arrays can also be created with the Array constructor. Calling Array without an argument produces an empty array. Passing a single numeric argument creates an array of the specified length, but it does not add real elements. Passing multiple arguments, or a single non-numeric argument to Array assigns those values as elements, much like an array literal:

let oInsPend = new Array("A11", "B04", "CXX");

JavaScript arrays are untyped, so different types can be mixed in the same instance. Because of this, they are sometimes used as tuples. Multidimensional arrays are structured as arrays of arrays.

Arrays are indexed with 32-bit unsigned integers, allowing over four billion elements to be stored. The element count is returned by the length property. Because arrays are objects, and because the array syntax can also be used to reference ordinary object properties, negative numbers, non-integer numbers, and other invalid indices can be used to read or write values, and the resulting properties are enumerable, but they do not change the array length. Dereferencing an index that is out of range produces undefined, like any attempt to read an undeclared property.

JavaScript arrays are resizable. Elements can be added or removed, and an array can be truncated or extended by writing to the length property. Assigning to an element with an index that is greater than or equal to the current length also extends the array. In both these cases, however, the length is increased without adding enumerable elements.

Array class

The Array class includes static methods such as:

of(el, ...)
Returns an array containing one element for each of the specified arguments. Added in ES6.
from(els)
from(els, map)
from(els, map, this)
Returns an array containing the elements referenced by iterable or array-like els. If a map function is specified, the elements are instead passed to that function, and its output is used to populate the new array. map receives the same arguments that it would receive if passed to the map method. If this is specified, its value is used for this within the map function. Added in ES6.

The Array class also includes methods like:

join()
join(delim)
Returns a new string that combines the string representation of every element, delimited by commas, or by delim.
entries()

Returns an iterator that returns all keys and values in the array. Each pair is stored as a two-element array within the value property of the iteration result:

const oEls = [1, 2, 3];
const oyEls = oEls.entries();
for (const [oKey, oVal] of oyEls)
  fOut(`${oKey}: ${oVal}`);

Added in ES2016.

slice(start)
slice(start, next)
Returns a new array containing elements that begin at start and stop at the array's end, or just before next. If either argument is negative, it wraps back once around the end of the array. The extracted array never wraps forward past the end, so arguments greater than the length are treated as if they were equal to the length.
indexOf(val)
indexOf(val, start)
Returns the index of the first element that equals val, or negative one if no match is found. If start is specified, the search begins at that index. If start is negative, it wraps back once around the end of the string. If start is greater than the last index, the search fails.
lastIndexOf(val)
lastIndexOf(val, start)
Returns the index of the last element that is equal to val, or negative one if no match is found. If start is specified, the search begins at that index. If start is negative, it wraps back once around the end of the string. If start is greater than the last index, the search begins at the last element.
includes(val)
includes(val, start)
Returns true if any element is equal to val. If start is specified, the search begins at that index. If start is negative, it wraps back once around the end of the string. If start is greater than the last index, the search fails. Added in ES2016.
unshift(el, ...)
Adds one or more elements to the beginning of the array, then returns its new length.
shift()
Removes one element from the beginning of the array and returns it.
push(el, ...)
Adds one or more elements to the end of the array, then returns its new length.
pop()
Removes one element from the end of the array and returns it.
concat(add, ...)
Returns a new array containing the original elements, plus any arguments passed to the function. Unlike splice, if one or more arguments are themselves arrays, their elements are added, rather than the arrays as a whole.
splice(start)
splice(start, len)
splice(start, len, ...)
Modifies the array in place by removing elements, or inserting them, or doing both, then returns any removed elements in a new array. The operation begins at index start. If no other arguments are provided, this element and those that follow it are removed and returned. If a len is specified, that number of elements are removed. If more arguments are provided, those values are also inserted at start. Unlike concat, array arguments are inserted as arrays.
fill(val)
fill(val, start)
fill(val, start, next)
Modifies the array in place by setting elements to val, then returns it. If start is specified, elements before that index are left unmodified. If next is specified, elements at and after that index are unmodified. Added in ES6.
reverse()
Reverses the element order in place, then returns the array.
sort()
sort(compare)
Sorts the elements in place, then returns the array. By default, elements are sorted by their string representations, so numbers are not sorted in increasing order. To customize the sort, pass a compare function that accepts two values, and returns a positive number if the second should be sorted after the first, zero if they are equal, and a negative number if the first should be sorted after the second.

The following Array methods pass elements to a function, call, which itself accepts up to three arguments: an element, its array index, and the array as a whole. These array methods also accept an optional this parameter. When this is provided, it is referenced wherever this is used within call:

forEach(call)
forEach(call, this)
Iterates the array and passes each element to call.
some(call)
some(call, this)
Iterates the array and returns true if call returns true for any element.
every(call)
every(call, this)
Iterates the array and returns true if call returns true for every element.
find(call)
find(call, this)
Returns the value of the first element for which call returns true, or undefined if no match is found. Added in ES6.
findIndex(call)
findIndex(call, this)
Returns the index of the first element for which call returns true, or negative one if no match is found. Added in ES6.
filter(call)
filter(call, this)
Iterates the array, passes each element to call, and returns a new array containing the elements for which call returned true.
map(call)
map(call, this)
Iterates the array, passes each element to call, and returns a new array containing the values returned by call.

The following Array methods use a callAccum function that accepts up to four values: an accumulator, which stores an ongoing calculation, an element, its array index, and the array as a whole:

reduce(callAccum)
reduce(callAccum, init)
Iterates the array, passes each element to callAccum, and returns the last value produced by that function. If init is provided, iteration begins at the first element, and init is used as the first accumulator value. If it is not provided, iteration begins at the second element, and the first is used as the accumulator.
reduceRight(callAccum)
reduceRight(callAccum, init)
Iterates the array in reverse, passes each element to callAccum, and returns the last value produced by that function. If init is provided, iteration begins at the last element, and init is used as the first accumulator value. If it is not provided, iteration begins at the element before the last, and the last element is used as the accumulator.

Array-like objects

Some objects (like the arguments instance defined within functions) are known as array-like objects. These are not true arrays, but they can sometimes be used as if they were. Every such object:

  • Provides a length property;
  • Associates a number of property values with integer indices.

Though they are not Array instances, many Array.prototype methods can be applied to these objects with Function.call or Function.apply.

Maps

All JavaScript objects can serve as associative arrays, but object keys are always strings or symbols. ES6 introduces the Map class, which allows keys to have any type.

A map can be created and initialized by passing an iterable to the Map constructor:

const oNumZones = [[10, "A"], [12, "B"], [20, "C"]];
const oZonesByNum = new Map(oNumZones);

The iterable is expected to return zero or more arrays, each containing one key/value pair. If any array contains fewer than two elements, one or both of the key and value will be considered undefined. Array elements beyond two will be ignored. If the iterable produces any result that is not an array, TypeError will be thrown. Omitting the iterable altogether produces an empty map.

If the same key is specified more than once, the last value takes precedence. This allows multiple map elements to updated with an array or another map:

const oNumZonesEx = [[20, "D"], [40, "E"]];
const oZonesByNumEx = new Map([
  ...oZonesByNum,
  ...oNumZonesEx
]);

Iterating a map returns arrays containing the key/value pairs in the order that the keys were added:

for (const [oNum, oZone] of oZonesByNum)
  fOut(`${oNum}: ${oZone}`);

Note that map instances are also objects. This means that properties can be added to the map, but these will not be recognized by Map methods like has:

oZonesByNum[100] = "F";

Map class

The Map class includes methods such as:

size()
Returns the number of elements in the map.
has(key)
Returns true if the map contains the specified key.
entries()
Returns an iterator that returns all keys/value pairs in the map. Each pair is stored as a two-element array within the value property of the iteration result.
read(key)

Returns the value with the specified key, or undefined if no such key exists:

const oZone12 = oZonesByNum.get(12);

Although NaN is unequal to itself, a pair with an NaN key can be read from the map.

Object instances are always considered unique when used as keys. There is no way to define a custom comparer.

set(key, val)

Creates an element with the specified key, or overwrites the element with that key if one already exists, then returns the map itself:

oZonesByNum.set(10, "Z")
  .set(12, "Y")
  .set(14, "X");
delete(key)

Deletes the element with the specified key, then returns true if key was found:

const oCkDel = oZonesByNum.delete(20);
clear()
Removes all elements from the map.
forEach(call)
forEach(call, this)
Iterates the map and passes each pair to the function call, which itself accepts up to three arguments: the element value, the element key, and the map as a whole. If this is provided, it is referenced wherever this is used within call.

Weak maps

ES6 also introduces weak maps, which are simple associative arrays with keys that weakly reference object instances. Any map can associate data with some object without the need to create new properties. Weak maps do this while also allowing the garbage collector to delete the object, if it is referenced nowhere else.

Like ordinary maps, they can be initialized with an iterable of key/value arrays:

let oCtsFromBuff = new WeakMap([
  [oBuffFront, 0],
  [oBuffBack, 0]
]);

However, only objects can be used as keys. A TypeError will be thrown if a primitive is used instead.

WeakMap is not iterable, and it provides only a few of the methods found in Map. These include set, has, get, and delete. Note that size and clear are not implemented.

Sets

ES6 also provides the Set class, which stores unique values. A set can be initialized with an iterable of values:

const oCds = new Set(["A", "B", "F"]);

If no iterable is specified, an empty set will be created.

Sets themselves are iterable. Set values are iterated in the same order they were added.

Set class

The Set class includes methods such as:

size()
Returns the number of values in the set.
has(val)
Returns true if val is a member of the set.
values()
keys()
entries()

values returns the same value iterator that is used when the set is treated as an iterable:

let oyCds = oCds.values();
for (const oCd of oyCds) {
  ...

For consistency with Map, the keys method also returns this iterator, while entries returns an iterator that produces value/value pairs.

add(val)

Adds val to the set, then returns the set instance. Has no effect if val is already in the set:

oCds.add("A").add("Z");
delete(val)

Removes the specified value and returns true if it was part of the set:

const oCkDel = oCds.delete("B");
clear()
Removes all values from the set.
forEach(call)
forEach(call, this)
Iterates the set and passes each value to the function call, which itself accepts up to three arguments: the value, the same value again, and the set as a whole. If this is provided, it is referenced wherever this is used within call.

Weak sets

ES6 also provides weak sets, which weakly reference unique object instances. Weak sets allow the garbage collector to delete a contained object if it is referenced nowhere else.

Like ordinary sets, they can be initialized with an iterable of values:

const oTagBase = { Name: "BASE", Ct: 10 };
const oTagOff = { Name: "OFF", Ct: 2 };
const oTags = new WeakSet([oTagBase, oTagOff]);

However, only objects can be used as keys. A TypeError will be thrown if a primitive is used instead.

WeakSet is not iterable, and the only methods it provides are has, add, and delete. In particular, size and clear are not implemented.

Regular expressions

Regular expressions are wrapped by instances of the RegExp class. Instances can be created with the RegExp constructor:

let oRegCd = new RegExp("A[1-3]");

or by assigning regular expression literals, which surround the expression with forward slashes:

let oRegCd = /A[1-3]/;

It is also possible to pass another RegExp instance to the constructor:

let oRegCd = new RegExp(/A[1-3]/);

Most expression characters are expected to match exactly within the target text. Others have special meanings:

\ / | . * + ^ $ ? : = ! [ ] { } ( )

To match one of these, it is usually necessary to escape the character with a backslash.

Tabs and other non-printing characters are specified with the same escape sequences used in string literals, with the exception of the backspace character, which is matched by [\b]. ctrl-X is matched with \c X.

The trailing slash in the expression literal can be followed by one or more letters that set flags:

let oRegCmds = /F\d\d/ig;

These letters can also be passed as a second parameter to the RegExp constructor. Flags are used to configure the search:

Flag Property Effect
i ignoreCase Produces a case-insensitive search. If the u flag is set, also uses Unicode case folding to ignore differences in case.
u unicode

Allows Unicode escape sequences to be used within patterns. Superfluous escape sequences (like \;, which would otherwise produce a semicolon) generate syntax errors in this mode.

Also causes strings encoding binary data to be interpreted as Unicode.

Added in ES6.

m multiline Enables multi-line mode, which causes ^ and $ to match the beginnings and ends of lines.
s dotAll Causes . to match newline characters as well as others. Added in ES2018.
g global Produces a global search, allowing some functions to process matches beyond the first.
y sticky Causes the match to succeed if it starts exactly at the position indicated by lastIndex. Matches beyond this point are not identified. Added in ES6.

Each flag sets the associated property, which cannot be changed later.

The search is started by invoking a RegExp method like exec or test.

Character classes

Expressions can also include character classes, each of which matches one of a number of characters. Enclosing characters within square braces produces a character set, which matches any one of the contained characters:

let oRegNumLot = /[123]/;

Prefixing the characters with a caret negates the set, causing it to match any one character that is not within the braces:

let oRegCdLot = /[^123]/;

A range of characters is specified by joining the lower and upper limits with a hyphen:

let oRegDigOct = /[0-7]/;

Neither periods nor asterisks are treated as special characters within the set, so there is no need to escape them.

Other classes include:

Class Match
. Any character that is not a newline
\s Any ASCII or Unicode whitespace character
\S Any character that is not matched by \s
\d Any ASCII number character
\D Any character that is not matched by \d
\w Any ASCII letter, number, or underscore character. Note that accented or non-roman characters are not included.
\W Any character that is not matched by \w

As will be seen, a sub-expression is created by surrounding an expression with parentheses. Within a sub-expression, entire sequences can be matched against a set of alternatives by delimiting the alternatives with pipe characters:

let oRegRt = /(MAIN\d|AUX\d\d|OFF) /;

Sub-expressions are checked from left to right, and the first to produce a match is used, even if another would match more completely.

Quantifiers

Characters and sub-expressions can be followed by quantifiers that allow them to repeat in the target text:

Quantifier Effect
? Match once or not at all
* Match zero or more times
+ Match one or more times
{ct} Match exactly ct times
{min,} Match at least min times
{min, max} Match anywhere from min to max times

Because they allow characters to be matched zero times, quantifiers like ? and * can produce expressions that match all strings, since every string contains zero or more instances of a given character.

By default, quantifiers implement greedy matches that consume as much of the target text as possible before the remainder is matched with the rest of the expression. Although ? is itself a quantifier, it can also be added to the end of a quantifier to specify a lazy match that consumes as little of the text as is needed to produce a match.

Capturing parentheses

Surrounding characters with parentheses produces a sub-expression that can be modified as a whole by a quantifier or another function:

let oReg = / (XO)+ /;

These capturing parentheses also store the target substring matched by the sub-expression. The substring can be recalled in another part of the expression by prefixing the sub-expression number with a backslash:

let oRegChQuot = /(["']).\1/;

The recalled substring is matched only if the target text contains an exact repetition of the substring that matched the referenced sub-expression.

Non-capturing parentheses do not store the matching substring. These are created by prefixing the inside characters with ?:.

let oReg = / (?:XO)+ /;

Anchors

Normally, expressions are matched wherever possible within the target text. Matches can be constrained to certain positions within the text by anchors. These are not matched to characters, but to positions between characters:

Anchor Position
^ The beginning of the text, or the beginning of any line, if the multi-line flag is set.
$ The end of the text, or the end of any line, if the multi-line flag is set.
\b The beginning or end of a word, which is any point between a \w character and a \W, or between a \w and the beginning or end of the text. Line breaks are already non-word characters, so there is no need to set the multi-line flag.
\B Any point that is not the beginning or end of a word, as defined by \b.

Enclosing characters within parentheses and prefixing with ?= or ?! creates a lookahead. Prefixing with ?<= or ?<! creates a lookbehind. These also constrains the match relative to its surroundings:

Expression Effect
patt(?=post) Matches patt if it is immediately followed by post, without consuming or matching post.
patt(?!post) Matches patt if it is not immediately followed by post.
(?<=pre)patt Matches patt if it is immediately preceded by pre. Added in ES2018.
(?<!pre)patt Matches patt if it is not immediately preceded by pre. Added in ES2018.

RegExp class

The RegExp class includes properties and methods such as:

source
Returns a string containing the expression itself. Starting with ES5, returns "(?:)" if the expression is empty.
flags
Returns a string containing the flags set in this instance.
lastIndex
The index in the target string where the next search should start.
exec(text)
Returns an array containing a substring matched by the regular expression, plus substrings matched by capturing parentheses in the expression, if such are defined. The array also defines an index property that gives the position of the match within text, and an input property that returns text itself. If the global search flag is set within the expression, the method also sets the lastIndex property of the expression instance to the position just after the match. This position becomes the starting point for the next search, if exec is called again. If no match is found, exec returns null.
test(text)
Returns true if a substring is matched by the regular expression. If the global search flag is set, the method also sets the lastIndex property within the expression instance to the position just after the match. This position becomes the starting point for the next search, if test is called again. Eventually, in this case, test will return false.

A number of String methods also use regular expressions, including search, match, split, and replace.

Miscellanea

Strict mode

The "use strict" directive is an ordinary string that enables strict mode. This mode makes many improvements to the language, including:

  • Instead of creating a new global variable, writing to an undeclared variable produces a ReferenceError;
  • Instead of referencing the global object, this is undefined within non-class functions;
  • The with statement is disallowed;
  • Instead of failing silently, an error is produced when an attempt is made to change a read-only property, or to add or delete a property from a read-only object;
  • Instead of being interpreted as octal values, integer literals that begin with zero produce SyntaxError;
  • Neither arguments nor eval are allowed to be assigned to another object or function;
  • Variables and functions declared within code passed to eval are not added to the containing scope.

Place the directive in the first non-comment line of the script to enable strict mode globally. Place it in the first line of a function to enable it locally:

function fExec() {
  "use strict";
  ...
}

The default state is sometimes known as sloppy mode. The directive is ignored altogether in versions before ES5.

eval

The global eval function accepts a single string and executes it as JavaScript code. Generally, the code behaves as if it were run from the calling scope. However:

  • If either the calling code or the eval code uses strict mode, then the eval code will be executed within a temporary scope, causing variables declared within eval to go out of scope when it returns. In sloppy mode, such variables will persist;
  • If the eval function is assigned to and called from another variable, if it is invoked through its call method, if it is called through globalThis.eval, or if it is called in any similarly indirect manner, its code will be executed within the global scope.

The code can always access and modify existing variables in the calling scope.

eval returns the value of its last statement, or undefined if that statement has no value. It also throws unhandled exceptions that were thrown within its code. In particular, if the code cannot be parsed, eval produces a SyntaxError.

debugger

The debugger statement pauses script execution and shows the debugger, like a breakpoint.

Sources

JavaScript for Impatient Programmers
Axel Rauschmayer
2019, Axel Rauschmayer

Eloquent JavaScript, 3rd Edition
Marijn Haverbeke
2019, No Starch Press, Inc.

JavaScript Pocket Reference, 3rd Edition
David Flanagan
2012, O'Reilly Media, Inc.

JavaScript: The Definitive Guide, 6rd Edition
David Flanagan
2011, O'Reilly Media, Inc.

MDN Web Docs: Arithmetic operators, async function, Asynchronous JavaScript, await, BigInt type, Binary strings, Classes, Concurrency model and the event loop, const, Default parameters, Destructuring assignment, Function.prototype.bind, globalThis, Iteration protocols, let, Map, Math, Object.defineProperty, Promise, Promise.prototype.then(), RegExp, Regular Expressions, Rest parameters, Set, Spread syntax, String, Symbol, Symbol (glossary entry), Symbol.for, Template literals, Using Promises, var, WeakMap
Retrieved January 2018 - November 2019

BigInt: Arbitrary precision integers in JavaScript
Ecma TC39
Retrieved October 2019

Exploring ES6: Generators
Axel Rauschmayer
Retrieved November 2019

Stack Overflow: Explain the encapsulated anonymous function syntax, How to understand JS realms, __proto__ VS. prototype in JavaScript
Retrieved February 2018 - October 2019