#deck:exploring-js-cards-topics-preview #html:true #notetype:Basic #separator:; #columns:Front;Back;Tags "Which programming languages influenced JavaScript?";"

When JavaScript was created in 1995, it was influenced by several programming languages:


More information ";"nature" "Why does JavaScript fail silently so often?";"

The reason for the silent failures is historical: JavaScript did not have exceptions until ECMAScript 3. Since then, its designers have tried to avoid silent failures.


More information ";"nature" "When was JavaScript created?";"

JavaScript was created in May 1995 in 10 days, by Brendan Eich.


More information ";"history" "Which standards organizations hosts the JavaScript standard?";"

There are two standards for JavaScript:

The language described by these standards is called ECMAScript, not JavaScript. A different name was chosen because Sun (now Oracle) had a trademark for the latter name. The “ECMA” in “ECMAScript” comes from the organization that hosts the primary standard.

The original name of that organization was ECMA, an acronym for European Computer Manufacturers Association. It was later changed to Ecma International (with “Ecma” being a proper name, not an acronym) because the organization’s activities had expanded beyond Europe. The initial all-caps acronym explains the spelling of ECMAScript.


More information ";"history" "What is the difference between JavaScript and ECMAScript?";"

Often, JavaScript and ECMAScript mean the same thing. Sometimes the following distinction is made:

Therefore, ECMAScript 6 is a version of the language (its 6th edition).


More information ";"history" "What is the first ECMAScript version that uses a year?";"
More information ";"history" "What is the name of the committee that evolves JavaScript?";"

TC39 (Ecma Technical Committee 39) is the committee that evolves JavaScript. Its members are, strictly speaking, companies: Adobe, Apple, Facebook, Google, Microsoft, Mozilla, Opera, Twitter, and others. That is, companies that are usually competitors are working together on JavaScript.


More information ";"history" "How many stages does the TC39 process for ECMAScript proposals have?";"
More information ";"history" "Why does JavaScript always remain backward-compatible?";"

One idea that occasionally comes up is to clean up JavaScript by removing old features and quirks. While the appeal of that idea is obvious, it has significant downsides.

Let’s assume we create a new version of JavaScript that is not backward compatible and fixes all of its flaws. As a result, we’d encounter the following problems:

So what is the solution? This is how JavaScript is evolved:


More information ";"history" "Which stage was added to the TC39 process later? Explain its number!";"

Stage 2.7 was added in late 2023, after stages 0, 1, 2, 3, 4 had already been in use for years.


More information ";"history" "In what way are variables declared via const immutable?";"

In JavaScript, const only means that the binding (the association between variable name and variable value) is immutable. The value itself may be mutable, like obj in the following example.

const obj = { prop: 0 };

// Allowed: changing properties of `obj`
obj.prop = obj.prop + 1;
assert.equal(obj.prop, 1);

// Not allowed: assigning to `obj`
assert.throws(
  () => { obj = {} },
  {
    name: 'TypeError',
    message: 'Assignment to constant variable.',
  }
);

More information ";"variables-assignment" "What is the scope of a variable?";"

The scope of a variable is the region of a program where it can be accessed. Consider the following code.

{ // // Scope A. Accessible: x
  const x = 0;
  assert.equal(x, 0);
  { // Scope B. Accessible: x, y
    const y = 1;
    assert.equal(x, 0);
    assert.equal(y, 1);
    { // Scope C. Accessible: x, y, z
      const z = 2;
      assert.equal(x, 0);
      assert.equal(y, 1);
      assert.equal(z, 2);
    }
  }
}
// Outside. Not accessible: x, y, z
assert.throws(
  () => console.log(x),
  {
    name: 'ReferenceError',
    message: 'x is not defined',
  }
);

Each variable is accessible in its direct scope and all scopes nested within that scope.

The variables declared via const and let are called block-scoped because their scopes are always the innermost surrounding blocks.


More information ";"variables-assignment" "What does shadowing a variable mean?";"

We can’t declare the same variable twice at the same level:

assert.throws(
  () => {
    eval('let x = 1; let x = 2;');
  },
  {
    name: 'SyntaxError',
    message: "Identifier 'x' has already been declared",
  }
);

We can, however, nest a block and use the same variable name x that we used outside the block:

const x = 1;
assert.equal(x, 1);
{
  const x = 2;
  assert.equal(x, 2);
}
assert.equal(x, 1);

Inside the block, the inner x is the only accessible variable with that name. The inner x is said to shadow the outer x. Once we leave the block, we can access the old value again.


More information ";"variables-assignment" "Programming languages: Meaning of “static” and “dynamic”?";"

These two adjectives describe phenomena in programming languages:

Let’s look at examples of these two terms.

Static phenomenon: scopes of variables

Variable scopes are a static phenomenon. Consider the following code:

function f() {
  const x = 3;
  // ···
}

x is statically (or lexically) scoped. That is, its scope is fixed and doesn’t change at runtime.

Variable scopes form a static tree (via static nesting).

Dynamic phenomenon: function calls

Function calls are a dynamic phenomenon. Consider the following code:

function g(x) {}
function h(y) {
  if (Math.random()) g(y); // (A)
}

Whether or not the function call in line A happens, can only be decided at runtime.

Function calls form a dynamic tree (via dynamic calls).


More information ";"variables-assignment" "Structure of JavaScript’s variable scopes (global variables, module variables, etc.)?";"

JavaScript’s variable scopes are nested. They form a tree:

The root is also called the global scope. In web browsers, the only location where one is directly in that scope is at the top level of a script. The variables of the global scope are called global variables and accessible everywhere. There are two kinds of global variables:

Each module has its own variable scope that is a direct child of the global scope. Therefore, variables that exist at the top level of a module are not global.


More information ";"variables-assignment" "How can we access the global object?";"

The global variable globalThis is the standard way of accessing the global object. It got its name from the fact that it has the same value as this in global scope (script scope, not module scope).


More information ";"variables-assignment" "What are use cases for acessing the global object?";"

The global object is now considered a mistake that JavaScript can’t get rid of, due to backward compatibility. It affects performance negatively and is generally confusing.

ECMAScript 6 introduced several features that make it easier to avoid the global object – for example:

It is usually better to access global object variables via variables and not via properties of globalThis. The former has always worked the same on all JavaScript platforms.

Tutorials on the web occasionally access global variables globVar via window.globVar. But the prefix “window.” is not necessary and I recommend to omit it:

window.encodeURIComponent(str); // no
encodeURIComponent(str); // yes

Therefore, there are relatively few use cases for globalThis – for example:


More information ";"variables-assignment" "Why does JavaScript throw an exception if we access a variable before its declaration?";"

For JavaScript, TC39 needed to decide what happens if we access a constant in its direct scope, before its declaration:

{
  console.log(x); // What happens here?
  const x = 123;
}

Some possible approaches are:

  1. The name is resolved in the scope surrounding the current scope.
  2. We get undefined.
  3. There is an error.

Approach 1 was rejected because there is no precedent in the language for this approach. It would therefore not be intuitive to JavaScript programmers.

Approach 2 was rejected because then x wouldn’t be a constant – it would have different values before and after its declaration.

let uses the same approach 3 as const, so that both work similarly and it’s easy to switch between them.


More information ";"variables-assignment" "What is a temporal dead zone?";"

The time between entering the scope of a variable and executing its declaration is called the temporal dead zone (TDZ) of that variable:


More information ";"variables-assignment" "Which JavaScript constructs are activated early?";"
ScopeActivationDuplicatesGlobal prop.
constBlockdecl. (TDZ)
letBlockdecl. (TDZ)
functionBlock (*)start
classBlockdecl. (TDZ)
importModulestart
varFunctionstart (partially)

More information ";"variables-assignment" "What is a variable closure?";"

What is a closure then? A closure is a function plus a connection to the variables that exist at its “birth place”.

What is the point of keeping this connection? It provides the values for the free variables of the function – for example:

function funcFactory(value) {
  return () => {
    return value;
  };
}

const func = funcFactory('abc');
assert.equal(func(), 'abc'); // (A)

funcFactory returns a closure that is assigned to func. Because func has the connection to the variables at its birth place, it can still access the free variable value when it is called in line A (even though it “escaped” its scope).

All functions in JavaScript are closures

Static scoping is supported via closures in JavaScript. Therefore, every function is a closure.


More information ";"variables-assignment" "What is a primitive value? What is an object?";"

The specification makes an important distinction between values:


More information ";"values" "How are primitive values different from objects?";"

They don’t differ much – e.g., both have properties and can be used anywhere. These are the differences:


More information ";"values" "What are the results of typeof?";"
xtypeof x
undefined'undefined'
null'object'
Boolean'boolean'
Number'number'
Bigint'bigint'
String'string'
Symbol'symbol'
Function'function'
All other objects'object'

More information ";"values" "What is they purpose of wrapper classes such as Number and String?";"

Each primitive type (except for the types undefined and null) has an associated constructor function (think class):

Each of these functions plays several roles – for example, Number:


More information ";"values" "What is coercion?";"

For many operations, JavaScript automatically converts the operands/parameters if their types don’t fit. This kind of automatic conversion is called coercion.

For example, the multiplication operator coerces its operands to numbers:

> '7' * '3'
21

Many built-in functions coerce, too. For example, Number.parseInt() coerces its parameter to a string before parsing it. That explains the following result:

> Number.parseInt(123.45)
123

The number 123.45 is converted to the string '123.45' before it is parsed. Parsing stops before the first non-digit character, which is why the result is 123.


More information ";"values" "Why and when do JavaScript operators produce unintuitive results?";"

JavaScript’s operators sometimes produce unintuitive results. With the following two rules, they are easier to understand:

As mentioned before, most operators only work with primitive values. If an operand is an object, it is usually coerced to a primitive value – for example:

> [1,2,3] + [4,5,6]
'1,2,34,5,6'

Why? The plus operator first coerces its operands to primitive values:

> String([1,2,3])
'1,2,3'
> String([4,5,6])
'4,5,6'

Next, it concatenates the two strings:

> '1,2,3' + '4,5,6'
'1,2,34,5,6'

More information ";"operators" "How does the plus operator (+) work";"

The plus operator works as follows in JavaScript:

String mode lets us use + to assemble strings:

> 'There are ' + 3 + ' items'
'There are 3 items'

Number mode means that if neither operand is a string (or an object that becomes a string) then everything is coerced to numbers:

> 4 + true
5

Number(true) is 1.


More information ";"operators" "Why is the == operator quirky?";"

It often coerces in unexpected ways

If the operands have different types, loose equality often coerces. Some of those type coercions make sense:

> '123' == 123
true
> false == 0
true

Others less so:

> 0 == '\r\n\t ' // only whitespace
true

An object is coerced to a primitive value (only) if the other operand is primitive:

> [1, 2, 3] == '1,2,3'
true
> ['17'] == 17
true

Comparing with boolean values works differently from conversion via Boolean()

> Boolean(0)
false
> Boolean(2)
true

> 0 == false
true
> 2 == true
false
> 2 == false
false

> Boolean('')
false
> Boolean('abc')
true

> '' == false
true
> 'abc' == true
false
> 'abc' == false
false

More information ";"operators" "What are the main differences between === and Object.is()";"

Object.is() is even stricter than === – e.g.:


More information ";"operators" "How does the comma operator (,) work?";"

The comma operator has two operands, evaluates both of them and returns the second one:

const result = (console.log('evaluated'), 'YES');
assert.equal(
  result, 'YES'
);

Output:

evaluated

More information ";"operators" "How does the void operator work?";"

The void operator evaluates its operand and returns undefined:

const result = void console.log('evaluated');
assert.equal(
  result, undefined
);

Output:

evaluated

More information ";"operators" "How are undefined and null different?";"

Both values are very similar and often used interchangeably. How they differ is therefore subtle. The language itself makes the following distinction:

Programmers sometimes make the following distinction:


More information ";"undefined-null" "Where does undefined occur in the language?";"

Uninitialized variable myVar:

let myVar;
assert.equal(myVar, undefined);

Parameter x is not provided:

function func(x) {
  return x;
}
assert.equal(func(), undefined);

Property .unknownProp is missing:

const obj = {};
assert.equal(obj.unknownProp, undefined);

If we don’t explicitly specify the result of a function via a return statement, JavaScript returns undefined for us:

function func() {}
assert.equal(func(), undefined);

More information ";"undefined-null" "Where does null occur in the language?";"

The prototype of an object is either an object or, at the end of a chain of prototypes, null. Object.prototype does not have a prototype:

> Object.getPrototypeOf(Object.prototype)
null

If we match a regular expression (such as /a/) against a string (such as 'x'), we either get an object with matching data (if matching was successful) or null (if matching failed):

> /a/.exec('x')
null

The JSON data format does not support undefined, only null:

> JSON.stringify({a: undefined, b: null})
'{"b":null}'

More information ";"undefined-null" "How does the nullish coalescing operator (??) work?";"

The nullish coalescing operator (??) lets us use a default if a value is undefined or null:

value ?? defaultValue

Examples:

> undefined ?? 'default'
'default'
> null ?? 'default'
'default'
> false ?? 'default'
false
> 0 ?? 'default'
0
> '' ?? 'default'
''
> {} ?? 'default'
{}

More information ";"undefined-null" "How does the nullish coalescing assignment operator (??=) work?";"

The nullish coalescing assignment operator (??=) assigns a default if a value is undefined or null:

value ??= defaultValue

Examples:

let value;

value = undefined;
value ??= 'DEFAULT';
assert.equal(
  value, 'DEFAULT'
);

value = 0;
value ??= 'DEFAULT';
assert.equal(
  value, 0
);

More information ";"undefined-null" "For which values do we get an error if we read a property?";"

undefined and null are the only two JavaScript values where we get an exception if we try to read a property. To explore this phenomenon, let’s use the following function, which reads (“gets”) property .prop and returns the result.

function getProp(x) {
  return x.prop;
}

If we apply getProp() to various values, we can see that it only fails for undefined and null:

> getProp(undefined)
TypeError: Cannot read properties of undefined (reading 'prop')
> getProp(null)
TypeError: Cannot read properties of null (reading 'prop')

> getProp(true)
undefined
> getProp({})
undefined

More information ";"undefined-null" "Why does JavaScript have two non-values undefined and null?";"

In Java (which inspired many aspects of JavaScript), initialization values depend on the static type of a variable:

JavaScript borrowed null and uses it where objects are expected. It means “not an object”.

However, storage locations in JavaScript (variables, properties, etc.) can hold either primitive values or objects. They need an initialization value that means “neither an object nor a primitive value”. That’s why undefined was introduced.


More information ";"undefined-null" "What is truthiness and falsiness?";"

In most locations where JavaScript expects a boolean value, we can instead use an arbitrary value and JavaScript converts it to boolean before using it. Examples include:

Consider the following if statement:

if (value) {}

In many programming languages, this condition is equivalent to:

if (value === true) {}

However, in JavaScript, it is equivalent to:

if (Boolean(value) === true) {}

That is, JavaScript checks if value is true when converted to boolean. This kind of check is so common that the following names were introduced:

Each value is either truthy or falsy. This is an exhaustive list of falsy values:

All other values (including all objects) are truthy:

> Boolean('abc')
true
> Boolean([])
true
> Boolean({})
true

More information ";"booleans" "What is the expression version of an if statement?";"

The conditional operator is the expression version of the if statement. Its syntax is:

«condition» ? «thenExpression» : «elseExpression»

It is evaluated as follows:

The conditional operator is also called ternary operator because it has three operands.

Examples:

> true ? 'yes' : 'no'
'yes'
> false ? 'yes' : 'no'
'no'
> '' ? 'yes' : 'no'
'no'

More information ";"booleans" "What are JavaScript’s binary logical operators?";"

JavaScript has two binary logical operators:


More information ";"booleans" "How are binary logical operators value-preserving?";"

Value-preservation means that operands are interpreted as booleans but returned unchanged:

> 12 || 'hello'
12
> 0 || 'hello'
'hello'

More information ";"booleans" "What is short-circuiting?";"

Short-circuiting means if the first operand already determines the result, then the second operand is not evaluated. The only other operator that delays evaluating its operands is the conditional operator. Usually, all operands are evaluated before performing an operation.

For example, logical And (&&) does not evaluate its second operand if the first one is falsy:

const x = false && console.log('hello');
// No output

If the first operand is truthy, console.log() is executed:

const x = true && console.log('hello');

Output:

hello

More information ";"booleans" "What is JavaScript’s logical Not operator?";"

The expression !x (“Not x”) is evaluated as follows:

  1. Evaluate x.
  2. Coerce the result to boolean.
  3. Is that result true? Return false.
  4. Return true.

Examples:

> !false
true
> !true
false

> !0
true
> !123
false

> !''
true
> !'abc'
false

More information ";"booleans" "How many numeric types does JavaScript have?";"

JavaScript has two kinds of numeric values:


More information ";"numbers" "What kind of integer literals does JavaScript have?";"

Several integer literals let us express integers with various bases:

// Binary (base 2)
assert.equal(0b11, 3); // ES6

// Octal (base 8)
assert.equal(0o10, 8); // ES6

// Decimal (base 10)
assert.equal(35, 35);

// Hexadecimal (base 16)
assert.equal(0xE7, 231);

More information ";"numbers" "What does the exponent e mean in, e.g., 3e2?";"

Exponent: eN means ×10N

> 3e2
300
> 3e-2
0.03
> 0.3e2
30

More information ";"numbers" "How can we access properties of decimal integer literals?";"
(7).toString(2)
7.0.toString(2)
7..toString(2)
7 .toString(2)  // space before dot

More information ";"numbers" "How can arbitrary values be converted to numbers?";"

These are three ways of converting values to numbers:


More information ";"numbers" "What numeric error values does JavaScript have?";"

JavaScript has two numeric error values:


More information ";"numbers" "Which decimal fractions can’t be represented precisely by floating point numbers?";"

JavaScript has floating point numbers that internally have the base 2. Therefore:


More information ";"numbers" "How are integers different from floating point numbers with fractions?";"

JavaScript (non-bigint) integers are simply floating point numbers without decimal fractions. But they are different in the following ways:


More information ";"numbers" "What is a safe integer?";"

This is the range of integer numbers that are safe in JavaScript (53 bits plus a sign):

[–(253)+1, 253–1]

An integer is safe if it is represented by exactly one JavaScript number. Given that JavaScript numbers are encoded as a fraction multiplied by 2 to the power of an exponent, higher integers can also be represented, but then there are gaps between them.

For example (18014398509481984 is 254):

> 18014398509481983
18014398509481984
> 18014398509481984
18014398509481984
> 18014398509481985
18014398509481984
> 18014398509481986
18014398509481984
> 18014398509481987
18014398509481988

The following mathematical integers are therefore not safe:


More information ";"numbers" "How many bits can we use with bitwise operators such as And (&), Or (|)?";"

Internally, JavaScript’s bitwise operators work with 32-bit integers. They produce their results in the following steps:


More information ";"numbers" "Why were bigints added to JavaScript?";"

Before ECMAScript 2020, JavaScript handled integers as follows:

Sometimes, we need more than signed 53 bits – for example:


More information ";"bigints" "How do bigints work?";"

Bigint is a primitive data type for integers. Bigints don’t have a fixed storage size in bits; their sizes adapt to the integers they represent:

A bigint literal is a sequence of one or more digits, suffixed with an n – for example:

123n

Operators such as - and * are overloaded and work with bigints:

> 123n * 456n
56088n

Bigints are primitive values. typeof returns a distinct result for them:

> typeof 123n
'bigint'

More information ";"bigints" "What are Unicode code points and Unicode code units?";"

Two concepts are crucial for understanding Unicode:


More information ";"unicode" "How are Unicode code points structured?";"

The first version of Unicode had 16-bit code points. Since then, the number of characters has grown considerably and the size of code points was extended to 21 bits. These 21 bits are partitioned in 17 planes, with 16 bits each:

Planes 1-16 are called supplementary planes or astral planes.


More information ";"unicode" "Which Unicode encodings are used in web development?";"

The Unicode encoding formats that are used in web development are: UTF-16 and UTF-8.

Source code internally: UTF-16

The ECMAScript specification internally represents source code as UTF-16.

Strings: UTF-16

The characters in JavaScript strings are based on UTF-16 code units:

> const smiley = '🙂';
> smiley.length
2
> smiley === '\uD83D\uDE42' // code units
true

Source code in files: UTF-8

HTML and JavaScript files are almost always encoded as UTF-8 now.

For example, this is how HTML files usually start now:

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
···

More information ";"unicode" "What are grapheme clusters and glyphs in Unicode?";"

A symbol is an abstract concept and part of written language:


More information ";"unicode" "What kinds of string literals does JavaScript have?";"
const str1 = 'Don\'t say "goodbye"'; // string literal
const str2 = "Don't say \"goodbye\""; // string literals
assert.equal(
  `As easy as ${123}!`, // template literal
  'As easy as 123!',
);

More information ";"strings" "What are the most common ways of converting values to strings? What are tricky values?";"
String(v)'' + vv.toString()
undefined'undefined''undefined'TypeError
null'null''null'TypeError
true'true''true''true'
123'123''123''123'
123n'123''123''123'
"abc"'abc''abc''abc'
Symbol()'Symbol()'TypeError'Symbol()'
{a:1}'[object Object]''[object Object]''[object Object]'
['a']'a''a''a'
{__proto__:null}TypeErrorTypeErrorTypeError
Symbol.prototypeTypeErrorTypeErrorTypeError
() => {}'() => {}''() => {}''() => {}'

`${v}` is equivalent to '' + v.

The most common tricky values are:


More information ";"strings" "What are the units that make up strings in JavaScript?";"
More information ";"strings" "How to escape UTF-16 code units and code points in string literals?";"

Code units – code unit escape:

> '\u03B1\u03B2\u03B3'
'αβγ'

Code points - code point escape:

> '\u{1F642}'
'🙂'

Code points below 256 – ASCII escape:

> 'He\x6C\x6Co'
'Hello'

More information ";"strings" "How to concatenate strings?";"

Operator +

assert.equal(3 + ' times ' + 4, '3 times 4');

Operator +=

let str = ''; // must be `let`!
str += 'Say it';
str += ' one more';
str += ' time';

assert.equal(str, 'Say it one more time');

Joining an Array

function getPackingList(isAbroad = false, days = 1) {
  const items = [];
  items.push('tooth brush');
  if (isAbroad) {
    items.push('passport');
  }
  if (days > 3) {
    items.push('water bottle');
  }
  return items.join(', '); // (A)
}
assert.equal(
  getPackingList(),
  'tooth brush'
);
assert.equal(
  getPackingList(true, 7),
  'tooth brush, passport, water bottle'
);

More information ";"strings" "How do tag functions work?";"

The expression in line A is a tagged template. It is equivalent to invoking tagFunc() with the arguments shown below line A.

function tagFunc(templateStrings, ...substitutions) {
  return {templateStrings, substitutions};
}

const setting = 'dark mode';
const value = true;

assert.deepEqual(
  tagFunc`Setting ${setting} is ${value}!`, // (A)
  {
    templateStrings: ['Setting ', ' is ', '!'],
    substitutions: ['dark mode', true],
  }
  // tagFunc(['Setting ', ' is ', '!'], 'dark mode', true)
);

More information ";"template-literals" "Explain two ways of using multiline template literals";"

Option 1 – indenting the text and removing the indentation via a template tag:

import dedent from 'dedent';
function divDedented(text) {
  return dedent`
    <div>
      ${text}
    </div>
  `;
}

Option 2 – not indenting the text and removing leading and trailing whitespace via .trim():

function divDedented(text) {
  return `
<div>
  ${text}
</div>
  `.trim();
}

More information ";"template-literals" "What does String.raw do?";"

Raw string literals are implemented via the tag function String.raw. They are string literals where backslashes don’t do anything special (such as escaping characters, etc.):

assert.equal(
  String.raw`\back`,
  '\\back'
);

More information ";"template-literals" "What is the nature of symbols?";"

Symbols are primitive values:

Even though symbols are primitives, they are also like objects in that values created by Symbol() have unique identities and are not compared by value:

> Symbol() === Symbol()
false

More information ";"symbols" "What are use cases for symbols?";"

Symbols as values for constants

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

function getComplement(color) {
  switch (color) {
    case COLOR_RED:
      return COLOR_GREEN;
    case COLOR_ORANGE:
      return COLOR_BLUE;
    case COLOR_YELLOW:
      return COLOR_VIOLET;
    case COLOR_GREEN:
      return COLOR_RED;
    case COLOR_BLUE:
      return COLOR_ORANGE;
    case COLOR_VIOLET:
      return COLOR_YELLOW;
    default:
      throw new Exception('Unknown color: '+color);
  }
}
assert.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET);

Symbols as unique property keys

const specialMethod = Symbol('specialMethod');
const obj = {
  _id: 'kf12oi',
  [specialMethod]() { // (A)
    return this._id;
  }
};
assert.equal(obj[specialMethod](), 'kf12oi');

More information ";"symbols" "Why are symbols useful as property keys?";"

The keys of properties (fields) in objects are used at two levels:

The base level and the meta-level of a program must be independent: Base-level property keys should not be in conflict with meta-level property keys.

Symbols, used as property keys, help us here: Each symbol is unique and a symbol key never clashes with any other string or symbol key.


More information ";"symbols" "What control flow statements does JavaScript have?";"
More information ";"control-flow" "What statement lets us leave a loop early?";"

There are two versions of break:

The former version works inside the following statements: while, do-while, for, for-of, for-await-of, for-in and switch. It immediately leaves the current statement:

for (const x of ['a', 'b', 'c']) {
  console.log(x);
  if (x === 'b') break;
  console.log('---')
}

Output:

a
---
b

break plus label: leaving any labeled statement

break with an operand works everywhere. Its operand is a label. Labels can be put in front of any statement, including blocks. break myLabel leaves the statement whose label is myLabel:

myLabel: { // label
  if (condition) break myLabel; // labeled break
  // ···
}

More information ";"control-flow" "What statement lets us skip to the next loop iteration?";"

continue only works inside while, do-while, for, for-of, for-await-of, and for-in. It immediately leaves the current loop iteration and continues with the next one – for example:

const lines = [
  'Normal line',
  '# Comment',
  'Another normal line',
];
for (const line of lines) {
  if (line.startsWith('#')) continue;
  console.log(line);
}

Output:

Normal line
Another normal line

More information ";"control-flow" "What values can be used as conditions of control flow statements?";"

if, while, and do-while have conditions that are, in principle, boolean. However, a condition only has to be truthy (true if coerced to boolean) in order to be accepted. In other words, the following two control flow statements are equivalent:

if (value) {}
if (Boolean(value) === true) {}

More information ";"control-flow" "How to choose between for-await-of, for-of, .forEach(), for and for-in?";"
More information ";"control-flow" "Explain what try, catch and finally clauses do";"
try {
  «try_statements»
} catch (error) {
  «catch_statements»
} finally {
  «finally_statements»
}

The try block can be considered the body of the statement. This is where we execute the regular code.

If an exception is thrown somewhere inside the try block (which may happen deeply nested inside the tree of function/method calls) then execution switches to the catch clause where the parameter refers to the exception. After that, execution normally continues after the try statement.

The code inside the finally clause is always executed at the end of a try statement – no matter what happens in the try block or the catch clause.


More information ";"exception-handling" "What properties do Error and its subclasses have?";"

This is what Error’s instance properties and constructor look like:

class Error {
  // Actually a prototype data property
  get name(): string {
    return 'Error';
  }

  // Instance properties
  message: string;
  cause?: unknown; // ES2022
  stack: string; // non-standard but widely supported

  constructor(
    message: string = '',
    options?: ErrorOptions // ES2022
  ) {}
}
interface ErrorOptions {
  cause?: unknown; // ES2022
}

The constructor has two parameters:


More information ";"exception-handling" "What kinds of functions are there?";"

JavaScript has two categories of functions:


More information ";"callables" "How do parameter default values work?";"

Parameter default values specify the value to use if a parameter has not been provided – for example:

function f(x, y=0) {
  return [x, y];
}

assert.deepEqual(f(1), [1, 0]);
assert.deepEqual(f(), [undefined, 0]);

undefined also triggers the default value:

assert.deepEqual(
  f(undefined, undefined),
  [undefined, 0]
);

More information ";"callables" "How do rest parameters work?";"

A rest parameter is declared by prefixing an identifier with three dots (...). During a function or method call, it receives an Array with all remaining arguments. If there are no extra arguments at the end, it is an empty Array – for example:

function f(x, ...y) {
  return [x, y];
}
assert.deepEqual(
  f('a', 'b', 'c'), ['a', ['b', 'c']]
);
assert.deepEqual(
  f(), [undefined, []]
);

More information ";"callables" "How can we simulate named parameters in JavaScript?";"

JavaScript doesn’t have real named parameters. The official way of simulating them is via object literals:

function selectEntries({start=0, end=-1, step=1}) {
  return {start, end, step};
}

More information ";"callables" "How does spreading into function calls work?";"

If we put three dots (...) in front of the argument of a function call, then we spread it. That means that the argument must be an iterable object and the iterated values all become arguments. In other words, a single argument is expanded into multiple arguments – for example:

function func(x, y) {
  console.log(x);
  console.log(y);
}
const someIterable = ['a', 'b'];
func(...someIterable);
  // same as func('a', 'b')

Output:

a
b

More information ";"callables" "Does the function method .call() work?";"

Each function someFunc has the following method:

someFunc.call(thisValue, arg1, arg2, arg3);

This method invocation is loosely equivalent to the following function call:

someFunc(arg1, arg2, arg3);

However, with .call(), we can also specify a value for the implicit parameter this. In other words: .call() makes the implicit parameter this explicit.


More information ";"callables" "Does the function method .apply() work?";"

Each function someFunc has the following method:

someFunc.apply(thisValue, [arg1, arg2, arg3]);

This method invocation is loosely equivalent to the following function call (which uses spreading):

someFunc(...[arg1, arg2, arg3]);

However, with .apply(), we can also specify a value for the implicit parameter this.


More information ";"callables" "Does the function method .bind() work?";"

.bind() is another method of function objects. This method is invoked as follows:

const boundFunc = someFunc.bind(thisValue, arg1, arg2);

.bind() returns a new function boundFunc(). Calling that function invokes someFunc() with this set to thisValue and these parameters: arg1, arg2, followed by the parameters of boundFunc().

That is, the following two function calls are equivalent:

boundFunc('a', 'b')
someFunc.call(thisValue, arg1, arg2, 'a', 'b')

More information ";"callables" "What is the difference between calling eval directly and indirectly?";"

There are two ways of invoking eval():

“Not via a function call” means “anything that looks different than eval(···)”:


More information ";"dynamic-code-evaluation" "How does new Function() work?";"

new Function() creates a function object and is invoked as follows:

const func = new Function('«param_1»', ···, '«param_n»', '«func_body»');

The previous statement is equivalent to the next statement. Note that «param_1», etc., are not inside string literals, anymore.

const func = function («param_1», ···, «param_n») {
  «func_body»
};

More information ";"dynamic-code-evaluation" "What are common JavaScript source code units?";"
UsageRuns onLoadedFilename ext.
ScriptLegacybrowsersasync.js
CommonJS moduleDecreasingserverssync.js .cjs
AMD moduleLegacybrowsersasync.js
ECMAScript moduleModernbrowsers, serversasync.js .mjs

More information ";"modules" "What is the difference between named exports and default exports?";"
More information ";"modules" "What is a namespace import?";"

Namespace imports are an alternative to named imports. If we namespace-import a module, it becomes an object whose properties are the named exports. This is what main.mjs looks like if we use a namespace import:

import * as myMath from './lib/my-math.mjs';
assert.equal(myMath.square(3), 9);

assert.deepEqual(
  Object.keys(myMath), ['LIGHT_SPEED', 'square']
);

More information ";"modules" "What is re-exporting?";"

Re-exporting turns another module’s exports into exports of the current module:

//===== library.mjs =====
// Named re-export [ES6]
export {internalFunc as func, INTERNAL_DEF as DEF} from './internal.mjs';
// Wildcard re-export [ES6]
export * from './internal.mjs';
// Namespace re-export [ES2020]
export * as ns from './internal.mjs';

More information ";"modules" "What is a package?";"

In the JavaScripte ecosystem, a package is a way of organizing software projects: It is a directory with a standardized layout. A package can contain all kinds of files - for example:

A package can depend on other packages (which are called its dependencies):


More information ";"modules" "What is a package registry and a package manager?";"

The main way of publishing a package is to upload it to a package registry – an online software repository. Two popular public registries are:

Companies can also host their own private registries.

A package manager is a command line tool that downloads packages from a registry (or other sources) and installs them as shell scripts and/or as dependencies. The most popular package manager is called npm and comes bundled with Node.js. Its name originally stood for “Node Package Manager”. Later, when npm and the npm registry were used not only for Node.js packages, that meaning was changed to “npm is not a package manager” ([source](https://en.wikipedia.org/wiki/Npm_(software)#Acronym)). There are other popular package managers such as jsr, vlt, pnpm and yarn. All of these package managers support either or both of the npm registry and JSR.


More information ";"modules" "What are the two kinds of names of npm packages?";"

Each package has a name. There are two kinds of names:


More information ";"modules" "What are the two most important file system entries of an npm package?";"

Once a package my-package is fully installed, it almost always looks like this:

my-package/
  package.json
  node_modules/
  [More files]

What are the purposes of these file system entries?


More information ";"modules" "What kinds of module specifiers are there?";"

There are three kinds of module specifiers:


More information ";"modules" "What is import.meta.url?";"

The most important property of import.meta is .url which contains a string with the URL of the current module’s file – for example:

'https://example.com/code/main.mjs'

This is how we get a URL instance that points to a file data.txt that sits next to the current module:

const urlOfData = new URL('data.txt', import.meta.url);

More information ";"modules" "How can we import modules dynamically?";"

Limitations of the static import statement:

The import() operator doesn’t have the limitations of import statements. It looks like this:

const namespaceObject = await import(moduleSpecifierStr);
console.log(namespaceObject.namedExport);

This operator is used like a function, receives a string with a module specifier and returns a Promise that resolves to a namespace object. The properties of that object are the exports of the imported module.


More information ";"modules" "What is top-level await?";"

We can use the await operator at the top level of a module. If we do that, the module becomes asynchronous and works differently. Thankfully, we don’t usually see that as programmers because it is handled transparently by the language.


More information ";"modules" "What are import attributes?";"

The motivating use case for import attributes was importing JSON data as a module. That looks as follows:

import configData from './config-data.json' with { type: 'json' };

type is an import attribute (more on the syntax soon).

To support import attributes, dynamic imports get a second parameter – an object with configuration data:

const configData = await import(
  './config-data.json', { with: { type: 'json' } }
);

More information ";"modules" "What is a polyfill?";"

Given a web platform feature X:


More information ";"modules" "As a data structure, objects can be used in two ways. Explain.";"

There are two ways of using objects in JavaScript:

Note that the two ways can also be mixed: Some objects are both fixed-layout objects and dictionary objects.


More information ";"objects" "What are accessors in object literals?";"

Accessors are methods that are invoked by accessing a property. It consists of either or both of:

Getters

A getter is created by prefixing a method definition with the modifier get:

const jane = {
  first: 'Jane',
  last: 'Doe',
  get full() {
    return `${this.first} ${this.last}`;
  },
};

assert.equal(jane.full, 'Jane Doe');
jane.first = 'John';
assert.equal(jane.full, 'John Doe');

Setters

A setter is created by prefixing a method definition with the modifier set:

const jane = {
  first: 'Jane',
  last: 'Doe',
  set full(fullName) {
    const parts = fullName.split(' ');
    this.first = parts[0];
    this.last = parts[1];
  },
};

jane.full = 'Richard Roe';
assert.equal(jane.first, 'Richard');
assert.equal(jane.last, 'Roe');

More information ";"objects" "What is spreading into object literals and what can it be used for?";"

Inside an object literal, a spread property adds the properties of another object to the current one:

const obj1 = {a: 1, b: 2};
const obj2 = {c: 3};
assert.deepEqual(
  {...obj1, ...obj2, d: 4},
  {a: 1, b: 2, c: 3, d: 4}
);

Use cases include:


More information ";"objects" "What can and cannot structuredClone() copy?";"
More information ";"objects" "Use the function method .call() to explain how this works in method invocations";"

Methods are functions and functions have methods themselves. One of those methods is .call(). Let’s look at an example to understand how this method works.

In the previous section, there was this method invocation:

obj.someMethod('a', 'b')

This invocation is equivalent to:

obj.someMethod.call(obj, 'a', 'b');

Which is also equivalent to:

const func = obj.someMethod;
func.call(obj, 'a', 'b');

.call() makes the normally implicit parameter this explicit: When invoking a function via .call(), the first parameter is this, followed by the regular (explicit) function parameters.

As an aside, this means that there are actually two different dot operators:

  1. One for accessing properties: obj.prop
  2. Another one for calling methods: obj.prop()

They are different in that (2) is not just (1) followed by the function call operator (). Instead, (2) additionally provides a value for this.


More information ";"objects" "How to properly extract methods from objects?";"

Problem – invoking func() in line A does not provide the proper this:

const jane = {
  first: 'Jane',
  says(text) {
    return `${this.first} says “${text}”`;
  },
};
const func = jane.says; // extract the method
assert.throws(
  () => func('hello'), // (A)
  {
    name: 'TypeError',
    message: "Cannot read properties of undefined (reading 'first')",
  }
);

How do we fix this? We need to use .bind() to extract method .says():

const func2 = jane.says.bind(jane);
assert.equal(func2('hello'), 'Jane says “hello”');

The .bind() ensures that this is always jane when we call func().

We can also use arrow functions to extract methods:

const func3 = text => jane.says(text);
assert.equal(func3('hello'), 'Jane says “hello”');

More information ";"objects" "How can we avoid accidentally shadowing this?";"
More information ";"objects" "How does optional chaining work?";"

The following kinds of optional chaining operations exist:

obj?.prop     // optional fixed property getting
obj?.[«expr»] // optional dynamic property getting
func?.(«arg0», «arg1», ···) // optional function or method call

The rough idea is:


More information ";"objects" "How is listing property keys affected by symbols and enumerability?";"
enumerablenon-e.stringsymbol
Object.keys()
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Reflect.ownKeys()

More information ";"objects" "In which order are own properties listed?";"

Own (non-inherited) properties of objects are always listed in the following order:

  1. Properties with string keys that contain integer indices:
    In ascending numeric order
  2. Remaining properties with string keys:
    In the order in which they were added
  3. Properties with symbol keys:
    In the order in which they were added

More information ";"objects" "What are the pitfalls of using an object as a dictionary and how to avoid them?";"

If we use plain objects (created via object literals) as dictionaries, we have to look out for two pitfalls.

Pitfall 1: getting inherited properties

The following dictionary object should be empty. However, we get a value (and not undefined) if we read an inherited property:

const dict = {};
assert.equal(
  typeof dict['toString'], 'function'
);

dict is an instance of Object and inherits .toString() from Object.prototype.

Pitfall 2: checking if a property exists

If we use the in operator to check if a property exists, we again detect inherited properties:

const dict = {};
assert.equal(
  'toString' in dict, true
);

As an aside: Object.hasOwn() does not have this pitfall. As its name indicates, it only considers own (non-inherited) properties:

const dict = {};
assert.equal(
  Object.hasOwn(dict, 'toString'), false
);

Pitfall 3: property key '__proto__'

We can’t use the property key '__proto__' because it has special powers (it sets the prototype of the object):

const dict = {};

dict['__proto__'] = 123;
// No property was added to dict:
assert.deepEqual(
  Object.keys(dict), []
);

Objects with null prototypes as dictionaries

Maps are usually the best choice when it comes to dictionaries: They have a convenient method-based API and support keys beyond strings and symbols.

However, objects with null prototypes are also decent dictionaries and don’t have the pitfalls we just encountered:

const dict = Object.create(null);

// No inherited properties
assert.equal(
  dict['toString'], undefined
);
assert.equal(
  'toString' in dict, false
);

// No special behavior with key '__proto__'
dict['__proto__'] = true;
assert.deepEqual(
  Object.keys(dict), ['__proto__']
);

We avoided the pitfalls:


More information ";"objects" "What are property attributes?";"

Just as objects are composed of properties, properties are composed of attributes. There are two kinds of properties and they are characterized by their attributes:

Additionally, there are attributes that both kinds of properties have. The following table lists all attributes and their default values.

Kind of propertyName and type of attributeDefault value
All propertiesconfigurable: booleanfalse
enumerable: booleanfalse
Data propertyvalue: anyundefined
writable: booleanfalse
Accessor propertyget: (this: any) => anyundefined
set: (this: any, v: any) => voidundefined

More information ";"objects" "What are the levels of making objects less mutable?";"

JavaScript has three levels of protecting objects:


More information ";"objects" "Where are private slots stored?";"

Private slots have unique keys that are similar to symbols. Consider the following class from earlier:

class MyClass {
  #instancePrivateField = 1;
  instanceProperty = 2;
  getInstanceValues() {
    return [
      this.#instancePrivateField,
      this.instanceProperty,
    ];
  }
}

Internally, the private field of MyClass is handled roughly like this:

let MyClass;
{ // Scope of the body of the class
  const instancePrivateFieldKey = Symbol();
  MyClass = class {
    __PrivateElements__ = new Map([
      [instancePrivateFieldKey, 1],
    ]);
    instanceProperty = 2;
    getInstanceValues() {
      return [
        this.__PrivateElements__.get(instancePrivateFieldKey),
        this.instanceProperty,
      ];
    }
  }
}

The value of instancePrivateFieldKey is called a private name. We can’t use private names directly in JavaScript, we can only use them indirectly, via the fixed identifiers of private fields, private methods, and private accessors. Where the fixed identifiers of public slots (such as getInstanceValues) are interpreted as string keys, the fixed identifiers of private slots (such as #instancePrivateField) refer to private names (similarly to how variable names refer to values).


More information ";"classes" "How are the following connected: a superclass Sup, a subclass Sub and s = new Sub()?";"
More information ";"classes" "Where are the following class members stored?";"
class C {
  static staticMethod() {}
  method() {}
  field = 1;
}


More information ";"classes" "What does v instanceof C actually check?";"

How does instanceof determine if a value x is an instance of a class C? Note that “instance of C” means direct instance of C or direct instance of a subclass of C.

instanceof checks if C.prototype is in the prototype chain of x. That is, the following two expressions are equivalent:

x instanceof C
C.prototype.isPrototypeOf(x)

More information ";"classes"