---
title: 'JavaScript Fundamentals'
source: 'https://academia.sh/en/courses/javascript-fundamentals'
language: en
updated: '2026-08-17T04:41:32+00:00'
license: 'CC BY-SA 4.0'
lessons: 21
outcomes:
    - 'Uses variable declaration, type conversion, and equality rules correctly'
    - 'Writes data transformations with the basic data structures (array, object)'
    - 'Runs a script in the browser and in a runtime and reads its errors'
---

# JavaScript Fundamentals

The value model of the language, declaration and scope rules, type coercion and equality algorithms, and the built-in data structures; running a script and reading its errors.

## Language Structure

The distinction between language, runtime, and standard; ways to run code; the scope difference between declaration kinds and hoisting.

### What Is JavaScript

Source: https://academia.sh/en/courses/javascript-fundamentals/what-is-javascript

The distinction between language, engine, runtime, and standard; the boundary between what ECMAScript defines and what the host environment adds.

The Programming Fundamentals course established the concepts of variable, expression,
condition, loop, and function independent of any language. This course does not look at
what those same concepts correspond to in a single language; it looks at that language's
own particular decisions: which scope a name is seen in, which rule decides whether two
values are equal, which steps convert one type into another.

Before starting this examination, a distinction is needed. The name "JavaScript" covers
three separate things at once in everyday speech: a language, a program that runs that
language, and the services around that program. This lesson's subject is the boundary
between these three. Without knowing the boundary, the question of why a script running
in a browser does not run on a server also has no answer.

#### Language, Engine, and Runtime

The **language** is syntax and semantics: which sequences of characters make up a valid
program, and what a valid program means. Constructs like `const`, `+`, and `if`, and
their behavior, belong to the language.

The **engine** is the program that runs the language. It parses the source text,
converts it into an internal representation, and executes it. The hybrid model from the
How Computers Work course's Compiler, Interpreter, and Virtual Machine lesson applies
here: the source is first converted into bytecode, and frequently run sections are then
converted into machine code through just-in-time compilation. This is why "JavaScript is
an interpreted language" is incomplete; the execution model is the engine's decision,
not part of the language's definition.

The **runtime** is the host environment around the engine. The engine alone cannot read
a file, write to a screen, or connect to a network. The host environment supplies these
abilities. A browser is a host environment; it offers document objects and page events.
A server-side runtime is also a host environment; it offers a file system and network
sockets.

The three layers can change independently of one another. The same language can be run
by different engines; the same engine can be embedded inside different host
environments.

#### What the Standard Says, and What It Does Not

The language's definition is the standard called **ECMAScript**. The relationship
between this name and "JavaScript" is historical; throughout this course, **JavaScript**
will be written when the language itself is meant, and **ECMAScript** when the rule set
by the standard is meant.

The standard's scope is fixed:

- Syntax and grammar.
- Value types and the operations on them.
- Built-in objects: `Object`, `Array`, `String`, `Math`, `JSON`, `Date`, `Map`, `Set`,
  and the like.
- Abstract operations: step-by-step defined procedures such as how two values are
  compared, or how a value is converted to a number. This course's Values and Types
  topic reads largely from these procedures.

What falls outside the standard's scope matters just as much. Writing to the screen,
reading a file, sending a network request, setting a timer, and accessing a document
are not defined in ECMAScript. All of this work comes from the host environment. Even
the `console` object is not part of the standard; the reason it is commonly found is
not that it is standard, but that host environments choose to provide it.

#### Testing Where a Name Comes From

The distinction should not stay abstract: which layer a name comes from can be tested
directly. The `typeof` operator produces `"undefined"` for an undefined name without
throwing an error, which makes it suitable for probing.

```js
console.log(typeof globalThis.Math);      // comes from the standard
console.log(typeof globalThis.JSON);      // comes from the standard
console.log(typeof globalThis.console);   // comes from the host environment
console.log(typeof globalThis.document);  // exists only in a document-based environment
```

When run in a server-side runtime:

```
object
object
object
undefined
```

`Math` and `JSON` are found in every ECMAScript environment. `console` exists in this
environment, but it comes not from the standard, it comes from the environment itself.
`document` does not exist: a document object is something a page-displaying environment
supplies. When the same four lines are run in a browser console, the fourth line
produces `object`; the first three do not change.

`globalThis` is the name for reaching that environment's global object in a portable
way. Because the global object's name differs across environments, portable code uses
this name.

A practical rule follows from this: if a script is meant to "run everywhere," it must
rely only on the names the standard defines. The moment file reading or writing to the
screen is needed, the script has bound itself to a host environment.

#### How a Script Is Executed

When a file is run, there are two separate phases, and the difference between them is
the foundation for reading error messages.

**Parsing** happens first. The entire source text is resolved according to the grammar.
An error found at this stage is a syntax error, and not a single line of the program
runs:

```js
console.log("does this line print?");
const x = ;
```

When this file is run, `does this line print?` is **not printed**. The output only
reports the error; the core of the message is this:

```
SyntaxError: Unexpected token ';'
```

If parsing succeeds, **execution** begins. An error arising at this stage is a runtime
error, and the lines before it have already run:

```js
console.log("this line prints");
const record = null;
console.log(record.temperature);
```

```
this line prints
```

Execution then stops, and the following message is given:

```
TypeError: Cannot read properties of null (reading 'temperature')
```

In both cases, the runtime writes the file's full path, the line the error is on, and a
stack trace before the message. These lines vary by machine and version, so this course
shows only the unchanging part of an error's output. Reading an error message is
detailed in the Ways to Run Code lesson.

The threefold distinction established in the Programming Fundamentals course's What Is a
Program lesson — syntax error, runtime error, logic error — applies here exactly as it
was. The only thing that changes is that which stage the error appears at can now be
observed concretely.

#### The Course's Axis: Measurement Records

Throughout this course, a single concrete problem will be worked on: a list of readings
coming from a set of measurement stations. This list will grow lesson by lesson; first
plain numbers, then records with fields, and eventually a data set that is read from
text, converted, and written back to text.

Its first form is nothing more than four temperature readings:

```js
const temperatures = [21.5, 19.75, 23, 18.25];
let total = 0;
for (const value of temperatures) {
  total += value;
}
console.log("reading count:", temperatures.length);
console.log("highest:", Math.max(...temperatures));
console.log("average:", total / temperatures.length);
```

```
reading count: 4
highest: 23
average: 20.625
```

Every name in this script belongs to one of two sources. `const`, `let`, `for`, and `of`
belong to the language; `Array` and `Math` belong to the standard's built-in objects;
`console` belongs to the host environment. Running the script in a browser changes only
the last binding — the language's and the built-in objects' behavior stay the same.

Notice the `23` in the output: `23` was written in the source, and `23` is what is
printed, not `23.0`. This detail of number display is the subject of the Values and
Types topic, and is a direct consequence of the How Computers Work course's Floating-
Point Numbers lesson.

#### Summary

- The name "JavaScript" covers three layers at once: the language, the engine that runs
  it, and the host environment around the engine. The three can change independently.
- The language's definition is the ECMAScript standard; the standard covers syntax,
  value types, built-in objects, and abstract operations.
- Writing to the screen, reading files, network access, and document objects are not in
  the standard; they come from the host environment. Even `console` is in this class.
- The probe `typeof globalThis.name` tests which layer a name comes from without
  throwing an error.
- A parsing error runs no line at all; a runtime error appears after the lines before it
  have already run.

#### Next Step

Where a script runs determines which names are found. The next lesson takes these
execution paths one by one: the browser console, a script tag attached to a page, and a
server-side runtime invoked from the command line. The same measurement script will be
run all three ways; where the difference between them comes from, and which part of an
error message is worth reading, will be seen.

### Ways to Run Code

Source: https://academia.sh/en/courses/javascript-fundamentals/ways-to-run-code

Running from a file, the interactive shell, the browser console, and the script tag; the script–module distinction and reading an error message.

The previous lesson showed that which names a script can see depends on the host
environment. This lesson looks at how code is handed to those environments: a file from
the command line, an interactive shell session, the browser console, and a script tag
attached to a page. The four paths are not merely "ways to start code"; each also
determines the rules under which the code will be interpreted.

The same measurement script will be run through all of these paths. Where the results
match and where they diverge draws the boundary between the language's core and the
environment's contribution a second time.

#### Running a File from the Command Line

The server-side runtime takes its source from a file. The examples in this course have
been run with the `node` command; the name and extension given to the file determine how
the source is interpreted.

Save the following code to a file named `measurement.mjs`:

```js
// measurement.mjs
const temperatures = [21.5, 19.75, 23, 18.25];
let total = 0;
for (const value of temperatures) {
  total += value;
}
console.log("reading count:", temperatures.length);
console.log("average:", total / temperatures.length);
```

Run the file with `node` in the shell:

```sh
node measurement.mjs
```

```
reading count: 4
average: 20.625
```

The file is read start to finish, parsed, and executed. The process ends once no
pending work is left after the code's last line finishes.

For one-line experiments, there is no need to open a file; the source can be given
directly as an argument:

```sh
node -e 'console.log(Math.max(21.5, 19.75, 23))'
```

```
23
```

#### The Interactive Shell

When called with no arguments, the runtime opens an **interactive shell (REPL)**. This
shell reads each line, evaluates it, prints the result, and loops back. Its name comes
from these four steps.

```
> const temperatures = [21.5, 19.75, 23, 18.25];
undefined
> temperatures.length
4
> Math.max(...temperatures)
23
> typeof temperatures
'object'
```

Two observations are enough to understand this shell.

First: the shell prints each line's **value**. A declaration statement produces no
value; this is why the first line's result is `undefined`. This does not mean the
declaration failed — the next line reads the variable without any problem. The
statement–expression distinction from the Programming Fundamentals course becomes
directly visible here: `const` is a statement, `temperatures.length` is an expression.

Second: the format the shell prints is not the same as the `console.log` format. The
result of `typeof` was written in the shell as `'object'`, with quotes; printed with
`console.log`, the same value would appear as `object`, without quotes. The shell prints
a value in an **inspection format**: strings are quoted, so the number `4` and the text
`'4'` can be told apart. This format's details are specific to the runtime and are not
defined in the standard.

The same difference shows up with objects too:

```js
const record = { station: "A1", temperature: 21.5 };
console.log(record);
console.log(String(record));
```

```
{ station: 'A1', temperature: 21.5 }
[object Object]
```

The readable form on the first line is the environment's inspection format. The second
line is the result of the language's own to-string rule and is the same in every
environment. The browser console shows a collapsible tree for the first line; the
second line still produces `[object Object]` there too.

#### The Browser Console and the Script Tag

In a document-based environment, code enters two ways. The **console** is an
interactive shell opened in the context of the page; the same rules above apply exactly.
The **script tag** binds code to the page's loading process:

```html
<script src="measurement.js"></script>
```

The tag's position and attributes determine the moment of execution. If no attribute is
given, document parsing stops, the source is downloaded and run, then parsing resumes.
The `defer` attribute delays execution to the end of document parsing; the
`type="module"` attribute both applies the same delay and loads the source as a module
rather than a script.

This course's examples can be verified in the console and from the command line; since
they do not touch document objects, they do not require a page context. Code that
accesses the document is the subject of the host environment, not the language.

#### Script or Module

ECMAScript parses a source text according to one of two separate targets: **script**
and **module**. This is a decision derived from the file extension or the tag's
attribute, and it produces three concrete differences.

The first difference is **strict mode**. Source loaded as a module always runs in
strict mode; source loaded as a script does not, unless requested separately. Strict
mode turns several behaviors that would silently produce a wrong result into an error.
Its most visible example is assigning to an undeclared name:

```js
try {
  undeclaredName = 5;
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

When run as a module (with an `.mjs` extension):

```
ReferenceError: undeclaredName is not defined
```

The same assignment throws no error in a non-strict script; it silently creates a global
name. This is why a name created by a typo can go on living throughout the program.

The second difference is **the value of `this` in the outermost scope**. In a module,
this value is `undefined`; in a script, it is an object chosen by the environment.

The third difference is **whether names declared in the outermost scope get written
onto the global object**. In a module, the outermost scope is private to the module;
no name declared there becomes a property of the global object:

```js
var x = 1;
console.log(typeof this);
console.log(typeof globalThis.x);
```

When run as a module:

```
undefined
undefined
```

In a source loaded as a classic script, on the other hand, outermost-scope names
declared with `var` do become properties of the global object. Two files accidentally
overwriting each other's names is a consequence of this behavior; the module model
removes this sharing.

Throughout this course, examples have been run as modules. If an example behaves
differently only in script mode, this will be noted separately in the text.

#### Reading an Error Message

An uncaught error carries three pieces of information: the error's **name**,
**message**, and **stack trace**. All three can be read separately:

```js
const records = [{ station: "A1", temperature: 21.5 }];
try {
  console.log(records[1].temperature);
} catch (error) {
  console.log("name   :", error.name);
  console.log("message:", error.message);
}
```

```
name   : TypeError
message: Cannot read properties of undefined (reading 'temperature')
```

**Name** gives the error's class. `TypeError` reports that a value was not of the
expected kind, `ReferenceError` that a name could not be found, `SyntaxError` that the
source could not be parsed.

**Message** is the event's detail. Reading the message above step by step directly says
where the error is: the value of `records[1]` came out `undefined`, and a `temperature`
property was then looked up on `undefined`. So the real problem is that the array's
second element does not exist — not the property's name. An error message shows the
**symptom** of the error; the cause is usually a line above it.

**Stack trace** lists, in lines starting with `at`, the call chain the error passed
through: the topmost line is where it occurred, the lines below give who called from
where. Because its format, file paths, and the runtime's internal frames vary by
machine, only the name and message sections will be shown in this course.

An uncaught error terminates the process with a nonzero exit code in a server-side
runtime. In a browser, the page keeps running; the error is only written to the
console. The same error's outcome differs across the two environments, but its message
is the same.

#### Summary

- Code enters four ways: a file, an interactive shell, the browser console, and a
  script tag in a document context.
- The interactive shell prints each line's value in an inspection format; because
  declarations produce no value, `undefined` appears. This format comes from the
  environment, not the standard.
- ECMAScript parses a source as either a script or a module; a module always runs in
  strict mode, its outermost `this` value is `undefined`, and its names are not written
  onto the global object.
- Strict mode turns assignment to an undeclared name into a `ReferenceError` instead of
  silently creating a global name.
- An error message is made up of name, message, and stack trace; the message describes
  the symptom, and the cause is usually in the step before it.

#### Next Step

The script being loaded as a module, the presence of `const` and `let` declarations,
the `for...of` loop — all of these are abilities the language gained at a specific
point. The next lesson takes up how a feature enters the language: how the standard is
versioned, the stages a proposal passes through, and how the question "does this
feature exist here" gets answered without looking at a version name.

### Language Versions and Standardization

Source: https://academia.sh/en/courses/javascript-fundamentals/language-versions-and-standardization

How ECMAScript editions are versioned, proposal stages, the traces backward compatibility leaves in the language, and feature detection.

The previous lesson used module mode, the `const` declaration, and the `for...of` loop.
None of these existed in the language's first form; each entered the standard with a
specific edition. Whether a feature "is in the language" and whether it "works in the
environment you have" are separate questions, and their answers come from separate
places.

This lesson covers how the standard is versioned, which stages a proposal passes
through, and what lasting traces the concern for compatibility leaves in the language.
It closes with a practical technique: testing whether an ability exists without looking
at a version name.

#### Versioning the Standard

The language's definition is the document numbered ECMA-262. The document is versioned
as an **edition**; an edition is a complete text containing every feature accepted into
the standard up to that point. Editions add on top of previous editions; a feature that
has been published is not removed in a later edition.

Editions moved to a yearly schedule starting with ECMAScript 2015, and from that edition
on are referred to by their publication year. This is a technical naming convention: the
sentence "as of ES2015, block-scoped declarations exist" says which text defines them.
An edition's name says nothing about where that feature can be used; that question
belongs to the environment.

Version names will be used in this course only where a behavior's origin needs to be
shown. For instance, the sentence "block scope arrived with ES2015" explains why the
difference between `var` and `let` exists.

#### How a Feature Enters the Language

Proposals go through a staged process in the technical committee that maintains the
language. The stages are numbered zero to four, and each stage has an entry criterion:

- **Stage 0 — idea.** An unformatted proposal submitted to the committee.
- **Stage 1 — proposal.** The problem to be solved, example usage, and possible
  difficulties are written down. The committee has found the problem worth taking up.
- **Stage 2 — draft.** The syntax and semantics are written formally, in the standard's
  language. Changes made after this stage are at the level of detail.
- **Stage 3 — candidate.** The text is complete and tested; the turn has come to
  implementation and usage feedback. Implementations are gathered at this stage;
  requiring them is the next stage's criterion.
- **Stage 4 — finished.** Independent implementations and conformance tests are ready.
  The proposal enters the next edition.

The process has two consequences. First, a feature may work in some environments before
it enters the standard; second, a proposal that has not reached stage 4 can still
change. The observation "this syntax works this way right now" does not show that the
syntax is permanent.

#### The Cost of Backward Compatibility

The strongest constraint the standard carries is that previously published pages keep
working. This constraint leads even behaviors accepted as wrong to go uncorrected. It is
one of the most visible examples of the backward compatibility concept defined in the
Programming Fundamentals course's Choosing a Language lesson.

```js
console.log(typeof null);
console.log([] + {});
```

```
object
[object Object]
```

The first line's result is not the outcome of a definition but of a coding choice made
in the language's first implementation. `null` is not an object; but having `typeof
null` produce `"null"` would break code depending on this expression's result. The
second line is the type-coercion rules converting an array and an object to text and
concatenating them; it will be worked through step by step in the Type Conversion and
Coercion lesson.

These behaviors should be learned not as "oddities" but as **rules**. Each is written
explicitly in the standard and can be traced step by step. This course's Values and
Types topic reads these steps.

The second consequence of the compatibility constraint is that new abilities mostly
arrive as **additions**: the old behavior is not removed, a new path is opened beside
it. `let` and `const` arriving alongside the `var` declaration is an example of this
pattern; the old one keeps working, the new one offers a different scope rule. The
difference between the two declaration kinds is the next lesson's subject.

#### Feature Detection

The question "does this environment have this ability" is not answered by looking at a
version name. How much of which edition an environment implements is learned by asking
the environment itself. For methods added to built-in objects, the probe is direct:

```js
console.log(typeof Array.prototype.map);
console.log(typeof Array.prototype.at);
console.log(typeof Object.groupBy);
```

In the environment the examples were run in, all three are found:

```
function
function
function
```

The same probe produces `undefined` in an environment that lacks the ability. This is
the basis of writing fallback code:

```js
const temperatures = [21.5, 19.75, 23, 18.25];

function lastElement(array) {
  if (typeof array.at === "function") {
    return array.at(-1);
  }
  return array[array.length - 1];
}

console.log(lastElement(temperatures));
```

```
18.25
```

Doing the probe with `typeof` matters: looking directly at an undeclared name throws a
`ReferenceError`, while `typeof` produces `"undefined"` without error. Object properties
carry no such risk, but a uniform habit prevents the wrong kind of probe.

#### Filling a Missing Ability

Detection reports an absence; it does not fix it. Code that stands in for a missing
ability is called a **polyfill**. A polyfill should be defined only if the ability is
missing, and it must not overwrite an existing implementation.

```js
const builtinExists = typeof Array.prototype.at === "function";
console.log("at built-in:", builtinExists);

if (typeof Array.prototype.last !== "function") {
  Object.defineProperty(Array.prototype, "last", {
    value: function () { return this[this.length - 1]; },
    writable: true, configurable: true, enumerable: false,
  });
}
console.log([3, 7, 9].last());

const keys = [];
for (const k in [1, 2]) keys.push(k);
console.log("for...in keys:", keys);
```

```
at built-in: true
9
for...in keys: [ '0', '1' ]
```

The third line shows why the polyfill is not written as a plain assignment. A property
added to the prototype by plain assignment becomes enumerable and gets caught up in
`for...in` traversal; the definition here gives `enumerable: false`, so the array's own
keys stay unchanged.

Two rules follow. First, if a built-in ability exists, it is left untouched — a polyfill
whose behavior differs from the standard breaks the assumptions of later code. Second,
adding a non-standard name (like `last`) carries a risk of colliding with the standard;
if the same name later enters it, the two definitions come face to face.

#### Why Syntax Cannot Be Detected

A method probe only works for things that **exist at runtime**. New syntax cannot be
tested this way, because the entire file is parsed before it runs. As seen in the first
lesson, a parsing error runs none of the file's lines. In an environment that does not
recognize a piece of syntax, the file containing that syntax is rejected in its
entirety; a check written inside it never gets the chance to run either.

This is why there are two separate tool classes for closing a missing ability:

- A **transpiler** converts new syntax into old syntax. It solves the parsing problem by
  changing the source.
- A **polyfill** defines a missing built-in method at runtime. It cannot change syntax;
  it only adds missing objects and methods.

The two solve different problems and cannot substitute for each other. This course uses
neither; its examples are written with syntax that can be run directly.

#### Reading the Standard Itself

The standard's text is not a teaching book; it is an exact definitional document. Once
its structure is learned, it gives an indisputable answer to the question "why did this
expression produce this result." In the text, every operator and every built-in method
reduces to a call of an **abstract operation** defined by numbered steps. The addition
operator, for example, first calls an abstract operation that converts both operands to
a primitive value; whether the result is a numeric sum or a text concatenation is
decided based on that step's result.

This course's Values and Types topic will follow these steps. The goal is not to
memorize the result, but to be able to follow the steps that produce it. Once followed
once, the result of an expression not yet encountered can be found by the same method.

#### Summary

- The language's definition is the ECMA-262 document; editions are cumulative and
  published features are not removed.
- Proposals go through five stages numbered zero to four; a proposal that reaches stage
  4 enters the next edition.
- Backward compatibility leads even behaviors accepted as wrong to be preserved;
  `typeof null` producing `"object"` is an example of this.
- New abilities are mostly added beside the old one rather than in its place; `let` and
  `const` existing alongside `var` is this pattern.
- A built-in ability's existence is tested with a `typeof` probe; syntax, being
  evaluated at the parsing stage, cannot be tested at runtime.

#### Next Step

The declaration kinds standing side by side was this lesson's closing observation. The
next lesson compares these three declarations: the difference between `var`, `let`, and
`const` is not merely a matter of writing style — it determines which region a name is
seen in, when it becomes accessible, and whether it can be rebound. The measurement
script will be brought into a form where this difference can be observed.

### Variable Declarations

Source: https://academia.sh/en/courses/javascript-fundamentals/variable-declarations

The difference between function-scoped and block-scoped declarations, redeclaration rules, the scope of constant binding, and the binding of the loop variable.

The previous lesson said new abilities are added beside the old one rather than in its
place. The most concrete example of this is the declaration kinds: the language's first
form had a single declaration, ES2015 added two more, and the old one was not removed.
The three do not do the same job.

The Programming Fundamentals course's Scope and Lifetime lesson defined scope as the
region of a program in which a name is visible. This lesson applies that definition to
JavaScript and shows which region each of the three declarations chooses. The difference
is not a matter of writing style; it determines where a name will be seen, when it
becomes accessible, and whether it can be rebound.

#### Two Scope Units

A name declared with `var` is **function-scoped**: it is seen throughout the function it
sits inside. The declaration having been made inside a block changes nothing.

A name declared with `let` or `const` is **block-scoped**: it is seen only inside the
pair of curly braces it sits in, and inside the blocks within that.

```js
function scopeExperiment() {
  if (true) {
    var varVariable = "with var";
    let letVariable = "with let";
    console.log(varVariable, "/", letVariable);
  }
  console.log(varVariable);
  console.log(typeof letVariable);
}
scopeExperiment();
```

```
with var / with let
with var
undefined
```

After the block closes, the name declared with `var` is still accessible; the name
declared with `let` has become invisible. This is why the third line is written with
`typeof`: reading it directly would throw an error.

Seeing the error itself shows how sharp the distinction is:

```js
if (true) {
  let threshold = 20;
  console.log(threshold);
}
try {
  console.log(threshold);
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
20
ReferenceError: threshold is not defined
```

Block scope limits a name's lifetime to the region it is needed in. The Programming
Foundations course's discussion of shadowing and name collision directly applies here:
the narrower a name's region of visibility, the lower the chance of it being overwritten
by accident.

#### Redeclaration

The three declarations react differently to the same name being declared a second time.

`var` allows the same name to be redeclared in the same scope. The second declaration
does not create a new name; it assigns a new value to the existing one:

```js
var threshold = 20;
var threshold = 25;
console.log(threshold);
```

```
25
```

This flexibility turns using the same name a second time, unnoticed, in the middle of a
long function, into a silent bug. `let` and `const` reject redeclaration in the same
scope:

```js
let threshold = 20;
let threshold = 25;
```

When this file is run, not a single line of it executes:

```
SyntaxError: Identifier 'threshold' has already been declared
```

Notice the kind of error. This is not a runtime error, it is a parsing error; the
distinction established in the first lesson is at work here. The same name being
declared twice is a condition that can be detected while the whole source is being
read, so the program never starts at all.

A `const` declaration also requires an initial value:

```js
const threshold;
```

```
SyntaxError: Missing initializer in const declaration
```

The reason for this will be seen in the next section: because a constant name can be
bound only once, an unbound constant could never receive a value.

#### The Limit of a Constant Declaration

`const` fixes the **binding**. The Programming Fundamentals course's Variables and
Binding lesson called the relationship between a name and a value "binding"; `const`
exactly prevents that relationship from changing.

```js
const threshold = 20;
try {
  threshold = 21;
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Assignment to constant variable.
```

What is fixed is the binding, not the value's content. If the bound value is a mutable
object, the object's content can still change:

```js
const record = { station: "A1", temperature: 21.5 };
record.temperature = 22;
console.log(record);
```

```
{ station: 'A1', temperature: 22 }
```

The name is still bound to the same object; what changed is one of the object's
properties. Binding a different object to the same name, on the other hand, is
rejected:

```js
const record = { station: "A1", temperature: 21.5 };
try {
  record = { station: "A2", temperature: 19 };
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Assignment to constant variable.
```

This distinction matches the terminology: `const` establishes a **constant** binding, it
does not make the value **immutable**. Actually freezing an object's content is a
separate operation and is outside this course's scope.

#### Binding the Loop Variable

Where the scope difference produces the most consequences is the loop header. A loop
variable declared with `var` is **a single binding** across the entire loop; a variable
declared with `let` is **rebound on every iteration**.

The difference is visible in functions produced inside the loop:

```js
const varFns = [];
for (var i = 0; i < 3; i++) {
  varFns.push(() => i);
}
console.log(varFns.map((f) => f()));

const letFns = [];
for (let j = 0; j < 3; j++) {
  letFns.push(() => j);
}
console.log(letFns.map((f) => f()));
```

```
[ 3, 3, 3 ]
[ 0, 1, 2 ]
```

In the first loop, all three functions read the same `i` binding. By the time the loop
ends, that binding's value is `3` — the loop terminated because the condition turned
false at this value. When the functions are called later, all of them see `3`.

In the second loop, a separate `j` binding is created for each iteration, and each
function reads its own iteration's binding. This behavior is a rule specific to the
`let` declaration in a loop header: a new binding is created at the start of each
iteration, initialized with the previous iteration's value.

The arrow function syntax (`() => i`) is used here only for brevity; its behavior is
covered in the Arrow Functions lesson. A function keeping itself bound to the names of
the scope it was created in is called a **closure**; it was defined in the Programming
Foundations course and will be detailed in the next course.

`const` can be used when iterating over a collection, because each iteration
establishes a new binding and the variable is not reassigned within the iteration:

```js
const temperatures = [21.5, 19.75, 23];
for (const value of temperatures) {
  console.log(value);
}
```

```
21.5
19.75
23
```

#### Applying It to the Measurement Script

When the course's axis script is rewritten according to these rules, which name is given
which declaration, and why, becomes explainable:

```js
const temperatures = [21.5, 19.75, 23, 18.25];
const threshold = 20;

let total = 0;
let overThreshold = 0;

for (const value of temperatures) {
  total += value;
  if (value > threshold) {
    const difference = value - threshold;
    overThreshold += 1;
    console.log("threshold exceeded, difference:", difference);
  }
}

console.log("average:", total / temperatures.length);
console.log("readings over threshold:", overThreshold);
```

```
threshold exceeded, difference: 1.5
threshold exceeded, difference: 3
average: 20.625
readings over threshold: 2
```

Three decisions were made together. Names that will not be rebound (`temperatures`,
`threshold`, the iteration variable `value`, the in-block `difference`) were declared
with `const`. Only the two names acting as accumulators (`total`, `overThreshold`) got
`let`. The name `difference` was declared only where it is needed, inside the `if`
block; thanks to block scope, it is invisible in the rest of the loop.

This writing convention will be kept throughout the course: **every name that will not
be rebound is declared with `const`, every name that will be rebound with `let`.** `var`
will be used only when its behavior is being demonstrated. The reasoning is not
aesthetic — once whether a name gets rebound can be read from its declaration, tracing
the code does not require scanning the entire body.

#### Summary

- `var` is function-scoped; `let` and `const` are block-scoped and are not visible
  outside the pair of curly braces.
- `var` can be redeclared in the same scope; `let` and `const` reject redeclaration with
  a `SyntaxError` at the parsing stage.
- `const` fixes the binding, it does not make the value immutable: an object's
  properties can change, but a different object cannot be bound to the name.
- A loop variable declared with `var` is a single binding; `let` establishes a new
  binding on every iteration, and functions produced inside the loop show this
  difference.
- The course's convention: a name that will not be rebound gets `const`, a name acting
  as an accumulator gets `let`.

#### Next Step

Half of the scope question has been answered: where a name is seen. The other half is
when it becomes accessible. Touching the same name on the line before its declaration
gives `undefined` with `var` and throws an error with `let`; both are explained by the
fact that the declaration is "hoisted." The next lesson covers lexical scope, hoisting,
and the rules of the region before a declaration.

### Naming and Scope Rules

Source: https://academia.sh/en/courses/javascript-fundamentals/naming-and-scope-rules

Naming rules and reserved words, the resolution of lexical scope, shadowing, hoisting, and the temporal dead zone.

The previous lesson determined where a name is seen. This lesson answers two more
questions: if the same name is declared in more than one scope, which one applies, and
what happens if a name is read before its declaration line?

Both answers come from a single principle: JavaScript uses **lexical scope**. Which
binding a name corresponds to is determined by looking at where the code is written; it
does not matter who calls whom at runtime. This principle, defined in the Programming
Foundations course's Scope and Lifetime lesson, is the source of every behavior below.

#### Naming Rules

A name starts with a letter, an underscore, or a dollar sign; digits may follow after
that. Names are case-sensitive: `threshold` and `Threshold` are two different names.

```js
const measurementCount = 4;
const MEASUREMENT_THRESHOLD = 20;
const $name = "A1";
const _temp = true;
console.log(measurementCount, MEASUREMENT_THRESHOLD, $name, _temp);
console.log("threshold" === "Threshold");
```

```
4 20 A1 true
false
```

The grammar also accepts letters outside the Latin alphabet, but every name in this
course is written with ASCII characters. The reason is practical: characters that look
visually similar but correspond to different code points produce errors that are hard
to tell apart. The code point concept defined in the How Computers Work course's
Character Encodings lesson applies directly here: two names are the same only if their
sequences of code points are the same.

Words reserved by the language cannot be used as names:

```js
const class = 1;
```

```
SyntaxError: Unexpected token 'class'
```

This too is a parsing error; the file does not run at all. The list of reserved words
is written in the standard and includes language constructs like `class`, `const`,
`return`, `typeof`, and `new`.

As a naming convention, this course writes variable and function names starting with a
lowercase letter and continuing with capitalized words (`measurementCount`); threshold
and setting values that will not be recalculated are written in uppercase
(`MEASUREMENT_THRESHOLD`). The convention is not the language's rule; it is chosen for
consistency.

#### Lexical Scope

When a name is read, the scope it is currently in is checked first. If the name is not
there, the **enclosing scope** is checked, then that scope's surroundings; if it is not
found even in the outermost scope, a `ReferenceError` is thrown. This chain is derived
from the code's written structure.

```js
const threshold = 20;

function outerScope() {
  const threshold = 25;
  function innerScope() {
    return threshold;
  }
  return innerScope();
}

function caller() {
  const threshold = 30;
  return outerScope();
}

console.log(outerScope());
console.log(caller());
console.log(threshold);
```

```
25
25
20
```

The second line is decisive. Inside the `caller` function, the name `threshold` is
bound to `30`, and `outerScope` is called from there. The result is still `25`:
`innerScope` looks for the name not in the caller's scope but in the enclosing scope of
**where it is itself written**. The call chain does not change the scope chain.

The alternative model — looking for a name in the caller's scope — is called dynamic
scoping and is found in some shell languages. JavaScript does not use this model. The
distinction has a practical consequence: which names a function can see can be
determined by whoever reads it, purely by looking at the source.

#### Shadowing

Declaring the same name in an inner scope as in an outer one is called **shadowing**. In
the example above, the `threshold` inside `outerScope` shadows the outermost
`threshold` name. The outer binding does not disappear; it only becomes unreachable in
that region — the last line printing `20` shows this.

Shadowing is useful when used deliberately: carrying a different value of the same
concept in a narrow region is more readable than inventing a new name. When done by
accident, it becomes hard to explain why the outer value was not used. The rule is
simple: if shadowing is happening, it should be deliberate.

#### Hoisting

Declarations are processed at the start of the scope, not on the line where they are
written in the source. This behavior is called **hoisting**. The three declaration
kinds are not hoisted the same way, and this is exactly where the difference shows up.

A name declared with `var` is created at the start of the scope and initialized with
`undefined`. It can be read up until the declaration line; when read, it gives
`undefined`:

```js
console.log(typeof varName);
var varName = 20;
console.log(varName);
```

```
undefined
20
```

A name declared with `let` or `const` is also created at the start of the scope, but it
is **not initialized**. Touching the name in the region between its creation and its
declaration line throws an error:

```js
try {
  console.log(letName);
} catch (error) {
  console.log(error.name + ": " + error.message);
}
let letName = 25;
console.log(letName);
```

```
ReferenceError: Cannot access 'letName' before initialization
25
```

This region is called the **temporal dead zone**. The message needs to be read
carefully: it does not say "name not found," it says "cannot access before
initialization." The name exists in the scope; it just has not been bound to a value
yet. Compared with the previous lesson's `esik is not defined` message — that name had
never been declared at all, this one has been declared but not initialized.

The dead zone, combined with shadowing, produces a result that looks counterintuitive:

```js
const threshold = 20;
function buggy() {
  try {
    console.log(threshold);
    const threshold = 25;
  } catch (error) {
    console.log(error.name + ": " + error.message);
  }
}
buggy();
```

```
ReferenceError: Cannot access 'threshold' before initialization
```

The outer `threshold` name, even though it is bound to `20`, could not be read. The
reason is the definition of lexical scope: because `threshold` is declared inside the
function body, every reference to `threshold` in that body points to the **local**
binding. The local binding has not been initialized yet. There is no way to reach the
outer binding; shadowing also applies before the declaration line.

#### Function Declarations

Function declarations are hoisted together with their bodies, not just their names.
This is why they can be called before their declaration line:

```js
console.log(average([21.5, 19.75, 23, 18.25]));

function average(array) {
  let total = 0;
  for (const value of array) total += value;
  return total / array.length;
}
```

```
20.625
```

If a function is assigned to a variable as an expression, what gets hoisted is only the
variable declaration; the function itself comes into being on the assignment line. If it
is assigned with `const`, the dead-zone rule applies:

```js
try {
  console.log(average([1, 2]));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
const average = (array) => array.reduce((total, value) => total + value, 0) / array.length;
console.log(average([1, 2]));
```

```
ReferenceError: Cannot access 'average' before initialization
1.5
```

The behavioral difference between the two definition forms will be covered in detail in
the Function Declaration and Expression lesson; here they were compared only from the
hoisting angle.

#### Applying It to the Measurement Script

Scope rules directly determine the measurement script's structure:

```js
const THRESHOLD = 20;

function thresholdReport(temperatures) {
  const exceedances = [];
  for (const value of temperatures) {
    if (value > THRESHOLD) {
      const difference = value - THRESHOLD;
      exceedances.push(difference);
    }
  }
  return exceedances;
}

console.log(thresholdReport([21.5, 19.75, 23, 18.25]));
console.log(typeof exceedances);
```

```
[ 1.5, 3 ]
undefined
```

`THRESHOLD` is in the outermost scope and is seen from inside the function through the
lexical scope chain. `exceedances` belongs to the function, `difference` belongs to the
`if` block; neither is accessible from outside. Writing the last line with `typeof` was
required — reading it directly would have thrown a `ReferenceError`.

The structure applies three rules together: every name is declared in the narrowest
scope it is needed in, no shadowing is done, declarations come before use. The third is
not required, thanks to hoisting, but it eliminates every dead-zone error entirely.

#### Summary

- Names are case-sensitive; reserved words cannot be used as names, and this is caught
  at the parsing stage.
- JavaScript uses lexical scope: a name is searched for in the enclosing scopes of where
  the code is written; the call chain does not affect this search.
- Declaring the same name in an inner scope shadows the outer binding; shadowing also
  applies before the declaration line.
- `var` is initialized with `undefined` at the start of the scope; `let` and `const` are
  not initialized and stay in the temporal dead zone until their declaration line.
- Function declarations are hoisted together with their bodies; function expressions
  assigned to a variable are not.

#### Next Step

The structure of the language — how code is run, how names are declared and resolved —
has been established. Next comes the values these names are bound to. The next topic
covers JavaScript's value model: how many primitive types there are, which
representation the number type uses, and why it carries a limit for integers. Its first
lesson introduces the primitive types one by one.

## Values and Types

Primitive types, arbitrary-precision integers, the type coercion algorithm, the three equality comparisons, and truthy and falsy values.

### Primitive Types

Source: https://academia.sh/en/courses/javascript-fundamentals/primitive-types

The seven primitive types and the distinction from objects; the single number type's floating-point representation, special number values, string, boolean, the two absence values, and symbol.

The previous topic established how names are declared and resolved. This topic takes up
the values names are bound to. The Basic Data Types lesson in the Programming Fundamentals
course defined a type as a set of values together with the operations defined on those
values.
This lesson applies that definition to JavaScript's value model.

The model is small: seven primitive types and one object class. The price of that
smallness is the weight each type carries in its decisions. For example, there is a
single number type and no separate type for integers; this one decision sets the topic
for several lessons ahead.

#### Seven Primitive Types and Objects

A value is either **primitive** or an **object**. Primitive values cannot have their
content changed and do not carry properties on their own. Objects are sets of properties,
and their content can be changed.

The `typeof` operator gives a value's type as text:

```js
console.log(typeof 21.5);
console.log(typeof "A1");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof null);
console.log(typeof 9007199254740993n);
console.log(typeof Symbol("measurement"));
console.log(typeof { station: "A1" });
console.log(typeof [1, 2]);
console.log(typeof function () {});
```

```
number
string
boolean
undefined
object
bigint
symbol
object
object
function
```

The output does not correspond to the type list one to one at two points. In the fifth
line, `null` is a primitive value, but the result is `object`; this is the trace of the
backward compatibility discussed in the Language Versions and Standardization lesson. In
the last line, `function` is not a separate type; functions are callable objects, and
`typeof` reports this with a distinct value.

The seven primitive types are: number, BigInt, string, boolean, `undefined`, `null`, and
symbol. This lesson's subject is six of these; BigInt is the next lesson's subject.

#### Number: A Single Type

JavaScript has no separate types for integers and real numbers. The whole of the `number`
type is the IEEE 754 double-precision representation defined in the Floating-Point
Numbers lesson of the How Computers Work course: 64 bits, 1 sign bit, 11-bit biased
exponent, 52-bit stored mantissa.

This produces two direct consequences.

First, writing an integer literal does not produce a separate type:

```js
console.log(Number.isInteger(23));
console.log(23 === 23.0);
```

```
true
true
```

`23` and `23.0` are the same value; both correspond to the same bit pattern. This is why
the first lesson's `Math.max` result was written as `23` — a floating-point number whose
fractional part is zero is written without showing the fraction when converted to text.

Second, decimal values that cannot be written as a finite binary fraction are held
approximately:

```js
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
```

```
0.30000000000000004
false
```

This result is not a surprise, it is a direct consequence of the representation. The
model from the Floating-Point Numbers lesson applies here exactly: the values $0.1$ and
$0.2$ cannot be written as a finite binary fraction, each is rounded to the nearest
representable value, and the addition is performed on these rounded values. The result
falls not on $0.3$'s representation, but on another value closest to it.

The practical rule established in the same lesson applies here too: floating-point values
are compared not with equality but with tolerance. Quantities requiring precision, like
money, are kept as integers carrying the smallest unit.

The safe range of integers also comes from the mantissa's width. Because the mantissa
provides 53 bits of precision, integers whose absolute value exceeds $2^{53}-1$ become
indistinguishable from one another:

```js
console.log(Number.MAX_SAFE_INTEGER);
console.log(9007199254740992 === 9007199254740993);
```

```
9007199254740991
true
```

Two different integers coming out equal is because both round to the same representable
value. Cases where this boundary is crossed are the next lesson's subject.

#### Special Number Values

The representation carries three special values, and all three are directly visible in
the language:

```js
console.log(1 / 0, -1 / 0, 0 / 0);
console.log(Object.is(0, -0), 1 / -0);
```

```
Infinity -Infinity NaN
false -Infinity
```

Division by zero does not throw an error; it produces an infinity value. Undefined
operations give `NaN` (not a number). `NaN` **is** a number value and its type is
`number`; its name says that what it represents is not a number.

Signed zero is also part of the representation: `0` and `-0` are different bit patterns
but are considered equal in equality comparisons. `Object.is` is the operation that sees
the distinction. The `-0` value is invisible in most computations; it only becomes
significant because it determines the sign in division by zero.

#### String

The string type carries text. Strings are written with single quotes, double quotes, or
backticks; there is no behavioral difference between the first two. The backtick form has
separate capabilities and will be covered in the Strings lesson.

```js
const name = "measurement";
console.log(name.length);
console.log(name[0]);
console.log(name.toUpperCase());
console.log("A1".codePointAt(0));
```

```
11
m
MEASUREMENT
65
```

String length is not the character count, it is the count of **16-bit code units**. The
surrogate pair concept defined in the Character Encodings lesson of the How Computers
Work course becomes visible here: a code point outside the basic multilingual plane is
represented by two code units, and `length` counts it as two.

```js
console.log("😀".length);
```

```
2
```

This is the key to understanding the string type: a string is not a sequence of code
points, it is a sequence of code units. The distinction changes the result in character
counting and slicing operations.

#### Boolean

The boolean type has two values: `true` and `false`. They are produced by comparisons and
are the input of conditional constructs. JavaScript also considers non-boolean values
usable in a condition; which value is considered true and which is considered false is
the subject of the Truthy and Falsy Values lesson.

#### Two Absence Values

Most languages have a single absence value. JavaScript has two, and their meanings are
separate.

`undefined` is **a value never having been given at all**. A declared but unassigned
variable, a nonexistent object property, and a parameter with no corresponding argument
carry this value. The language produces it on its own.

`null` is **a deliberately placed absence**. The language does not produce it on its own;
the program's author assigns it to say "there is no value here."

```js
const record = { station: "A1", temperature: 21.5 };
console.log(record.humidity);
console.log("humidity" in record);

let nextReading;
console.log(nextReading);

const calibration = null;
console.log(calibration);
```

```
undefined
false
undefined
null
```

The distinction is directly useful in measurement data. If a record's humidity field
**does not exist at all**, reading it gives `undefined`; if the field exists but the
sensor produced no value, writing `null` preserves the information "not measured." The
two are not the same thing: the first concerns the data's shape, the second concerns the
data's content.

#### Symbol

Symbol is the primitive type that produces a unique value on every call. Two symbols
produced with the same description text are not equal to each other:

```js
console.log(Symbol("source") === Symbol("source"));
```

```
false
```

Its use case is adding properties to objects with no risk of collision. Symbol-keyed
properties are invisible in ordinary key listings and in serialization:

```js
const source = Symbol("source");
const record = { station: "A1", temperature: 21.5, [source]: "local file" };
console.log(Object.keys(record));
console.log(JSON.stringify(record));
console.log(record[source]);
```

```
[ 'station', 'temperature' ]
{"station":"A1","temperature":21.5}
local file
```

The provenance information added to the measurement record was carried without polluting
the record's data content. Symbols' second role in the language — adapting objects'
specific behaviors — is the subject of the next course.

#### Primitive Values Are Immutable

A primitive value's content cannot be changed. Attempting an indexed assignment on a
string throws an error in strict mode:

```js
const name = "A1";
try {
  name[0] = "B";
} catch (error) {
  console.log(error.name + ": " + error.message);
}
console.log(name);
```

```
TypeError: Cannot assign to read only property '0' of string 'A1'
A1
```

The same operation throws no error in a script not in strict mode; it is silently
ignored, and the string still does not change. This is the second example of the
strict-mode difference discussed in the Ways to Run Code lesson: the behavior is the
same, the feedback is different.

This is why string methods never modify the original value, they always produce a new
string. The immutable–mutable distinction from the Programming Fundamentals course is
strict here by type: all seven primitive types are immutable, objects are mutable.

#### The Measurement Record's Types

The data that is this course's axis is a combination of these types:

```js
const record = {
  station: "A1",
  temperature: 21.5,
  humidity: null,
  verified: false,
};

for (const key of Object.keys(record)) {
  console.log(key, "->", typeof record[key]);
}
console.log("missing field:", typeof record.pressure);
```

```
station -> string
temperature -> number
humidity -> object
verified -> boolean
missing field: undefined
```

The third line is a reminder: because the `humidity` field carries `null`, the `typeof`
result comes out as `object`. Whether a field is truly empty is tested not with `typeof`
but with a direct comparison. Which rule these comparisons follow is the subject of the
Equality Comparisons lesson.

#### Summary

- Values are either primitive or objects; there are seven primitive types and all of them
  are immutable.
- `typeof null` gives `object`, and functions give `function`; neither corresponds to the
  type list one to one.
- The single number type is the IEEE 754 double-precision representation: `23` and `23.0`
  are the same value, decimal fractions are approximate, and the safe integer range is
  bounded by $2^{53}-1$.
- `Infinity`, `-Infinity`, `NaN`, and `-0` are part of the representation; `NaN`'s type is
  `number`.
- String length is the code unit count, not the character count.
- `undefined` denotes a value never given, `null` a deliberately placed absence.

#### Next Step

Two different integers above the safe integer range coming out equal is the one point
this lesson leaves unresolved. The next lesson takes up the cases where this boundary is
actually crossed and the language's answer to it: a separate primitive type carrying an
integer of arbitrary size, its behavior in operators, and the error that arises when it is
mixed with the number type.

### BigInt

Source: https://academia.sh/en/courses/javascript-fundamentals/bigint

Cases where the safe integer range is crossed, the arbitrary-precision integer type, operator behavior, the ban on mixing types, and the serialization limit.

The previous lesson showed that the whole of the number type is the IEEE 754
double-precision representation, and that this representation carries a boundary at
$2^{53}-1$ for integers. Above the boundary, two different integers round to the same bit
pattern and come out equal.

This lesson takes up the cases where that boundary is actually crossed and the language's
answer to it: a separate primitive type carrying an integer of arbitrary size. The type
itself matters, and so does the strict boundary placed between it and the number type.

#### Where the Boundary Is Crossed

The safe range is about nine quadrillion. This value, which looks distant in everyday
calculations, is crossed with a single reading in certain kinds of data.

- **Nanosecond-resolution timestamps.** The number of nanoseconds elapsed since a start
  point is a million times larger than the same value in milliseconds, and it exits the
  range.
- **64-bit integer identifiers coming from other systems.** Even if no arithmetic is done
  on the identifier, it has to be read back and written out without losing value.
- **Counters accumulating over a long period.** In a high-sample-rate measurement
  network, the total sample count can reach this range.

What all three share is that precision has to be preserved down to the last digit. When
working with the number type, the loss is silent; no error is thrown, only the wrong
value is carried.

#### A Separate Primitive Type

BigInt is the primitive type that carries an integer of arbitrary digit count. It is
written in two forms: by appending `n` to the end of a number literal, or through a
conversion call.

```js
const a = 9007199254740993n;
const b = BigInt("9007199254740993");
console.log(a === b);
console.log(typeof a);
console.log(a + 1n);
```

```
true
bigint
9007199254740994n
```

In the previous lesson, this value could not be represented with the number type; here
it is preserved exactly. The `n` suffix in the output is not part of the value, it is the
runtime's inspection format. The suffix disappears when the value is converted to text:

```js
console.log(String(10n));
console.log((10n).toString());
console.log(10n);
```

```
10
10
10n
```

The first two lines are the result of the language's text-conversion rule, and are the
same in every environment. The display on the third line is the environment's inspection
format. The distinction established in the Ways to Run Code lesson shows up again here.

#### Operator Behavior

Arithmetic operators work with integer semantics. The most noticeable difference is in
division:

```js
console.log(7n / 2n);
console.log(7n % 2n);
console.log(2n ** 64n);
```

```
3n
1n
18446744073709551616n
```

Division produces no fraction; the result is truncated toward zero. The distinction
between the two division operations established in the Basic Data Types lesson of the
Programming Fundamentals course applies here — with the BigInt type there is only integer
division. The truncation direction is toward zero, not downward; with negative operands,
this distinction changes the result.

The exponentiation operator gives exactly a magnitude the number type could not
represent. $2^{64}$ has twenty digits and is correct down to the last digit.

#### Types Cannot Be Mixed

The two number types cannot come together in arithmetic operators:

```js
try {
  console.log(1n + 1);
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Cannot mix BigInt and other types, use explicit conversions
```

This ban is not a shortcoming, it is a deliberate design decision. The two types'
semantics do not agree: division produces a fraction in the number type, it does not in
BigInt; large values round in the number type, they do not in BigInt. Which rule a mixed
expression should be evaluated under cannot be chosen in a single consistent way. The
language leaves the choice to the program's author and demands an explicit conversion.

Comparison operators behave differently. Operators that compare value can take the two
types together; the operator that also compares type keeps them apart:

```js
console.log(1n == 1);
console.log(1n === 1);
console.log(2n > 1);
```

```
true
false
true
```

The first and third lines compare the mathematical value. The second line gives false
because it also compares the types. These three operators' rules will be defined step by
step in the Equality Comparisons lesson.

#### The Cost of Conversion

Conversion between the two types can be written explicitly, but not every direction is
safe.

Converting BigInt to number loses precision for values outside the range:

```js
const timeNs = 1700000000123456789n;
console.log(Number(timeNs));
console.log(BigInt(Number(timeNs)));
```

```
1700000000123456800
1700000000123456768n
```

The two lines have to be read together. The first line is the text form of the number
that comes out of the conversion; text conversion chooses the shortest decimal
representation that rounds back to the same bit pattern, which is why it looks round. The
second line writes the same value with a lossless type and shows the actual value:
`...768`. So the original value has been lost, and the amount of the loss is also
different from what it looks like at first glance.

Converting number to BigInt rejects fractional values:

```js
try {
  console.log(BigInt(1.5));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
RangeError: The number 1.5 cannot be converted to a BigInt because it is not an integer
```

Built-in methods that work with the number type also do not accept BigInt:

```js
try {
  console.log(Math.max(1n, 2n));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Cannot convert a BigInt value to a number
```

The entire `Math` object is defined for floating-point numbers. If a comparison with
BigInt values is needed, comparison operators are used.

#### Application to the Measurement Script

When a nanosecond-resolution timestamp is added to measurement records, the two types
stand side by side in the same record:

```js
const readings = [
  { station: "A1", timeNs: 1700000000123456789n, temperature: 21.5 },
  { station: "A1", timeNs: 1700000000923456789n, temperature: 21.7 },
];

const diff = readings[1].timeNs - readings[0].timeNs;
console.log("diff (ns):", diff.toString());
console.log("diff (ms):", (diff / 1000000n).toString());
```

```
diff (ns): 800000000
diff (ms): 800
```

The time difference was computed at nanosecond precision, then converted to
milliseconds. The fraction was dropped in the conversion — integer division truncates.
If a fractional result is needed, the division is done after switching to the number
type, and at that point the loss of precision is knowingly accepted.

The temperature field stayed in the number type; that is the right choice for a
floating-point magnitude. The two types being present in the same record is not a
problem — the problem is mixing the two in the same expression.

#### The Serialization Limit

BigInt values prevent data from being written in JSON format:

```js
try {
  console.log(JSON.stringify({ counter: 10n }));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
console.log(JSON.stringify({ counter: String(10n) }));
```

```
TypeError: Do not know how to serialize a BigInt
{"counter":"10"}
```

The reason is not a shortcoming, it is the format's definition: JSON's number
representation carries no precision guarantee, and the reading side may convert the value
to the number type. Writing it as text to carry the value losslessly is the solution that
accepts the format's boundary. JSON's boundaries are the subject of a separate lesson.

#### Summary

- The safe integer range is crossed in nanosecond timestamps, 64-bit external
  identifiers, and counters accumulating over a long period; the loss is silent.
- BigInt is a separate primitive type; it is written with the `n` suffix or through
  explicit conversion, and it carries an arbitrary number of digits.
- Division produces no fraction, it truncates toward zero; `Math` methods do not work
  with this type.
- The two number types cannot be mixed in arithmetic operators; comparison operators work
  together for value comparison, the operator that also compares type keeps them apart.
- Converting BigInt to number loses precision outside the range; it cannot be written
  directly to JSON, it is carried as text.

#### Next Step

This lesson showed the one case that requires an explicit conversion between the two
types to be written. In the rest of the language, the situation is the reverse: types are
converted on their own in most operators, and the result depends on the order in which
the conversion is done. The next lesson will define the steps of this implicit coercion;
the result of expressions like `[] + {}` will be read not as something to memorize, but
as the output of a traceable procedure.

### Type Conversion and Coercion

Source: https://academia.sh/en/courses/javascript-fundamentals/type-conversion-and-coercion

The distinction between explicit conversion and implicit coercion; the procedures for converting to number, to string, and to primitive value; the two faces of the addition operator, and reading measurement values from text.

The previous lesson showed that mixing the two number types in arithmetic operators is
rejected. This is not the language's general rule; it is the exception. In the rest of
the language, types are converted on their own, and the result depends on the order in
which the conversion is done.

The Type Conversion lesson in the Programming Fundamentals course established the
distinction between explicit and implicit conversion. This lesson applies that
distinction to JavaScript and defines the steps of implicit conversion — the behavior
that will be called **coercion** in this course. The goal is not to memorize results, but
to be able to trace the procedure that produces the result.

#### Three Target Types

Coercion is always toward one of three targets: number, string, or boolean. The standard
defines three abstract operations for these. This lesson covers the first two; converting
to boolean is the subject of the Truthy and Falsy Values lesson.

The to-number conversion procedure gives a fixed rule for each primitive type:

```js
console.log(Number("21.5"));
console.log(Number(""));
console.log(Number("   "));
console.log(Number("21.5 C"));
console.log(Number(null));
console.log(Number(undefined));
console.log(Number(true), Number(false));
```

```
21.5
0
0
NaN
0
NaN
1 0
```

Four lines need attention. An empty string and a string containing only whitespace give
zero — the text "containing no number" results in being counted as zero. Text that starts
with a number and continues with a letter gives `NaN`; the procedure requires the
**entire** text to be a valid number literal. `null` converts to zero, `undefined`
converts to `NaN`; the distinction between the two absence values continues here too.

The to-string conversion procedure is also defined per type:

```js
console.log(String(21.5));
console.log(String(null), String(undefined), String(true));
console.log(String([1, 2, 3]));
console.log(String([]));
console.log(String([null, undefined, 3]));
console.log(String({ a: 1 }));
```

```
21.5
null undefined true
1,2,3

,,3
[object Object]
```

Arrays join their elements with commas; an empty array gives an empty string. `null` and
`undefined` values inside an array convert to an empty string — this is why the fourth
line has two leading commas. Ordinary objects do not show their contents; they give the
fixed text `[object Object]`.

#### From Object to Primitive Value

The array and object results above go through an intermediate step. When an object
enters a place expecting a number or string, it is first **converted to a primitive
value**. This conversion tries two of the object's methods in a specific order: in a
numeric context the value-producing method is called first, in a text context the
text-producing method is called first.

The behavior can be observed directly:

```js
const obj = {
  valueOf() { return 42; },
  toString() { return "forty two"; },
};
console.log(obj * 2);
console.log(String(obj));
console.log(obj + 1);
```

```
84
forty two
43
```

Multiplication is a numeric context: the value-producing method was called. Converting to
string is a text context: the text-producing method was called. The third line is the
addition operator's special case; it is the subject of the next section.

In an ordinary object, the value-producing method returns the object itself — that is, it
does not give a primitive value. So the turn passes to the text-producing method, and
`[object Object]` comes out. Arrays give `1,2,3` because they define the text-producing
method to join their elements.

#### The Two Faces of the Addition Operator

The addition operator is set apart from the other arithmetic operators. Its steps are
these: both operands are converted to a primitive value; **if either one is a string**,
the result is text concatenation, otherwise both are converted to number and added.

```js
console.log(1 + 2);
console.log("1" + 2);
console.log(1 + 2 + "3");
console.log("1" + 2 + 3);
```

```
3
12
33
123
```

The last two lines show that the operator associates left to right. In the third line,
`1 + 2` is computed numerically first, then the result `3` is converted to string and
concatenated. In the fourth line, the first operation is already concatenation; because
the result is a string, the second operation becomes concatenation too. The order of
operations determines the type of the result.

The other arithmetic operators do not have this duality; they always convert to number:

```js
console.log("21.5" - 1);
console.log("21.5" * 2);
console.log(1 + null);
console.log(1 + undefined);
console.log(true + true);
```

```
20.5
43
1
NaN
2
```

The third and fourth lines are a direct consequence of the to-number conversion rules:
`null` gives zero, `undefined` gives `NaN`, and any arithmetic operation involving `NaN`
produces `NaN`.

The commonly quoted expressions also come out of the same two steps:

```js
console.log([] + {});
console.log([] + []);
```

```
[object Object]

```

First line: an empty array converts to an empty string when converted to primitive, the
object converts to `[object Object]`; because one is a string, concatenation happens.
Second line: two empty strings are concatenated and an empty line is printed. There is no
mystery in the middle — there is only a two-step procedure.

#### Explicit Conversion Tools

Writing the conversion instead of relying on coercion makes code easier to read. Four
tools need to be distinguished:

```js
console.log(parseInt("21.5 C"), parseFloat("21.5 C"));
console.log(parseInt("  42px"), Number("  42px"));
console.log(parseInt(""), Number(""));
console.log(parseInt("1f", 16));
console.log(+"21.5", +"", +"abc");
```

```
21 21.5
42 NaN
NaN 0
31
21.5 0 NaN
```

`Number` requires the entire text to be valid; if it is not, it gives `NaN`, and it
counts an empty string as zero. `parseInt` and `parseFloat`, on the other hand, **start
from the beginning and read as far as they can**; they ignore the rest, and give `NaN`
if they cannot read anything at all. `parseInt` also takes a base parameter; the
notations from the Hexadecimal and Octal Bases lesson in the How Computers Work course
are read with this parameter.

The unary plus operator applies the same rule as `Number`; because its brevity comes at
the expense of readability, it will not be used in this course.

The choice between the two tools depends on the data. If a field "must contain only a
number," `Number` is the right choice: it reports corrupted data with `NaN`. If a field
is "text starting with a number," `parseFloat` is the right choice. The wrong choice lets
corrupted data pass through silently.

#### Application to the Measurement Script

Measurement records mostly arrive as text. The script below reads semicolon-separated
lines and converts the numeric fields:

```js
const lines = ["A1;21.5;48", "A2;19.75;55", "B1;;41", "B2;abc;60"];

for (const line of lines) {
  const [station, temperatureText, humidityText] = line.split(";");
  const temperature = Number(temperatureText);
  const humidity = Number(humidityText);
  console.log(station, temperature, humidity, Number.isNaN(temperature));
}
```

```
A1 21.5 48 false
A2 19.75 55 false
B1 0 41 false
B2 NaN 60 true
```

The last two lines show two separate kinds of data corruption, and only one of them is
caught.

In the `B2` record, the temperature field says `abc`; `Number` turned this into `NaN`,
and the `Number.isNaN` check reported the state. In the `B1` record, though, the
temperature field is **empty**, and `Number` converted the empty string to zero. Zero is
a valid temperature value; the check assumed this record was sound. A missing measurement
silently turned into zero degrees.

The correct behavior is to test whether the field is empty before conversion:

```js
function readTemperature(text) {
  if (text.trim() === "") {
    return null;
  }
  const value = Number(text);
  return Number.isNaN(value) ? null : value;
}

for (const field of ["21.5", "", "   ", "abc", "0"]) {
  console.log(JSON.stringify(field), "->", readTemperature(field));
}
```

```
"21.5" -> 21.5
"" -> null
"   " -> null
"abc" -> null
"0" -> 0
```

The empty field and the corrupted field are now caught separately, and a valid zero
measurement is preserved. The last line matters for this reason: zero is not missing
data.

An attempt to write the same job more briefly breaks at exactly this point:

```js
function readTemperatureShort(text) {
  const value = Number(text);
  return value ? value : null;
}

for (const field of ["21.5", "", "abc", "0"]) {
  console.log(JSON.stringify(field), "->", readTemperatureShort(field));
}
```

```
"21.5" -> 21.5
"" -> null
"abc" -> null
"0" -> null
```

Three lines are correct, the last one is wrong: a valid zero measurement turned into
missing data. The cause is not the conversion, it is how the condition evaluates the
value `0`. The rule for this behavior will be defined in the Truthy and Falsy Values
lesson.

Choosing `null` for a missing value is deliberate: per the distinction in the Primitive
Types lesson, `null` carries the information "not measured"; `undefined` would say the
field does not exist at all.

#### Summary

- Coercion is toward three targets: number, string, and boolean. There is a
  step-by-step-defined procedure in the standard for each target.
- In converting to number, an empty string gives zero, invalid text gives `NaN`, `null`
  gives zero, `undefined` gives `NaN`.
- Objects are first converted to a primitive value; the value-producing method takes
  priority in a numeric context, the text-producing method in a text context.
- The addition operator performs concatenation if, after one of the operands is
  converted to a primitive value, it is a string; the other arithmetic operators always
  convert to number.
- `Number` requires the entire text, `parseInt`/`parseFloat` read as far as they can;
  they are chosen based on the field's meaning.
- Converting an empty string to zero turns a missing measurement into a valid value;
  a blank check is required before conversion.

#### Next Step

This lesson used comparisons like `temperature === null` without defining their rules.
The next lesson takes up the three comparison operators step by step: strict equality,
which compares type; loose equality, which applies coercion; and same-value equality,
which separates the `NaN` and `-0` cases. Loose equality's steps are a direct continuation
of this lesson's coercion procedures.

### Equality Comparisons

Source: https://academia.sh/en/courses/javascript-fundamentals/equality-comparisons

Strict equality, the step-by-step algorithm of loose equality, same-value equality, and identity comparison for objects.

The previous lesson defined the coercion procedures: how a value is converted to number,
to string, and to a primitive value. This lesson takes up the place those procedures are
used the most. JavaScript has three separate ways to compare two values, and the three
answer different questions.

All three diverge on a single axis: whether coercion is applied before comparison, and
how the special number values (`NaN`, `-0`) are handled. Knowing the distinction turns
the question "why did this come out equal" from a guess into a traceable step.

#### Strict Equality

The `===` operator **looks at type first**. If the types differ, the result is directly
false; no coercion is done. If the types are the same, the values are compared.

```js
console.log(21.5 === 21.5);
console.log("A1" === "A1");
console.log(1 === "1");
console.log(NaN === NaN);
console.log(0 === -0);
```

```
true
true
false
false
true
```

The third line gives false because of the type difference. The last two lines are two
exceptions specific to the number type, and both come from the IEEE 754 representation.
`NaN` is not equal to any value, including itself; the standard writes this explicitly.
Signed zeros, despite being different bit patterns, are considered equal.

`NaN` not being equal to itself makes it impossible to test whether a value is `NaN`
using equality. There is a separate probe for this:

```js
console.log(Number.isNaN(NaN));
console.log(Number.isNaN("abc"));
console.log(isNaN("abc"));
```

```
true
false
true
```

The difference between the two probes matters. `Number.isNaN` requires the value to
**be of number type and be `NaN`**. The general probe first converts its argument to a
number; because the text `"abc"` converts to `NaN`, it gives true. The second answers the
question "can this value not be converted to a number," the first answers "is this value
`NaN`." Only the first will be used in this course.

#### Loose Equality

The `==` operator, if the types differ, applies coercion and descends to a common type.
Its steps are defined in sequence in the standard, and in practice the following five
rules are enough:

1. If the types are the same, strict equality is applied.
2. If one side is `null` and the other is `undefined`, the result is true. Neither of
   these is loosely equal to any other value.
3. If one side is a number and the other is a string, the string is converted to a
   number.
4. If one side is a boolean, **that side is converted to a number first**, then the rules
   are applied again.
5. If one side is an object and the other is primitive, the object is converted to a
   primitive value and the rules are applied again.

Results of the rules:

```js
console.log(null == undefined);
console.log(null == 0, undefined == 0);
console.log("1" == 1);
console.log("" == 0);
console.log("0" == 0);
console.log("" == "0");
```

```
true
false false
true
true
true
false
```

The second line is a direct consequence of the second rule: `null` is not converted to a
number; it only matches `undefined`. This is the one behavior that makes it safe to write
a `null` check with loose equality.

The boolean rule produces the most counterintuitive results:

```js
console.log(true == 1, true == "1", false == "");
console.log([] == false);
console.log([21.5] == 21.5);
console.log([1, 2] == "1,2");
```

```
true true true
true
true
true
```

Reading the second line step by step shows the method. In the expression `[] == false`,
the fourth rule applies first: `false` is converted to a number, becoming `0`. Now one
side is an object, the other a number; per the fifth rule the array is converted to a
primitive value and becomes an empty string. In the last step, the third rule converts
the empty string to a number; the result becomes `0 == 0` and comes out true. No step is
arbitrary.

#### Loose Equality Is Not Transitive

One of the properties expected of an equality relation is transitivity: if $a = b$ and
$b = c$, then $a = c$ should hold. Loose equality does not carry this property:

```js
console.log("0" == 0);
console.log(0 == "");
console.log("0" == "");
```

```
true
true
false
```

In the first two lines, the comparison was done through number; in the third line,
because both sides are strings, the first rule kicks in and there is no coercion. The
result is a relation that does not contradict itself but is not transitive.

The course's rule follows from this:

> **Equality comparisons are written with `===`.** The one exception is the `value ==
> null` form, which tests together whether a value is `null` or `undefined`.

The reasoning for the exception is the second rule: this form covers exactly two values,
and accidentally includes no other value.

```js
const incoming = { station: "A2", humidity: undefined };
console.log("loose:", incoming.humidity == null);
console.log("strict:", incoming.humidity === null);
```

```
loose: true
strict: false
```

The two lines answer different questions. The first is "does this field have no value at
all"; the second is "is this field deliberately empty." The distinction in the Primitive
Types lesson determines which question needs to be asked.

#### Same-Value Equality

The third way removes both of strict equality's exceptions:

```js
console.log(Object.is(NaN, NaN));
console.log(Object.is(0, -0));
console.log(Object.is(21.5, 21.5));
```

```
true
false
true
```

`Object.is` asks whether two values are **the same value**. `NaN` is the same value as
itself; `0` and `-0` are different values. This is the relation used internally in
several places in the standard.

`Object.is` is not needed in everyday use; `===` together with `Number.isNaN` does the
same job and is more readable. Knowing the distinction is necessary to explain the
behavior of built-in methods — for example, some array search methods can find `NaN`
while others cannot, because they use different relations.

#### Comparing Objects

None of the three operators compares object content. What is compared for objects is
**identity**: whether two references point to the same object.

```js
const a = { station: "A1" };
const b = { station: "A1" };
const c = a;
console.log(a === b);
console.log(a === c);
console.log(a == b);
```

```
false
true
false
```

`a` and `b` have the same content but are different objects. `c` is equal because it
points to the same object as `a`. Loose equality gives the same result: because both
sides are objects, the first rule kicks in and no coercion happens.

The identity–equality distinction established in the Variables and Binding lesson of the
Programming Fundamentals course maps directly onto the language's operators here. If
content comparison is needed, it has to be written; the language does not provide it.

#### Application to the Measurement Script

Checks that test records' validity, when written with the right operator, distinguish
both missing and corrupted fields:

```js
const records = [
  { station: "A1", temperature: 21.5, humidity: null },
  { station: "A2", temperature: 0, humidity: 55 },
  { station: "B1", temperature: NaN, humidity: 41 },
];

for (const record of records) {
  const humidityMissing = record.humidity == null;
  const temperatureInvalid = Number.isNaN(record.temperature);
  console.log(record.station, "humidity missing:", humidityMissing, "temperature invalid:", temperatureInvalid);
}
```

```
A1 humidity missing: true temperature invalid: false
A2 humidity missing: false temperature invalid: false
B1 humidity missing: false temperature invalid: true
```

The `0` value in the second record passed both checks — zero is neither missing nor
invalid. This is exactly the situation the shorthand form broke in the previous lesson.
The `NaN` in the third record can only be caught with `Number.isNaN`; the form
`record.temperature === NaN` would never give true.

#### Summary

- `===` looks at type first; if the types differ, it gives false without coercion.
- `NaN` is not equal to any value, not even itself; `0` and `-0` are equal under strict
  equality.
- `==` descends to a common type with five rules; `null` and `undefined` are equal only
  to each other, booleans are converted to number first, objects are reduced to a
  primitive value.
- Loose equality is not transitive; this is why comparisons are written with `===`, with
  the one exception being the `value == null` form.
- `Object.is` is same-value equality: `NaN` equals itself, `0` and `-0` differ.
- Objects are compared by identity; content comparison does not happen unless written.

#### Next Step

The question left from the previous lesson is still open: what happens when a number,
string, or object is placed inside a condition? The form `if (value)` is not an equality
comparison; it calls the third coercion procedure, the one that converts a value to
boolean. The next lesson will give this procedure's full list and show why the value `0`
is mistaken for missing data.

### Truthy and Falsy Values

Source: https://academia.sh/en/courses/javascript-fundamentals/truthy-and-falsy-values

The to-boolean conversion procedure, the eight values counted as false, logical operators returning a value, nullish coalescing, and optional chaining.

The previous two lessons defined two coercion procedures: converting to number and to
string. The third, converting to boolean, is the foundation of conditional constructs and
answers a question deferred across two lessons: why was the value `0` mistaken for
missing data?

The procedure itself is short — there is no page of rules, there is a list of eight
values. The real subtlety is not in the list, it is in how logical operators use that
list.

#### The Eight Values Counted as False

The to-boolean conversion procedure is defined by a list: the following eight values are
**counted as false (falsy)**, every other value is counted as true.

```js
const falsyValues = [
  ["false", false],
  ["0", 0],
  ["-0", -0],
  ["0n", 0n],
  ['""', ""],
  ["null", null],
  ["undefined", undefined],
  ["NaN", NaN],
];

for (const [label, value] of falsyValues) {
  console.log(label.padEnd(10), Boolean(value));
}
```

```
false      false
0          false
-0         false
0n         false
""         false
null       false
undefined  false
NaN        false
```

The list is closed: no value not on it is counted as false. This is a more useful rule
than memorizing the list, because it resolves counterintuitive examples directly:

```js
console.log(Boolean("0"), Boolean("false"), Boolean(" "));
console.log(Boolean([]), Boolean({}));
console.log(Boolean(-1), Boolean(Infinity));
```

```
true true true
true true
true true
```

Every non-empty string is counted as true — regardless of its content. The texts `"0"`
and `"false"` fall under this rule too; they are not converted to number or boolean. An
empty array and an empty object are also counted as true: objects are not on the list, so
all of them are true. Every number besides zero, including negatives and infinity, is
counted as true.

One point deserves attention: `Boolean([])` gives true, while the previous lesson's
`[] == false` comparison gave true too. There is no contradiction — two different
procedures are running. Loose equality converts the array to a primitive value, then to a
number; converting to boolean never looks inside the object at all. The same value giving
a different result in two different contexts makes it necessary to know which procedure
is being called.

#### Logical Operators Do Not Return a Boolean

The `&&` and `||` operators, despite their names, do not produce `true` or `false`. They
return **one of the operands**:

```js
console.log(0 || "fallback");
console.log("A1" || "fallback");
console.log(0 && "never");
console.log("A1" && 21.5);
```

```
fallback
A1
0
21.5
```

The rule is this. The `||` operator evaluates the left operand; if it is counted as true,
it returns it, otherwise it returns the right operand. The `&&` operator evaluates the
left operand; if it is counted as false, it returns it, otherwise it returns the right
operand.

The right operand is evaluated only when needed. This is the short-circuit evaluation
defined in the Operators and Expressions lesson of the Programming Fundamentals course. In
the third line, `"never"` was never evaluated at all.

If the result needs to be forced to boolean type, the conversion is written explicitly.
Because conditional constructs already convert to boolean, this is usually unnecessary.

#### The Default-Value Trap

The `||` operator's most common use is putting a fallback in place of a missing value.
This use breaks down when a falsy value is valid data:

```js
const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: 0 },
  { station: "B1" },
];

for (const record of records) {
  const viaOr = record.temperature || -273.15;
  const viaNullish = record.temperature ?? -273.15;
  console.log(record.station, "|| ->", viaOr, " ?? ->", viaNullish);
}
```

```
A1 || -> 21.5  ?? -> 21.5
A2 || -> -273.15  ?? -> 0
B1 || -> -273.15  ?? -> -273.15
```

The middle line is the answer to the question left open across two lessons. Station
`A2`'s temperature is `0`; this is a valid measurement and it is present in the record.
Still, the `||` operator counted it as false and moved to the fallback value. The
measurement turned into absolute-zero temperature.

In the bottom line's `B1` record, the temperature field truly does not exist; the value
of `record.temperature` is `undefined`, and moving to the fallback is correct there. The
two lines produced the same result, but only one of them was the intended behavior — the
`||` operator cannot distinguish "there is no value" from "the value is zero."

The `??` operator — **nullish coalescing** — moves to the right operand only for `null`
and `undefined` values:

```js
console.log(0 || "fallback");
console.log(0 ?? "fallback");
console.log("" || "fallback");
console.log("" ?? "fallback");
console.log(null ?? "fallback");
```

```
fallback
0
fallback

fallback
```

The fourth line looking empty is because the `??` operator counts an empty string as a
valid value and returns it. The second line, on the other hand, preserved the value `0`.

The selection rule is clear: **if the meaning "value was never given" is wanted, use
`??`; if the meaning "value is counted as false" is wanted, use `||`.** In measurement
data, zero, an empty string, and `false` are most often valid values; this is why default
assignments are written with `??`.

#### Optional Chaining

The third operator of the same family shortens the absence check while accessing nested
fields:

```js
const record = { station: "A1", location: { elevation: 120 } };
const missing = { station: "B1" };
console.log(record.location?.elevation);
console.log(missing.location?.elevation);
console.log(missing.location?.elevation ?? 0);
```

```
120
undefined
0
```

The `?.` operator breaks the chain and returns `undefined` if the value to its left is
`null` or `undefined`; otherwise it continues the access. The check's criterion is the
same as `??` — not values counted as false, only the two absence values.

The same access without the operator throws an error:

```js
const missing = { station: "B1" };
try {
  console.log(missing.location.elevation);
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Cannot read properties of undefined (reading 'elevation')
```

The Ways to Run Code lesson showed how to read this message: the problem is not in the
`elevation` field, it is that the value of `missing.location` is `undefined`.

Optional chaining should be used carefully. It is the right tool if a field being absent
is an expected situation; if it is an unexpected situation, it suppresses the error and
carries the problem forward. The difference lies in the data's contract, not in the
syntax.

#### Application to the Measurement Script

The shortest use of truthy and falsy values is a filtering operation:

```js
const fields = ["21.5", "", "0", "abc", null];
const filled = fields.filter(Boolean);
console.log(filled);
```

```
[ '21.5', '0', 'abc' ]
```

The empty string and `null` were eliminated; the text `"0"` was preserved, because it is
a non-empty string. This is exactly the intended behavior: what is being filtered here is
raw text, not a numeric value.

Applying the same filter to numeric values loses data:

```js
const measurements = [21.5, 0, NaN, 19.75];
console.log(measurements.filter(Boolean));
console.log(measurements.filter((value) => !Number.isNaN(value)));
```

```
[ 21.5, 19.75 ]
[ 21.5, 0, 19.75 ]
```

The first filter dropped a valid zero measurement. The second dropped only what was
truly invalid. The rule is that truthy and falsy values are not a general validity
criterion: what counts as valid depends on the data's meaning and has to be written
explicitly.

#### Summary

- The to-boolean conversion procedure counts eight values as false: `false`, `0`, `-0`,
  `0n`, an empty string, `null`, `undefined`, `NaN`. Every other value is counted as
  true.
- An empty array, an empty object, and strings like `"0"` are counted as true; objects
  are not on the list.
- `&&` and `||` return one of the operands, not a boolean, and evaluate the right operand
  only when needed.
- `||` moves to the fallback for all eight values; `??` moves only for `null` and
  `undefined`. Default assignments in measurement data are written with `??`.
- `?.` breaks the chain only for `null` and `undefined`; it should not be used to hide an
  unexpected absence.

#### Next Step

The value model is complete: what types exist, how they convert, how they are compared,
and how they are evaluated in a condition. Next is building program flow with these
values. The next topic will begin with conditional selection: `if` chains, the ternary
operator, and which equality the `switch` statement uses for comparison — one of the
three operators from the previous lesson.

## Control Flow and Functions

Conditional selection, loop forms, the behavioral difference between function definition forms, parameter rules, arrow functions, and error handling.

### Conditions and Selection

Source: https://academia.sh/en/courses/javascript-fundamentals/conditions-and-selection

`if` chains and guard clauses, the ternary operator, the `switch` statement's strict equality, fall-through behavior, and scope in case blocks.

The value model is complete. This topic covers building program flow with those values:
which code runs under which condition, how repetition is written, and how work is
split into functions. The Programming Fundamentals course's Conditional Branching lesson
established the purpose of these structures; here, JavaScript's own particular rules are
covered.

Three things about conditional selection are specific to the language: how a condition
is converted to a boolean value (previous lesson), the ternary operator being an
expression, and which equality `switch` uses.

#### Condition Chains and Guard Clauses

The `if` statement converts the expression in parentheses to a boolean value and runs
the body if it is truthy. The chain continues with `else if`.

**Guard clauses** — conditions placed at the start of a chain that return early on an
invalid case — rescue the real logic from nested blocks:

```js
function classify(temperature) {
  if (temperature == null || Number.isNaN(temperature)) {
    return "invalid";
  }
  if (temperature < 18) {
    return "low";
  } else if (temperature < 22) {
    return "normal";
  } else {
    return "high";
  }
}

for (const value of [17.9, 18, 21.5, 22, 23, null, NaN]) {
  console.log(String(value).padEnd(5), classify(value));
}
```

```
17.9  low
18    normal
21.5  normal
22    high
23    high
null  invalid
NaN   invalid
```

The guard clause combines two checks. The `== null` spelling is the exception defined
in the Equality Comparisons lesson: it covers both the `null` and `undefined` cases.
`Number.isNaN` catches an invalid number; because `NaN` passes no comparison, without
this guard `classify(NaN)` would silently give `"high"` — all three comparisons would
come out false and flow would fall to the final `else` branch.

Which side a boundary value falls on is read from the output: `18` went to normal, `22`
went to high. The choice of comparison operator (`<` versus `<=`) sets these boundaries
and is part of the definition.

Curly braces are not required for single-statement bodies, but this course always
writes them. The reason is that a second statement added later would otherwise
silently fall outside the condition.

#### The Ternary Operator

`if` is a statement: it produces no value, it directs flow. The ternary operator is an
**expression**: it produces a value and can be written anywhere a value is expected.

```js
const temperature = 21.5;
const status = temperature >= 22 ? "high" : temperature >= 18 ? "normal" : "low";
console.log(status);
console.log(`${temperature} degrees: ${temperature > 20 ? "above threshold" : "below threshold"}`);
```

```
normal
21.5 degrees: above threshold
```

The second line shows the concrete benefit of being an expression: a condition was
placed directly inside a template literal. An `if` statement could not have been written
there.

A chained ternary associates to the right; the second question mark sits inside the
first expression's false branch. This spelling quickly loses readability past two
branches. This course's rule: **a ternary is chained at most once**; anything more is
written as an `if` chain or a mapping object.

#### The `switch` Statement

`switch` compares a value against multiple constants. Matching is done with **strict
equality** — one of the three operators from the Equality Comparisons lesson, `===`.

```js
function unit(field) {
  switch (field) {
    case "temperature":
      return "C";
    case "humidity":
      return "%";
    case "pressure":
      return "hPa";
    default:
      return "unknown";
  }
}
console.log(unit("temperature"), unit("humidity"), unit("wind"));
```

```
C % unknown
```

Using strict equality removes any expectation of coercion:

```js
function check(value) {
  switch (value) {
    case 1:
      return "number one";
    case "1":
      return "text one";
    default:
      return "no match";
  }
}
console.log(check(1), "|", check("1"), "|", check(true));
```

```
number one | text one | no match
```

The number `1` and the text `"1"` went to separate branches. The value `true` matched
neither — under loose equality `true == 1` would be true, but `switch` does not use
loose equality.

#### Fall-Through

The `switch` statement starts at the matching `case` label and **flows downward until
it meets a `break` or `return`**. This behavior is called fall-through, and when done
unintentionally it produces a silent bug:

```js
function label(field) {
  let result = "";
  switch (field) {
    case "temperature":
      result = "temperature (C)";
    case "humidity":
      result += " humidity (%)";
      break;
    default:
      result = "unknown";
  }
  return result;
}
console.log(JSON.stringify(label("temperature")));
console.log(JSON.stringify(label("humidity")));
console.log(JSON.stringify(label("pressure")));
```

```
"temperature (C) humidity (%)"
" humidity (%)"
"unknown"
```

On the first line, flow fell from the first branch into the second and the two texts
concatenated. This was not the writer's intent. The outputs were written with
`JSON.stringify`; otherwise the leading space on the second line would not be visible.

Used deliberately, fall-through binds multiple values to the same branch:

```js
function group(station) {
  switch (station) {
    case "A1":
    case "A2":
      return "group A";
    case "B1":
      return "group B";
    default:
      return "unknown";
  }
}
console.log(group("A1"), "|", group("A2"), "|", group("B1"), "|", group("C9"));
```

```
group A | group A | group B | unknown
```

Empty `case` labels have no body; flow falls straight through to the next one. This is
an accepted use of the language and is not hard to read — falling from a branch **with**
a body into another will not be written in this course.

#### Scope in Case Blocks

The entire `switch` body is **a single block**. Separate `case` labels do not open
separate scopes; this is why declaring the same name in two branches gives a parsing
error:

```js
switch (1) {
  case 1:
    const x = 1;
    break;
  case 2:
    const x = 2;
    break;
}
```

```
SyntaxError: Identifier 'x' has already been declared
```

The redeclaration rule from the Variable Declarations lesson shows up here in an
unexpected place. The fix is to wrap each branch in its own curly braces:

```js
switch (1) {
  case 1: {
    const x = "first";
    console.log(x);
    break;
  }
  case 2: {
    const x = "second";
    console.log(x);
    break;
  }
}
```

```
first
```

#### Choosing a Selection Structure

The choice among the three structures is made according to the shape of the
comparison.

`switch` fits when a single value is compared against constants. Range checks, compound
conditions, or a comparison other than equality call for an `if` chain.

Constant mappings offer a third option: they can be written as key–value pairs in an
object and read directly.

```js
const UNITS = { temperature: "C", humidity: "%", pressure: "hPa" };

function unit(field) {
  return UNITS[field] ?? "unknown";
}
console.log(unit("temperature"), unit("wind"));
```

```
C unknown
```

This spelling keeps its readability as the mapping grows and separates data from code.
Using `??` is deliberate: if a key in the mapping had a value of `0` or an empty
string, `||` would have dropped that to the default too.

#### Summary

- Guard clauses return early on an invalid case and rescue the real logic from nested
  blocks; without a `NaN` guard, flow silently falls to the final branch.
- The ternary operator is an expression and can be written anywhere a value is
  expected; it is chained at most once.
- `switch` matches with strict equality; it performs no coercion.
- Flow starts at the matching label and falls through until it meets `break` or
  `return`; bodyless labels bind multiple values to the same branch.
- The `switch` body is a single block; to declare a name in a branch, wrap each branch
  in its own curly braces.
- Constant mappings written as objects substitute for `switch` and take a default with
  `??`.

#### Next Step

Conditions classified a single record. Processing the entire measurement list requires
repetition. The next lesson covers loop forms: the counted loop, the conditional loop,
and two different collection loops. The difference between the two collection loops —
one gives keys, the other gives values — produces unexpected results on arrays.

### Loops

Source: https://academia.sh/en/courses/javascript-fundamentals/loops

Counted, conditional, and collection loops; the `for...in` versus `for...of` distinction, loop control, labeled exit, and traversing objects.

The previous lesson classified a single measurement. Processing the entire list
requires repetition. The Programming Fundamentals course's Loops and Loop Control
lessons established the purpose of these structures and the loop-invariant concept;
here, the forms JavaScript offers and the difference between them — especially between
the two collection loops — are covered.

There are five loop forms, and the choice among them is not arbitrary: the counted
loop is used when an index is needed, the conditional loops when the repeat count is not
known in advance (with the form that tests at the end when the body must run at least
once), and the collection loops when working directly on elements.

#### Counted and Conditional Loops

The classic `for` loop has three parts: initialization, condition, and end-of-round
action. As shown in the Variable Declarations lesson, the counter is declared with
`let` and rebound on every round.

```js
const temperatures = [21.5, 19.75, 23, 18.25];

for (let i = 0; i < temperatures.length; i++) {
  console.log(i, temperatures[i]);
}
```

```
0 21.5
1 19.75
2 23
3 18.25
```

The `while` loop carries only a condition; it is used when the repeat count is not
known in advance:

```js
const temperatures = [21.5, 19.75, 23, 18.25];
let i = 0;
while (i < temperatures.length && temperatures[i] < 23) {
  i += 1;
}
console.log("first index at or above 23:", i);
```

```
first index at or above 23: 2
```

The order in the condition matters. The bound check (`i < temperatures.length`) is
written first; thanks to short-circuit evaluation, the second comparison never runs
once the index is exceeded. Written in reverse order, the comparison
`undefined < 23` would run at the end of the array — this expression gives false
because it goes through a coercion that produces `NaN`, so the loop would still end,
but the reason would be coincidence rather than the guard.

The `do...while` form runs the body at least once; the condition is tested at the end
of the round:

```js
let round = 0;
do {
  round += 1;
} while (round < 3);
console.log("round:", round);
```

```
round: 3
```

#### Two Collection Loops

JavaScript has two collection loops that resemble each other and **give different
things**.

`for...in` gives an object's **enumerable property keys**. `for...of` gives an
iterable value's **elements**.

```js
const temperatures = [21.5, 19.75, 23];

for (const index in temperatures) {
  console.log(typeof index, index);
}
for (const value of temperatures) {
  console.log(typeof value, value);
}
```

```
string 0
string 1
string 2
number 21.5
number 19.75
number 23
```

The first loop gave indices, and the indices are of **string type**. The reason is that
object keys are strings; an array is also an object and carries its indices as keys.
This explains why code that sums with `for...in` ends up concatenating text — the
addition rule from the Type Conversion and Coercion lesson kicks in.

The difference becomes even clearer once a property is added to the array:

```js
const temperatures = [21.5, 19.75, 23];
temperatures.source = "A1";

for (const index in temperatures) {
  console.log("for...in:", index);
}
for (const value of temperatures) {
  console.log("for...of:", value);
}
console.log("length:", temperatures.length);
```

```
for...in: 0
for...in: 1
for...in: 2
for...in: source
for...of: 21.5
for...of: 19.75
for...of: 23
length: 3
```

`for...in` also traversed a key that is not an array element; `for...of` gave only the
elements. The `length` value did not change either — the added property does not count
toward the array's length.

This gives the course's rule: **`for...of` is used on arrays.** `for...in` is written
only to traverse plain objects' keys.

For the case where both index and value are wanted together, there is a separate
method:

```js
const temperatures = [21.5, 19.75, 23];
for (const [index, value] of temperatures.entries()) {
  console.log(index, value);
}
```

```
0 21.5
1 19.75
2 23
```

Here the index is of number type. The bracketed spelling splits the pair's two
components into two names; this **destructuring** form is covered in detail in the
Arrays and Objects lessons.

#### Traversing Objects

Plain objects cannot be traversed with `for...of`:

```js
const record = { station: "A1", temperature: 21.5, humidity: 48 };
try {
  for (const d of record) {
    console.log(d);
  }
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: record is not iterable
```

The message is exact: the object is not **iterable**. Arrays, strings, and some
built-in collections are iterable; plain objects are not.

There are two paths:

```js
const record = { station: "A1", temperature: 21.5, humidity: 48 };

for (const key in record) {
  console.log(key, record[key]);
}
for (const [key, value] of Object.entries(record)) {
  console.log(key, "=", value);
}
```

```
station A1
temperature 21.5
humidity 48
station = A1
temperature = 21.5
humidity = 48
```

The two gave the same result, but their scope is not the same. `for...in` also
traverses inherited properties; `Object.entries` gives only the object's own
properties. The inheritance chain is a later course's subject; for now, it is enough to
know the second form is narrower and more predictable. In this course, object
traversal will be written with `Object.entries`.

#### Loop Control

`break` ends a loop, `continue` skips that round. Both affect the innermost loop.

```js
const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: NaN },
  { station: "B1", temperature: 23 },
  { station: "B2", temperature: 18.25 },
];

let total = 0;
let count = 0;
for (const record of records) {
  if (Number.isNaN(record.temperature)) {
    continue;
  }
  total += record.temperature;
  count += 1;
}
console.log("valid readings:", count, "average:", total / count);

let firstHigh = null;
for (const record of records) {
  if (record.temperature > 22) {
    firstHigh = record.station;
    break;
  }
}
console.log("first high:", firstHigh);
```

```
valid readings: 3 average: 20.916666666666668
first high: B1
```

The first loop skipped the invalid record and computed the average only over valid
readings — this is why `count` had to be used as the divisor instead of
`records.length`. The second loop stopped once it found what it was looking for; the
remaining records were never checked.

The average's digits are the result of the floating-point representation covered in the
Primitive Types lesson: the result of `62.75 / 3` cannot be written exactly as a binary
fraction and is rounded to the nearest representable value.

Breaking out of an outer loop from a nested one requires a **label**:

```js
const grid = [[1, 2], [3, 4], [5, 6]];
outerLoop: for (const row of grid) {
  for (const cell of row) {
    if (cell === 4) {
      console.log("found, breaking both loops");
      break outerLoop;
    }
    console.log("looking at:", cell);
  }
}
```

```
looking at: 1
looking at: 2
looking at: 3
found, breaking both loops
```

A label is a name placed in front of a loop; `break` and `continue` take this name to
say which loop they affect. An unlabeled `break` would have broken only the inner loop,
and the outer loop would have moved on to the next row.

Labeled exit is rarely needed. Wrapping the same work in a function and exiting with
`return` is usually more readable; function definition is the next lesson's subject.

#### Summary

- Counted `for` is used when an index is needed, `while` when the repeat count is
  unknown, `do...while` when the body must run at least once.
- `for...in` gives property keys, and the keys are of string type; `for...of` gives
  elements.
- `for...of` is used on arrays; `for...in` also traverses non-array properties.
- Plain objects are not iterable; key–value traversal is written with
  `Object.entries`.
- `break` and `continue` affect the innermost loop; a label is used to reach an outer
  one.

#### Next Step

In this lesson, checks and accumulators were embedded in the loop body; doing the same
work a second time would mean copying code. The next lesson moves to functions and
opens with a distinction specific to JavaScript: the difference between writing a
function as a declaration and as an expression is not just syntax — it also determines
when the name becomes accessible.

### Function Declaration and Expression

Source: https://academia.sh/en/courses/javascript-fundamentals/function-declarations-and-expressions

The hoisting difference between the two definition forms, named function expressions, using functions as values, return rules, and the automatic semicolon insertion trap.

The previous lesson had checks and accumulators embedded in the loop body. Doing the
same work a second time would mean copying code. The Programming Fundamentals course's
Function Definition and Invocation lesson established the reason for this separation.

JavaScript has more than one way to define a function, and the difference between them
is not just syntax. This lesson compares the two basic forms; the third form, arrow
functions, is covered in this topic's fifth lesson.

#### Two Definition Forms

A **function declaration** is a statement that begins with the `function` keyword and
carries a name. A **function expression** is a form that produces a value and is
usually assigned to a variable.

The difference shown in the Naming and Scope Rules lesson repeats here: a declaration
is hoisted together with its body, an expression is not.

```js
console.log(averageDeclaration([21.5, 19.75]));

function averageDeclaration(array) {
  let total = 0;
  for (const value of array) total += value;
  return total / array.length;
}

const averageExpression = function (array) {
  let total = 0;
  for (const value of array) total += value;
  return total / array.length;
};
console.log(averageExpression([21.5, 19.75]));
```

```
20.625
20.625
```

The declaration could be called from the line before its definition. Had the
expression been called before its assignment line, it would have thrown a temporal
dead zone error.

The second difference between the two forms is that the expression is a **value**: it
can be passed directly as an argument, made a property of an object, or returned from a
function. A declaration cannot do this; it first has to be bound to a name.

This course's rule: **helper functions in the outermost scope are written in
declaration form**; functions passed to another function or carried in a variable are
written in expression form. The reason is that a declaration is independent of call
order and frees up the file's reading order.

#### Named Function Expressions

A function expression may carry a name. This name is seen only inside the function's
**own body**:

```js
const sum = function adder(array, index = 0) {
  if (index >= array.length) return 0;
  return array[index] + adder(array, index + 1);
};
console.log(sum([21.5, 19.75, 23]));
console.log(sum.name);
try {
  console.log(adder([1]));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
64.25
adder
ReferenceError: adder is not defined
```

The inner name made the recursive call possible and showed up in the `name` property,
but it was not accessible from outside. This lets a recursive function expression call
itself without depending on the outer variable; even if the outer variable later gets
bound to another value, the recursion keeps working.

Functions carry two informative properties:

```js
function aboveThreshold(array, threshold) { return array.filter((d) => d > threshold); }
const anonymous = function (a, b) { return a + b; };
console.log(aboveThreshold.name, aboveThreshold.length);
console.log(anonymous.name, anonymous.length);
console.log(typeof aboveThreshold, aboveThreshold instanceof Object);
```

```
aboveThreshold 2
anonymous 2
function true
```

`name` is the name that appears in error messages and stack traces; it can be inferred
from the variable it is assigned to. `length` is the expected parameter count —
parameters with defaults and rest parameters are not counted, which is the next
lesson's subject.

The last line is a type observation: functions are objects. `typeof` gives a distinct
result, but they carry properties and behave as objects.

#### Functions Are Values

A function being a value is what makes passing it as an argument and returning it from
a function possible. This is the higher-order function concept defined in the
Programming Fundamentals course's Introduction to Functional Programming lesson.

```js
const temperatures = [21.5, 19.75, 23, 18.25];

function thresholdFilter(threshold) {
  return function (value) {
    return value > threshold;
  };
}

const above20 = thresholdFilter(20);
console.log(temperatures.filter(above20));
console.log(temperatures.filter(thresholdFilter(22)));
```

```
[ 21.5, 23 ]
[ 23 ]
```

The returned function keeps carrying the `threshold` binding from the call it was
created in. Two separate calls produced two separate bindings; the first saw `20`, the
second `22`. This behavior is called a **closure** and is one of the core subjects of
the next course. Here it is only being used.

A function expression can be called immediately where it is defined. This pattern is
used to do work without leaking a name to the outer scope:

```js
const report = (function () {
  const hidden = [21.5, 19.75];
  return { count: hidden.length };
})();
console.log(report);
console.log(typeof hidden);
```

```
{ count: 2 }
undefined
```

The name `hidden` is not visible outside. Module mode removes most of this need — as
seen in the Ways to Run Code lesson, a module's outermost scope is already private —
but the pattern is still used to open a narrow scope.

#### Declarations Inside a Block

In strict mode, function declarations are **block-scoped**:

```js
if (true) {
  function inBlock() {
    return "block";
  }
  console.log(inBlock());
}
try {
  console.log(inBlock());
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
block
ReferenceError: inBlock is not defined
```

This behavior is different in non-strict scripts, and compatibility rules come into
play. The way to avoid ambiguity is to write conditional definitions as expressions
rather than declarations: a variable declaration's scope rule is the same in both
modes.

#### Return Rules

A function with no return value written returns `undefined`. This is the value the
caller sees, independent of whether the function did any work.

```js
function logRecord(record) {
  console.log(record.station, record.temperature);
}
const result = logRecord({ station: "A1", temperature: 21.5 });
console.log("return:", result);

function earlyExit(value) {
  if (value == null) return;
  return value * 2;
}
console.log(earlyExit(null), earlyExit(10));
```

```
A1 21.5
return: undefined
undefined 20
```

The command–query separation from the Programming Fundamentals course applies here too:
a function either does work or returns a value. `logRecord` does work; using its return
value is meaningless.

#### The Automatic Semicolon Insertion Trap

JavaScript inserts a semicolon at line end in certain situations. The rule has a sharp
consequence for the `return` statement: **no line break may sit between `return` and
the expression to be returned.**

```js
function brokenReturn() {
  return
    21.5;
}
function correctReturn() {
  return 21.5;
}
console.log(brokenReturn());
console.log(correctReturn());
```

```
undefined
21.5
```

In the first function, the `return` statement was terminated on its own line; the
`21.5;` line became a separate statement that never runs. No error was thrown, no
warning appeared — only the wrong value came back.

The same rule applies to `throw`, `break`, `continue`, and the increment operators. The
way to guard against it is to start the expression to be returned on the same line as
`return`. If a long expression is being returned, its opening parenthesis is placed on
the same line.

#### Applying It to the Measurement Script

Once the previous lessons' checks and computations are split into functions, the
script's body shrinks to a few lines showing the flow:

```js
function isValid(record) {
  return record.temperature != null && !Number.isNaN(record.temperature);
}

function average(records) {
  let total = 0;
  let count = 0;
  for (const record of records) {
    if (!isValid(record)) continue;
    total += record.temperature;
    count += 1;
  }
  return count === 0 ? null : total / count;
}

const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: NaN },
  { station: "B1", temperature: 23 },
  { station: "B2", temperature: null },
];

console.log("valid:", records.filter(isValid).length);
console.log("average:", average(records));
console.log("empty list:", average([]));
```

```
valid: 2
average: 22.25
empty list: null
```

`isValid` was both passed as an argument to `filter` and called inside a loop — the
direct benefit of a function being a value. The `average` function returns `null` for
an empty list; had it divided anyway, it would have produced `NaN` and the error would
have moved forward silently.

#### Summary

- A function declaration is hoisted together with its body; a function expression comes
  into being on its assignment line and is in the dead zone before that.
- A named function expression's name is seen only inside its own body and makes
  recursion independent of the outer variable.
- Functions are values: they are passed as arguments, returned, and carry properties as
  objects.
- In strict mode, function declarations inside a block are block-scoped; conditional
  definitions are written as expressions.
- A function with no return value written gives `undefined`; if a line break sits
  between `return` and the expression, the statement ends there and `undefined` is
  returned silently.

#### Next Step

In this lesson, functions were called with a fixed number of parameters. In real usage,
an argument may be missing, extra, or its count may not be known in advance. The next
lesson covers parameter rules: when default values kick in, how rest parameters
collect, and why the old arguments object is not used in newer code.

### Parameters

Source: https://academia.sh/en/courses/javascript-fundamentals/parameters

Missing and extra arguments, the condition under which default values kick in, rest parameters and spread, the arguments object, and destructured parameters.

In the previous lesson, functions were called with a fixed number of arguments.
JavaScript is loose about this: a missing argument throws no error, and neither does an
extra one. The cost of this looseness is that a missing argument stays silent.

This lesson covers parameter rules. The Programming Fundamentals course's Function
Definition and Call lesson established the parameter–argument distinction; here, which
syntax JavaScript meets these concepts with, and which traps it carries, are shown.

#### Missing and Extra Arguments

A parameter not given at the call site gets the value `undefined`. An extra argument
given is silently ignored:

```js
function report(station, threshold) {
  return `${station} / ${threshold}`;
}
console.log(report("A1"));
console.log(report("A1", 20, "extra"));
```

```
A1 / undefined
A1 / 20
```

Neither behavior produced an error. This is a deliberate decision of the language and
makes it possible for functions to behave differently based on argument count. Its cost
is that an argument left missing by a typo is only noticed once it carries an
`undefined` value into a later operation and breaks it.

#### Default Values

A parameter can be given a default value. **The default kicks in only when the
argument is `undefined`** — whether it is missing or explicitly given as `undefined`.

```js
function report(station, threshold = 20) {
  return `${station} / ${threshold}`;
}
console.log(report("A1"));
console.log(report("A1", undefined));
console.log(report("A1", null));
console.log(report("A1", 0));
console.log(report.length);
```

```
A1 / 20
A1 / 20
A1 / null
A1 / 0
1
```

The third and fourth lines show that this is a narrower criterion than even the `??`
operator from the Truthiness lesson: not even `null` triggers the default. The value
`0` also passes through — default values naturally avoid the trap that a fallback
assignment written with `||` would fall into.

The last line shows that the `length` property gives the parameter count **before**
the parameter with a default.

Default expressions are **re-evaluated on every call** and can use the parameters that
come before them:

```js
let counter = 0;
function next() {
  counter += 1;
  return counter;
}
function label(name, sequence = next()) {
  return `${name}-${sequence}`;
}
console.log(label("A1"));
console.log(label("A2"));
console.log(label("B1", 99));
console.log(label("B2"));
```

```
A1-1
A2-2
B1-99
B2-3
```

In the third call, `next` was never called because an argument was given; the counter
did not increase. This is proof that a default is evaluated at call time and only when
needed.

A default resting on a previous parameter is useful in range definitions:

```js
function range(low, high = low + 5) {
  return [low, high];
}
console.log(range(18));
console.log(range(18, 30));
```

```
[ 18, 23 ]
[ 18, 30 ]
```

The order is one-directional: a parameter can refer only to parameters written
**before** it.

A default expression being evaluated at call time also opens a way to declare required
parameters:

```js
function required(name) {
  throw new Error(`${name} parameter is required`);
}
function save(station = required("station"), temperature = required("temperature")) {
  return `${station}=${temperature}`;
}
console.log(save("A1", 21.5));
try {
  save("A1");
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
A1=21.5
Error: temperature parameter is required
```

When an argument is given, the default expression never runs; when it is not given, an
error is thrown. Error objects and the `throw` statement are covered in this topic's
last lesson.

#### Rest Parameters and Spread

Arguments whose count is not known in advance are collected with a **rest
parameter**. The three-dot spelling puts all remaining arguments into a real array:

```js
function highest(label, ...readings) {
  return `${label}: ${Math.max(...readings)} (${readings.length} readings)`;
}
console.log(highest("A1", 21.5, 19.75, 23));
console.log(highest("B1"));
console.log(highest.length);
```

```
A1: 23 (3 readings)
B1: -Infinity (0 readings)
1
```

The second line shows an edge case: called with no readings, `readings` is an empty
array, and `Math.max()` gives `-Infinity`. This is mathematically consistent — the
maximum of an empty set returns the neutral element — but is meaningless in a
measurement context. An empty-list check should be written.

The same three dots work in the opposite direction at the call site: they spread an
array into separate arguments.

```js
function highest(label, ...readings) {
  return `${label}: ${Math.max(...readings)}`;
}
const temperatures = [21.5, 19.75, 23, 18.25];
console.log(highest("all", ...temperatures));
```

```
all: 23
```

The two uses should not be confused. Three dots in a parameter list **collect**; three
dots at a call site **spread**. The same syntax's two directions are told apart by
where they sit.

A rest parameter can only be in the last position and is not counted toward the
`length` property.

#### The Arguments Object

Before rest parameters, a variable number of arguments was read through an implicit
object called `arguments`. This object still exists and is found in older code:

```js
function oldForm() {
  console.log(typeof arguments, arguments.length);
  console.log(Array.isArray(arguments));
  console.log(Array.from(arguments));
}
oldForm(21.5, 19.75);
```

```
object 2
false
[ 21.5, 19.75 ]
```

The second line is the problem itself: `arguments` is not an array. It has a length
and indices, but no array methods; it has to be converted to an array first to be used.

A second problem shows up with default values:

```js
function withDefault(a = 1) {
  console.log("arguments.length:", arguments.length, "a:", a);
}
withDefault();
withDefault(5);
```

```
arguments.length: 0 a: 1
arguments.length: 1 a: 5
```

`arguments` only counts the arguments **actually given**; it does not see a parameter
filled in by a default. The two sources of information diverge, and which one to read
becomes unclear.

Third, `arguments` is not found at all in arrow functions — this topic's next subject.
In this course, a variable number of arguments will always be written with a rest
parameter.

#### Destructured Parameters

In a function taking more than three or four parameters, the call line becomes
unreadable: the order has to be memorized. The solution is to gather the options into
an object and unpack it in the parameter list through **destructuring**:

```js
function summarize({ station, temperature, unit = "C" }) {
  return `${station}: ${temperature} ${unit}`;
}
console.log(summarize({ station: "A1", temperature: 21.5 }));
console.log(summarize({ station: "A2", temperature: 19.75, unit: "K" }));
```

```
A1: 21.5 C
A2: 19.75 K
```

The call line now states which value is which on its own, and order no longer matters.
Defaults can be given to individual fields one by one.

This spelling has a trap: if the argument is not given at all, destructuring tries to
operate on `undefined`:

```js
function summarize({ station, temperature }) {
  return `${station}: ${temperature}`;
}
try {
  console.log(summarize());
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Cannot destructure property 'station' of 'undefined' as it is undefined.
```

The fix is to give the entire parameter an empty-object default:

```js
function summarizeSafe({ station = "?", temperature = null } = {}) {
  return `${station}: ${temperature}`;
}
console.log(summarizeSafe());
console.log(summarizeSafe({ station: "B1" }));
```

```
?: null
B1: null
```

Two layers of defaults work together: the outer `= {}` kicks in if the object itself
is missing, the inner ones kick in if individual fields are missing.

If all of a function's options are required, the outer default is not written; then a
missing call throws an error, and this is the intended behavior. The choice depends on
what the contract is.

#### Summary

- A missing argument becomes `undefined`, an extra argument is ignored; neither
  produces an error.
- A default value kicks in only when the argument is `undefined`; `null` and `0` do
  not trigger the default.
- Default expressions are evaluated on every call and only when needed; they can use
  the parameters before them.
- A rest parameter collects arguments into a real array and must be last; three dots
  at a call site work in reverse, spreading an array into arguments.
- The `arguments` object is not an array and does not count parameters filled by a
  default; this course uses rest parameters.
- A destructured parameter provides named options; if the whole object has no default,
  a call with no arguments throws an error.

#### Next Step

Short forms like `(d) => d > threshold` were used unexplained in this lesson. The next
lesson covers arrow functions: the forms of the short syntax, the implicit return rule,
and the difference these functions have from the `arguments` object and from context
behavior. The difference is not just brevity — an arrow function cannot be used in some
places.

### Arrow Functions

Source: https://academia.sh/en/courses/javascript-fundamentals/arrow-functions

The forms of the short syntax, the implicit return and object-return trap, the absence of the arguments object, and context coming from the enclosing scope.

Earlier lessons used spellings like `(d) => d > threshold` unexplained. This lesson
defines that syntax. The arrow function is a third function-definition form and offers
two gains: brevity and a change in context behavior.

The second is the real one. Brevity sometimes improves readability and sometimes
reduces it; context behavior, on the other hand, is what makes an arrow function
unusable in some places. By the end of this lesson, which form to choose where will be
tied to a rule.

#### Syntax Forms

An arrow function consists of a parameter list and a body, separated by `=>`.

```js
const square = (x) => x * x;
const singleParam = x => x * 2;
const two = (a, b) => a + b;
const zero = () => 42;
const withBody = (array) => {
  let total = 0;
  for (const value of array) total += value;
  return total;
};

console.log(square(4), singleParam(4), two(1, 2), zero());
console.log(withBody([21.5, 19.75]));
```

```
16 8 3 42
41.25
```

Parentheses can be omitted for a single parameter; they are required for the
no-parameter and multi-parameter forms. In this course, parentheses will always be
written — a single form reduces the change needed when a parameter is added.

The body comes in two kinds. A body without curly braces is an **expression**, and its
value is implicitly returned; `return` is not written. A body with curly braces is a
**block**, and the return is written explicitly. Without `return`, the `withBody`
function's result would have been `undefined`.

#### The Object-Return Trap

Returning an object with an implicit return creates a syntactic ambiguity: does the
opening curly brace mean an object, or a block?

```js
const broken = (name) => { station: name };
console.log(broken("A1"));
```

```
undefined
```

The parser read the curly braces as a block. The line `station: name` inside the block
is not an object property, it is a statement labeled `station` — the same labeled-loop
syntax as the Loops lesson. Because the block has no `return`, the result was
`undefined`. No error was thrown.

The fix is to wrap the object in parentheses:

```js
const returnsObject = (name) => ({ station: name });
console.log(returnsObject("A1"));
```

```
{ station: 'A1' }
```

The parentheses signal that the curly braces are in an expression context; the parser
now reads an object.

#### No Arguments Object

Arrow functions have no `arguments` object of their own:

```js
const arrowFn = () => {
  try {
    return arguments.length;
  } catch (error) {
    return error.name + ": " + error.message;
  }
};
console.log(arrowFn(1, 2));
```

```
ReferenceError: arguments is not defined
```

The message says "not defined" — an arrow function does not define such a name. A
variable number of arguments is taken with a rest parameter:

```js
const withRest = (...args) => args.length;
console.log(withRest(1, 2));
```

```
2
```

The rule set in the Parameters lesson becomes mandatory here.

#### Context Comes From the Enclosing Scope

Arrow functions have no `this` binding of their own. The `this` in the body is the
`this` value **where the function is defined**. The lexical scope principle
established in the Naming and Scope Rules lesson also applies to binding in arrow
functions.

The rules of `this` binding are the next course's subject. Here, only the observed
difference is covered, and it shows up most in callback functions:

```js
const watcher = {
  threshold: 20,
  temperatures: [21.5, 19.75, 23],

  classicCallback() {
    try {
      return this.temperatures.filter(function (d) {
        return d > this.threshold;
      });
    } catch (error) {
      return error.name + ": " + error.message;
    }
  },

  arrowCallback() {
    return this.temperatures.filter((d) => d > this.threshold);
  },
};

console.log(watcher.classicCallback());
console.log(watcher.arrowCallback());
```

```
TypeError: Cannot read properties of undefined (reading 'threshold')
[ 21.5, 23 ]
```

Both methods were called on the same object, and the outer `this` was the object
itself in both. The difference is in the inner function. The classic function
expression establishes its own `this` binding; in strict mode this binding is
`undefined`, and reading a property throws an error. The arrow function establishes no
binding of its own, uses the outer one, and the filter works as expected.

The same behavior also produces a result in the opposite direction. When an arrow
function is written as an object method, `this` does not bind to the object:

```js
const station = {
  name: "A1",
  classicMethod() {
    return this === undefined ? "this: undefined" : `this.name: ${this.name}`;
  },
  arrowMethod: () => {
    return this === undefined ? "this: undefined" : `this.name: ${this.name}`;
  },
};
console.log(station.classicMethod());
console.log(station.arrowMethod());
```

```
this.name: A1
this: undefined
```

The arrow function took the `this` value of where it was defined. Because this example
runs in a module file, the outermost `this` value is `undefined` — the module rule
shown in the Ways to Run Code lesson. The same line in script mode would give a
different value; in neither case would it be the object itself.

The rule follows from this: **object methods are not written as arrow functions.**

#### Cannot Be Used as a Constructor

Arrow functions cannot be called with `new`:

```js
const Measurement = (name) => { this.name = name; };
try {
  new Measurement("A1");
} catch (error) {
  console.log(error.name + ": " + error.message);
}

function ClassicMeasurement(name) { this.name = name; }
console.log(new ClassicMeasurement("A1").name);
console.log(typeof Measurement.prototype, typeof ClassicMeasurement.prototype);
```

```
TypeError: Measurement is not a constructor
A1
undefined object
```

The last line shows the reason: an arrow function has no `prototype` property. Why a
constructor call needs this property, and how the prototype chain is built, is the
next course's subject.

#### Choosing a Form

The choice among the three definition forms comes down to these rules:

- **Declaration:** named helper functions in the outermost scope. Because it is
  hoisted, the file's order is free.
- **Arrow function:** short callbacks passed to another function, and inner functions
  that need to use the enclosing context.
- **Classic function expression:** object methods and cases where `this` binding
  should be determined by the call site.

An arrow function is not short everywhere. An operation whose body runs past a few
lines and carries meaning on its own becomes both more readable and reusable when
pulled out into a named declaration.

#### Applying It to the Measurement Script

Transformation chains are arrow functions' best-fitting use:

```js
const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: NaN },
  { station: "B1", temperature: 23 },
  { station: "B2", temperature: 18.25 },
];

const THRESHOLD = 20;

const valid = (record) => !Number.isNaN(record.temperature);
const aboveThreshold = (record) => record.temperature > THRESHOLD;
const label = (record) => `${record.station}=${record.temperature}`;

console.log(records.filter(valid).filter(aboveThreshold).map(label));

console.log(
  records
    .filter((r) => !Number.isNaN(r.temperature))
    .map((r) => ({ ...r, deviation: r.temperature - THRESHOLD })),
);
```

```
[ 'A1=21.5', 'B1=23' ]
[
  { station: 'A1', temperature: 21.5, deviation: 1.5 },
  { station: 'B1', temperature: 23, deviation: 3 },
  { station: 'B2', temperature: 18.25, deviation: -1.75 }
]
```

Both spellings belong to the same family. In the first, the filters were named and the
chain stayed readable; in the second, short callbacks were written in place. In the
second output, an object was returned, so the parentheses were required — the trap
from this lesson's second section.

The multi-line display in the second output is the runtime's inspection format; an
array is broken into lines once it exceeds a certain length. This format is not
defined in the standard and varies across environments.

The object spread syntax (`{ ...r }`) was used here and will be defined in the Objects
lesson.

#### Summary

- In an arrow function, parentheses can be omitted for a single parameter; a body
  without curly braces is an expression and its value is implicitly returned.
- To return an object with an implicit return, the object is wrapped in parentheses;
  without it, the curly braces are parsed as a block and the result is `undefined`.
- Arrow functions have no `arguments` object; a variable number of arguments is taken
  with a rest parameter.
- An arrow function's `this` value comes from the scope it is defined in; this is the
  desired behavior in callbacks and the undesired behavior in object methods.
- Arrow functions cannot be called with `new`; they have no `prototype` property.

#### Next Step

In this topic's examples, error conditions were caught with `try`/`catch`, but the
structure was never defined. The next lesson covers error handling: the flow of
`try`/`catch`/`finally` blocks, the `throw` statement, built-in error types, and
defining your own error type. The measurement script's response to broken data will
also be tied to a contract there.

### Error Handling

Source: https://academia.sh/en/courses/javascript-fundamentals/error-handling

The flow of `try`/`catch`/`finally`, the `throw` statement, built-in error types, the error object's fields, the cause chain, and the choice between an error and a return value.

In earlier lessons, the `try`/`catch` structure was used to show error messages but was
never defined. This lesson completes the structure: the flow of the blocks, throwing
errors, built-in error types, and — most importantly — the decision of whether a
problem should be reported as an error or as a return value.

The Ways to Run Code lesson showed that an error object carries a name, a message, and
a stack trace. Here, we will produce these objects ourselves.

#### The Flow of the Blocks

The `try` block contains the code being watched. If an error occurs, flow jumps to the
`catch` block. The `finally` block runs whether or not there was an error.

```js
function attempt(value) {
  try {
    console.log("1 try start");
    if (value < 0) throw new Error("negative value");
    console.log("2 try end");
    return "try return";
  } catch (error) {
    console.log("3 catch:", error.message);
    return "catch return";
  } finally {
    console.log("4 finally");
  }
}
console.log(attempt(1));
console.log("---");
console.log(attempt(-1));
```

```
1 try start
2 try end
4 finally
try return
---
1 try start
3 catch: negative value
4 finally
catch return
```

Two observations matter. First, when an error occurs, the `try` block's remaining
lines do not run; flow moves directly to `catch`. Second, the `finally` block runs
**after** the `return` statement but **before** the function actually returns. This is
why cleanup work is written there: closing an opened resource, releasing an acquired
lock.

Writing `return` inside `finally` overrides the return value:

```js
function overrides() {
  try {
    return "try";
  } finally {
    return "finally";
  }
}
console.log(overrides());
```

```
finally
```

The same behavior also swallows a thrown error. This is why the `finally` block should
contain only cleanup — it should carry no statement that redirects flow.

If the error object is not needed, the catch binding may be omitted:

```js
function isValidJson(text) {
  try {
    JSON.parse(text);
    return true;
  } catch {
    return false;
  }
}
console.log(isValidJson('{"a":1}'), isValidJson("{broken}"));
```

```
true false
```

#### Built-in Error Types

The standard defines several error types, and errors the language itself produces are
among them:

```js
const attempts = [
  () => null.x,
  () => unknownName,
  () => ([].length = -1),
  () => (1234).toFixed(200),
];
for (const a of attempts) {
  try {
    a();
  } catch (error) {
    console.log(error.name + ": " + error.message);
  }
}
```

```
TypeError: Cannot read properties of null (reading 'x')
ReferenceError: unknownName is not defined
RangeError: Invalid array length
RangeError: toFixed() digits argument must be between 0 and 100
```

`TypeError` reports that a value was not of the expected kind, `ReferenceError` that a
name could not be found, `RangeError` that a value was outside the allowed range.
`SyntaxError` is produced during parsing; as seen in the first lesson, it never starts
the program at all — its one exception is the case where a built-in method that parses
text produces it at runtime.

Error **names** are standard; **messages** are not. Message text varies across
runtimes and across versions. This is why code should not branch on message text; it
should look at the name, the type, or a field we add ourselves.

#### Throwing an Error

The `throw` statement can throw any value. What is thrown does not have to be an error
object, but it should be:

```js
try {
  throw "plain text";
} catch (error) {
  console.log(typeof error, error);
  console.log("name:", error.name, "| stack:", typeof error.stack);
}

try {
  throw new Error("error object");
} catch (error) {
  console.log(typeof error, error instanceof Error);
  console.log("name:", error.name, "| stack:", typeof error.stack);
}
```

```
string plain text
name: undefined | stack: undefined
object true
name: Error | stack: string
```

When text is thrown, there is no name and no stack trace; where the error came from is
lost. This course's rule: **every thrown value is an error object.**

When wrapping a lower-layer error, the original error should be preserved. An error
object can carry a **cause** field for this:

```js
function parseTemperature(text) {
  const value = Number(text);
  if (Number.isNaN(value)) {
    throw new Error(`could not convert to number: ${JSON.stringify(text)}`);
  }
  return value;
}

function readRecord(line) {
  const [station, temperatureText] = line.split(";");
  try {
    return { station, temperature: parseTemperature(temperatureText) };
  } catch (error) {
    throw new Error(`record could not be read: ${station}`, { cause: error });
  }
}

console.log(readRecord("A1;21.5"));
try {
  readRecord("B2;abc");
} catch (error) {
  console.log(error.message);
  console.log("cause:", error.cause.message);
}
```

```
{ station: 'A1', temperature: 21.5 }
record could not be read: B2
cause: could not convert to number: "abc"
```

The upper layer added its own context (which record), the lower layer's information
(the cause) was not counted as lost. This is the standard way to enrich error messages
with context.

#### Marking Your Own Error Type

It should be possible to tell apart an error we produced ourselves; otherwise a
`catch` block swallows unexpected programming errors along with the code's own errors.

The simplest way is to add a name and a code field to the error object:

```js
function measurementError(code, message) {
  const error = new Error(message);
  error.name = "MeasurementError";
  error.code = code;
  return error;
}

const e = measurementError("EMPTY_FIELD", "temperature field is empty");
console.log(e.name, "|", e.code, "|", e.message, "|", e instanceof Error);
```

```
MeasurementError | EMPTY_FIELD | temperature field is empty | true
```

Defining named error classes through inheritance — using class syntax — is the next
course's subject. In this course, a name and a code field are enough.

#### Applying It to the Measurement Script

The response to broken data can now be tied to a contract: every line is either
accepted or rejected with a reason; no line silently turns into a wrong value.

```js
const CODE_EMPTY = "EMPTY_FIELD";
const CODE_NOT_NUMBER = "NOT_A_NUMBER";

function measurementError(code, message) {
  const error = new Error(message);
  error.name = "MeasurementError";
  error.code = code;
  return error;
}

function readTemperature(text) {
  if (text.trim() === "") {
    throw measurementError(CODE_EMPTY, "temperature field is empty");
  }
  const value = Number(text);
  if (Number.isNaN(value)) {
    throw measurementError(CODE_NOT_NUMBER, `temperature could not be converted to a number: ${JSON.stringify(text)}`);
  }
  return value;
}

const lines = ["A1;21.5", "A2;", "B1;abc", "B2;0"];
const accepted = [];
const rejected = [];

for (const line of lines) {
  const [station, field] = line.split(";");
  try {
    accepted.push({ station, temperature: readTemperature(field) });
  } catch (error) {
    if (error.name !== "MeasurementError") throw error;
    rejected.push({ station, code: error.code, message: error.message });
  }
}

console.log("accepted:", accepted);
console.log("rejected:", rejected);
```

```
accepted: [
  { station: 'A1', temperature: 21.5 },
  { station: 'B2', temperature: 0 }
]
rejected: [
  {
    station: 'A2',
    code: 'EMPTY_FIELD',
    message: 'temperature field is empty'
  },
  {
    station: 'B1',
    code: 'NOT_A_NUMBER',
    message: 'temperature could not be converted to a number: "abc"'
  }
]
```

Three decisions separate this script from its earlier versions.

First, the `0` value in the `B2` record was accepted. The distinction built in the
Truthiness lesson holds: zero is a valid measurement.

Second, the catch block **recognizes its own error**. The check
`error.name !== "MeasurementError"` rethrows an unexpected error. Without this line, a
`TypeError` arising, say, in the `line.split` call would also be counted as a
"rejected record," and a programming error would look like a data error.

Third, the rejected list carries a reason. How many records were rejected and why can
be read straight from the output.

#### Error or Return Value

Not every unusual situation is an error. The criterion is this: **if the caller can
anticipate the situation, a return value fits; if not, an error fits.**

Asking for the average of an empty list is a foreseeable situation; the `average`
function in the Function Declaration and Expression lesson returned `null` for exactly
this reason. In contrast, finding a letter in a field that should be numeric is a
violation of the data's contract; an error fits.

Both practices sit together in the script above: `readTemperature` throws an error, the
calling loop catches the error and turns it into a data structure — the rejected list.
The boundary is drawn by where the problem can be handled.

Errors are not used for flow control. Throwing an error to exit a loop, skip a value,
or signal a condition is both costly and hard to read; `break`, `continue`, and return
values are what this job is for.

#### Summary

- If an error occurs in the `try` block, the remaining lines do not run; `finally` runs
  in every case and after `return` is evaluated.
- Writing `return` or `throw` inside `finally` overrides the return value and the
  error; the block should contain only cleanup.
- Error names are standard, messages are not; branching is not done on message text.
- Every thrown value should be an error object; otherwise there is no name and no
  stack trace.
- Wrapped errors are preserved with the `cause` field; your own errors are marked with
  a name and a code field, and unrecognized errors are rethrown.
- A foreseeable situation is reported with a return value, a contract violation with an
  error.

#### Next Step

Control flow and functions are complete. In this topic's examples, structures like
`filter`, `map`, `split`, and object spread were used unexplained. The next topic
defines these: arrays' construction and transformation methods, object access
shortcuts, template literals, JSON conversions, and date–time representation. There,
the measurement list will turn into a complete data set that is read from text and
written back to text.

## Built-in Data Structures

Array and object operations, string formatting, serialization and parsing with JSON, and date–time representation.

### Arrays

Source: https://academia.sh/en/courses/javascript-fundamentals/arrays

Ways to create an array and holes, index access, the distinction between mutating and non-mutating methods, the sorting trap, and transformation chains.

The previous topic used methods like `filter`, `map`, and `split` without explaining
them. This topic defines them. The Data Structures course examined an array's internal
layout, cost, and a dynamic array's amortized behavior; here, the concrete behavior and
traps of JavaScript's array object are the subject.

A JavaScript array is not a one-to-one match for the contiguous-memory model from the
Data Structures course. Its `typeof` result is `object`; it is a special object that
carries indices as keys and whose `length` property updates itself. This distinction
directly explains several behaviors.

#### Creation

```js
const direct = [21.5, 19.75, 23];
const sparse = new Array(3);
const singleElement = Array.of(3);
const fromText = Array.from("A1");
const generated = Array.from({ length: 3 }, (_, i) => i * 10);

console.log(direct, direct.length);
console.log(sparse, sparse.length);
console.log(singleElement, fromText);
console.log(generated);
console.log(Array.isArray(direct), typeof direct);
```

```
[ 21.5, 19.75, 23 ] 3
[ <3 empty items> ] 3
[ 3 ] [ 'A', '1' ]
[ 0, 10, 20 ]
true object
```

The second line shows a trap: the constructor called with a single numeric argument
does not turn that value into an element, it produces an empty array of that length.
`Array.of` removes this ambiguity and counts the argument as an element.

`Array.from` does two jobs: it converts an iterable value into an array, and it produces
an array from an object that has a `length` field. Its second argument is a mapping
function called for every element. The underscore name is a writing convention chosen
for an unused parameter; it is not a rule of the language.

The last line is a type check: whether a value is an array cannot be told with
`typeof`; it is tested with `Array.isArray`.

#### Holes

Some indices of an array may hold no element at all. These **holes** are different from
elements carrying the value `undefined`:

```js
const sparse = new Array(3);
console.log(sparse[0], 0 in sparse);
console.log(sparse.map(() => 1));
console.log(Array.from({ length: 3 }, () => 1));
```

```
undefined false
[ <3 empty items> ]
[ 1, 1, 1 ]
```

The first line shows the distinction: reading gave `undefined`, but the index is **not
in** the array. The second line is the consequence: the mapping method skips holes, and
the callback never runs. The third line is the correct way.

Holes also form by writing directly to an index:

```js
const arr = [21.5, 19.75];
arr.length = 1;
console.log(arr);
arr[4] = 99;
console.log(arr, arr.length);
```

```
[ 21.5 ]
[ 21.5, <3 empty items>, 99 ] 5
```

Writing to the `length` field truncates the array. Writing to an index beyond the length
leaves the indices in between empty and grows the `length` value. This course's rule:
**arrays are not left with holes**; adding is done with `push`, generating with
`Array.from`.

The `<3 empty items>` shown in the output is the runtime's inspection format; it is used
to distinguish a hole from the value `undefined`, and it varies across runtimes.

#### Access and Search

```js
const readings = [21.5, 19.75, 23, 18.25];
console.log(readings[0], readings[readings.length - 1], readings.at(-1), readings.at(-2));
console.log(readings[10]);
console.log(readings.indexOf(23), readings.includes(23));

const withNaN = [NaN, 1];
console.log(withNaN.indexOf(NaN), withNaN.includes(NaN));
```

```
21.5 18.25 18.25 23
undefined
2 true
-1 true
```

An out-of-range index does not throw; it gives `undefined`. The `at` method accepts a
negative index and counts from the end.

The last line pays off a hint left in the Equality Comparisons lesson: `indexOf` uses
strict equality, and since `NaN` is not equal to anything, it cannot be found;
`includes` uses a relation close to same-value equality and does find `NaN`. The two
methods do not ask the same question.

#### Mutating and Non-Mutating Methods

Arrays are mutable objects. Methods split into two classes, and not knowing which class
a method belongs to produces silent bugs in shared data.

**Mutating methods** update the array in place: `push`, `pop`, `shift`, `unshift`,
`splice`, `sort`, `reverse`, `fill`.

**Non-mutating methods** return a new array: `slice`, `concat`, `map`, `filter`, `flat`,
`toSorted`, `toReversed`.

```js
const original = [21.5, 19.75, 23];

const copy = original.slice();
copy.push(18.25);
console.log(original, copy);

const reversed = original.toReversed();
console.log(original, reversed);

const inPlace = [...original];
inPlace.reverse();
console.log(inPlace);
```

```
[ 21.5, 19.75, 23 ] [ 21.5, 19.75, 23, 18.25 ]
[ 21.5, 19.75, 23 ] [ 23, 19.75, 21.5 ]
[ 23, 19.75, 21.5 ]
```

A copy taken with spread syntax (`[...original]`) or with `slice` is **shallow**: the
array itself is new, but the elements are the same objects. The shallow-copy/deep-copy
distinction from the Programming Fundamentals course applies here; in an array of
objects, a change made to an element is visible from both arrays.

#### The Sorting Trap

When given no comparison function, the `sort` method converts elements to text and
sorts them in **lexicographic order**:

```js
const numbers = [21.5, 19.75, 100, 3];
console.log([...numbers].sort());
console.log([...numbers].sort((a, b) => a - b));
console.log(numbers.toSorted((a, b) => b - a));
console.log(numbers);
```

```
[ 100, 19.75, 21.5, 3 ]
[ 3, 19.75, 21.5, 100 ]
[ 100, 21.5, 19.75, 3 ]
[ 21.5, 19.75, 100, 3 ]
```

The first line is numerically wrong but correct by the rule: the text `"100"` comes
before the text `"3"`. A comparison function is mandatory for numeric sorting. The
function reports order by returning negative, zero, or positive; `a - b` gives ascending
order, `b - a` descending.

The last two lines also show the mutating/non-mutating distinction: `toSorted` did not
disturb the original array. Since the `sort` calls were made on copies, `numbers` never
changed.

#### Transformation Chains

Most work on the measurement list is written by chaining a few methods:

```js
const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: 19.75 },
  { station: "B1", temperature: 23 },
  { station: "B2", temperature: 18.25 },
];

console.log(records.map((r) => r.temperature));
console.log(records.filter((r) => r.temperature > 20).map((r) => r.station));
console.log(records.reduce((t, r) => t + r.temperature, 0));
console.log(records.find((r) => r.station === "B1"));
console.log(records.findIndex((r) => r.temperature > 22));
console.log(records.some((r) => r.temperature > 22), records.every((r) => r.temperature > 15));
```

```
[ 21.5, 19.75, 23, 18.25 ]
[ 'A1', 'B1' ]
82.5
{ station: 'B1', temperature: 23 }
2
true true
```

These are the map/filter/reduce trio from the Programming Fundamentals course. `find`
gives the first matching element, `findIndex` its index; if not found, they return
`undefined` and `-1` respectively. `some` and `every` produce a boolean and
short-circuit.

Reduce produces more than one summary in a single pass when the accumulator is an
object:

```js
const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: 19.75 },
  { station: "B1", temperature: 23 },
];

const summary = records.reduce(
  (accumulator, record) => ({
    count: accumulator.count + 1,
    total: accumulator.total + record.temperature,
    highest: Math.max(accumulator.highest, record.temperature),
  }),
  { count: 0, total: 0, highest: -Infinity },
);
console.log(summary);
console.log("average:", summary.total / summary.count);
```

```
{ count: 3, total: 64.25, highest: 23 }
average: 21.416666666666668
```

The initial value looks optional, but it is not:

```js
try {
  console.log([].reduce((a, b) => a + b));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
console.log([].reduce((a, b) => a + b, 0));
```

```
TypeError: Reduce of empty array with no initial value
0
```

If no initial value is given, the first element becomes the accumulator, and an empty
array has no such element. The rule: **reduce is always written with an initial
value.**

Nested lists are flattened with `flat` and `flatMap`:

```js
const groups = [[21.5, 19.75], [23], [], [18.25]];
console.log(groups.flat());
console.log(groups.flatMap((g) => g.map((d) => d * 2)));
console.log([1, [2, [3, [4]]]].flat(2));
```

```
[ 21.5, 19.75, 23, 18.25 ]
[ 43, 39.5, 46, 36.5 ]
[ 1, 2, 3, [ 4 ] ]
```

By default, `flat` opens only one level; depth is given as an argument. In the last
line, two levels were opened, and the third level stayed an array.

#### Summary

- The array constructor with a single numeric argument produces an empty array of that
  length; use `Array.of` or a direct literal for an element.
- Holes differ from elements carrying `undefined` and are skipped by mapping methods;
  writing to the `length` field truncates the array or opens a hole.
- An out-of-range index gives `undefined`; `at` accepts a negative index; `indexOf`
  cannot find `NaN`, `includes` can.
- Methods split into mutating and non-mutating; copies are shallow.
- Sorting with no comparison function converts to text and applies lexicographic order.
- Reduce is written with an initial value; without one, it throws on an empty array.

#### Next Step

This lesson used records as objects, but object syntax was never defined. The next
lesson takes up objects: the two forms of property access, shorthand syntax, computed
keys, destructuring, and spread. Grouping the measurement records by station will also
be written there.

### Objects

Source: https://academia.sh/en/courses/javascript-fundamentals/objects

The two forms of property access, key stringification, shorthand and computed syntax, existence checks, destructuring, spread, and reference semantics.

The previous lesson used measurement records as objects, but the syntax was never
defined. This lesson takes up objects: creation, access, separating, and combining.

An object is an ordered set of key–value pairs. Keys are strings or symbols; values can
be of any type. The prototype chain, property descriptors, and class syntax are the next
course's subject; this lesson covers plain data objects.

#### Creation and Access

```js
const field = "temperature";
const station = "A1";
const temperature = 21.5;

const record = {
  station,
  temperature,
  [field + "Unit"]: "C",
  summary() {
    return `${this.station}: ${this.temperature}`;
  },
};

console.log(record);
console.log(record.station, record["temperature"], record[field]);
console.log(record.summary());
```

```
{
  station: 'A1',
  temperature: 21.5,
  temperatureUnit: 'C',
  summary: [Function: summary]
}
A1 21.5 21.5
A1: 21.5
```

Three writing shortcuts were used together. **Shorthand property** (`station`) takes its
value from a variable of the same name. **Computed key** (`[field + "Unit"]`) produces
the key at runtime. **Shorthand method** (`summary()`) writes a function property
without the `function` keyword; by the rule from the Arrow Functions lesson, methods are
not written in arrow form.

There are two notations for access. **Dot notation** requires a fixed, valid name. **Bracket
notation** accepts any expression; it is required when the key is determined at
runtime. In the output's second line, all three access forms gave the same value.

The object being written across multiple lines is the runtime's inspection format; once
an object passes a certain length it is split across lines, and function properties are
shown as `[Function: name]`. This display is not defined in the standard.

#### Keys Are Strings

Every key except a symbol is converted to a string:

```js
const obj = {};
obj[1] = "number one";
obj["1"] = "text one";
obj[true] = "boolean";
console.log(obj);
console.log(Object.keys(obj));
console.log(typeof Object.keys(obj)[0]);
```

```
{ '1': 'text one', true: 'boolean' }
[ '1', 'true' ]
string
```

`1` and `"1"` are the same key; the second assignment overwrote the first. This is why
the indices that come out of `for...in` in the Loops lesson are strings — an array is
also an object and carries its indices as keys.

If a mapping with numeric keys is needed, the `Map` structure preserves the distinction;
that is outside this course's scope.

#### Existence Checks

A field being **absent** and a field carrying the value `undefined` are different
situations:

```js
const record = { station: "A1", humidity: undefined };
console.log(record.humidity, "humidity" in record, Object.hasOwn(record, "humidity"));
console.log(record.pressure, "pressure" in record, Object.hasOwn(record, "pressure"));
delete record.humidity;
console.log("after delete:", "humidity" in record, record);
```

```
undefined true true
undefined false false
after delete: false { station: 'A1' }
```

Reading gave `undefined` in both cases; only the existence check showed the difference.
The `in` operator also counts inherited properties; `Object.hasOwn` looks only at the
object's own properties. This course uses the latter.

`delete` removes the property entirely. Assigning `undefined` leaves the property in
place and only changes its value.

#### Key–Value Conversions

Moving back and forth between an object and an array is the basis of writing
transformations:

```js
const record = { station: "A1", temperature: 21.5, humidity: 48 };
console.log(Object.keys(record));
console.log(Object.values(record));
console.log(Object.entries(record));

const pairs = [["station", "B1"], ["temperature", 23]];
console.log(Object.fromEntries(pairs));
```

```
[ 'station', 'temperature', 'humidity' ]
[ 'A1', 21.5, 48 ]
[ [ 'station', 'A1' ], [ 'temperature', 21.5 ], [ 'humidity', 48 ] ]
{ station: 'B1', temperature: 23 }
```

`Object.entries` and `Object.fromEntries` are each other's inverse. This pair makes it
possible to use array methods on an object: the object is converted to an array,
filtered or mapped, then converted back to an object.

#### Destructuring

**Destructuring** separates an object's or array's fields into separate names:

```js
const record = { station: "A1", temperature: 21.5, location: { elevation: 120 } };

const { station, temperature: degrees, humidity = null } = record;
console.log(station, degrees, humidity);

const { location: { elevation } } = record;
console.log(elevation);

const { station: name, ...rest } = record;
console.log(name, rest);

const [first, , third = 0] = [21.5, 19.75];
console.log(first, third);
```

```
A1 21.5 null
120
A1 { temperature: 21.5, location: { elevation: 120 } }
21.5 0
```

Four abilities were used: **renaming** (`temperature: degrees`), **default**
(`humidity = null`), **nested destructuring**, and **gathering the remaining fields**.
The default's criterion is the same as in the Parameters lesson: it engages only if the
value is `undefined`.

In array destructuring, order is by position; an element to skip is left blank. This is
the `[index, value]` syntax used with `entries` in the Loops lesson.

#### Spread and Combining

Spread syntax copies an object's own enumerable properties into another object literal:

```js
const base = { station: "A1", temperature: 21.5, unit: "C" };
const update = { temperature: 22.75 };

console.log({ ...base, ...update });
console.log({ ...update, ...base });
console.log(base);
```

```
{ station: 'A1', temperature: 22.75, unit: 'C' }
{ temperature: 21.5, station: 'A1', unit: 'C' }
{ station: 'A1', temperature: 21.5, unit: 'C' }
```

**Order determines the result:** a property with the same name written later overwrites
the earlier one. In the second line, the order is reversed, so the update is lost. The
third line shows the original object did not change — spread produces a new object.

The copy is **shallow**:

```js
const deep = { station: "A1", location: { elevation: 120 } };
const copy = { ...deep };
copy.location.elevation = 999;
console.log(deep.location.elevation);
```

```
999
```

The inner object was not copied; the two objects share the same `location` object. Deep
copying is outside this course's scope and will be taken up in the Immutability
Techniques lesson.

#### Reference Semantics

Objects are bound to names by reference. The rule established in the Passing by Value
and by Reference lesson of the Programming Fundamentals course applies here exactly:

```js
const record = { station: "A1", temperature: 21.5 };
const alias = record;
alias.temperature = 22.75;
console.log(record.temperature);

function mutates(r) { r.temperature = 0; }
function doesNotMutate(r) { return { ...r, temperature: 0 }; }

mutates(record);
console.log(record.temperature);
console.log(doesNotMutate(record), record.temperature);
```

```
22.75
0
{ station: 'A1', temperature: 0 } 0
```

The `mutates` function changed the caller's data. `doesNotMutate` produced a new object
and left the original record as it was — the `0` in the last line is left over from the
`mutates` call.

This course's rule: **transformation functions do not change their inputs; they return
a new object.** If a side-effecting update is actually needed, the function's name
should say so.

#### Applying It to the Measurement Script

Grouping records by station is the object used as a map:

```js
const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: 19.75 },
  { station: "A1", temperature: 22.75 },
  { station: "B1", temperature: 23 },
];

const groups = {};
for (const record of records) {
  const key = record.station;
  if (!Object.hasOwn(groups, key)) {
    groups[key] = [];
  }
  groups[key].push(record.temperature);
}
console.log(groups);

const averages = Object.fromEntries(
  Object.entries(groups).map(([name, values]) => [
    name,
    values.reduce((t, v) => t + v, 0) / values.length,
  ]),
);
console.log(averages);
```

```
{ A1: [ 21.5, 22.75 ], A2: [ 19.75 ], B1: [ 23 ] }
{ A1: 22.125, A2: 19.75, B1: 23 }
```

Both steps use this lesson's tools. Grouping writes to the object with a computed key
and does the existence check with `Object.hasOwn`. The average computation converts the
object to pairs, applies a map, and converts it back to an object. The callback's
parameter was destructured with the `[name, values]` syntax.

#### Summary

- Shorthand property, computed key, and shorthand method are the three shortcuts of
  object syntax.
- Keys other than symbols are converted to strings; `1` and `"1"` are the same key.
- A field being absent and carrying `undefined` are separate situations; the
  distinction is seen with `Object.hasOwn`.
- `Object.entries` and `Object.fromEntries` are each other's inverse and make it
  possible to use array methods on an object.
- Destructuring offers renaming, defaults, nested destructuring, and gathering
  remaining fields.
- In spread, a property written later overwrites the earlier same-named one; the copy
  is shallow, and objects are bound to names by reference.

#### Next Step

Up to this lesson, output was produced with `console.log` arguments or concatenation.
The next lesson takes up strings: formatting with template literals, search and split
methods, the practical consequences of the code-unit/grapheme distinction, and number
formatting. The measurement report's readable text will be written there.

### Strings

Source: https://academia.sh/en/courses/javascript-fundamentals/strings

Template literals, slicing and search methods, locale-dependent case conversion, the code-unit/grapheme distinction, and number formatting.

Up to this lesson, output was produced with `console.log` arguments. Writing report
text requires strings' own tools. This lesson defines those tools and finishes a subject
left open in the Primitive Types lesson — the practical consequence of a string being a
sequence of code units.

Strings are immutable; every method here produces a new string and does not change the
original value.

#### Template Literals

Strings written with backticks carry three extra abilities: expression interpolation,
being multi-line, and different escaping rules.

```js
const record = { station: "A1", temperature: 21.5 };
const threshold = 20;

const template = `${record.station}: ${record.temperature} C (${record.temperature > threshold ? "above threshold" : "below threshold"})`;
console.log(template);

const multiLine = `station: ${record.station}
temperature: ${record.temperature}`;
console.log(multiLine);
console.log(`quote: "A1", backtick: \`A1\`, dollar: \${record}`);
```

```
A1: 21.5 C (above threshold)
station: A1
temperature: 21.5
quote: "A1", backtick: `A1`, dollar: ${record}
```

What gets interpolated is an **expression**; conditions are written with the ternary
operator from the Conditions and Selection lesson. Every expression is converted to
text — the ToString procedure from the Type Conversion and Coercion lesson applies — so
interpolating an object produces `[object Object]`.

A line break is written directly; no escape sequence is needed. A backslash is used to
escape the backtick and the dollar–brace pair.

In this course, text concatenation is written with template literals. The reason is to
avoid the `+` operator's two-faced behavior seen in the Type Conversion and Coercion
lesson: with a template, the syntax itself makes it clear the result is text.

#### Slicing and Splitting

```js
const line = "A1;21.5;48";
console.log(line.length, line[0], line.at(-1));
console.log(line.slice(0, 2), line.slice(-2));
console.log(line.slice(4, 2), line.substring(4, 2));
console.log(line.split(";"));
console.log(line.split(";", 2));
console.log(["A1", "21.5"].join(";"));
```

```
10 A 8
A1 48
 ;2
[ 'A1', '21.5', '48' ]
[ 'A1', '21.5' ]
A1;21.5
```

The third line shows the difference between the two slicing methods. `slice` accepts a
negative index and gives an **empty string** when the start is greater than the end.
`substring` counts negative indices as zero and **swaps** its arguments if needed; this
is why it produced `;2`. The two results being written on the same line, separated by a
space, shows the first is empty. This course uses `slice`: its behavior matches array
`slice` and produces no surprises.

`split` splits by a separator, and a second argument limits the result count. The limit
does not append the remaining text to the last piece — it **discards** it, a common
source of unexpected data loss when parsing a file.

#### Search and Replace

```js
const line = "station=A1;field=temperature;field=humidity";
console.log(line.includes("field"), line.indexOf("field"), line.lastIndexOf("field"));
console.log(line.startsWith("station"), line.endsWith("humidity"));
console.log(line.replace("field", "FIELD"));
console.log(line.replaceAll("field", "FIELD"));
console.log("  A1  ".trim() + "|", "  A1  ".trimStart() + "|");
console.log("A1".padStart(5, "0"), "A1".padEnd(5, "."), "-".repeat(10));
```

```
true 11 29
true true
station=A1;FIELD=temperature;field=humidity
station=A1;FIELD=temperature;FIELD=humidity
A1| A1  |
000A1 A1... ----------
```

When called with a text argument, `replace` changes **only the first match**;
`replaceAll` is used for all of them. This is a distinction that produces silent bugs.

Padding methods are the basis of producing aligned output, and a padding character can
be given. Trimming methods drop leading and trailing whitespace; which characters count
as whitespace is defined in the standard.

#### Locale-Dependent Case Conversion

Case conversion is language-dependent, and the default methods apply a
language-independent rule:

```js
const name = "station";
console.log(name.toUpperCase());
console.log(name.toLocaleUpperCase("tr"));
console.log("ISTANBUL".toLowerCase());
console.log("ISTANBUL".toLocaleLowerCase("tr"));
```

```
STATION
STATİON
istanbul
ıstanbul
```

The difference shows up in languages where dotted and dotless **i** are separate
letters. The default conversion turns `i` into the dotless capital; the locale-aware
conversion produces the dotted capital.

Two rules follow from this. If text is being converted to show to a user, the locale
should be specified. In contrast, a conversion done **for comparison** should not use
the locale-aware form: the same text gives different results in different locales, and
the comparison becomes inconsistent. Key comparisons are done with the
language-independent form.

#### Code Unit and Grapheme

The Primitive Types lesson said a string is a sequence of code units. The consequence
shows up in slicing:

```js
const text = "A1😀";
console.log(text.length);
console.log([...text].length);
console.log([...text]);
console.log(Array.from(text).length);
```

```
4
3
[ 'A', '1', '😀' ]
3
```

`length` gave four because the last character consists of two code units — the
surrogate pair from the How Computers Work course. Spread and `Array.from` walk the
string by **code points** and give three elements.

Cutting at a code-unit boundary corrupts the text:

```js
const text = "A1😀";
console.log(text.slice(0, 3).length);
console.log([...text].slice(0, 3).join(""));
```

```
3
A1😀
```

The first slice took only the first of the double code unit; the result is a code unit
with no meaning on its own, and it looks broken when displayed. The second way did not
split the character, because it operates on the sequence of code points.

A **grapheme** — the unit a user counts as a single character — can be larger than a
code point too; combining marks are an example:

```js
const combined = "é";
const single = "é";
console.log(combined.length, single.length, combined === single);
console.log(combined.normalize("NFC") === single);
```

```
2 1 false
true
```

The two strings look identical but are different sequences of code points; comparison
gives the wrong answer. Normalization brings both to a common form. Code that compares
text should normalize its inputs first.

#### Number Formatting

```js
console.log((21.456).toFixed(2));
console.log((0.1 + 0.2).toFixed(2));
console.log((1.005).toFixed(2));
console.log((21.5).toString(2));
console.log(String(1e21), String(1e-7));
```

```
21.46
0.30
1.00
10101.1
1e+21 1e-7
```

`toFixed` rounds to the given digit count and returns **text**. The third line looks
like a surprise, but it is a consequence of the model from the Primitive Types lesson:
the value `1.005` cannot be represented exactly in floating-point, and the stored value
is slightly less than `1.005`, so it rounds down. The rounding error is not in the
formatting, it is in **the number itself**.

`toString` takes a base argument; the representation from the Binary Number System
lesson of the How Computers Work course is produced this way. The last line shows very
large and very small numbers switching to exponential notation; the thresholds are
defined in the standard.

#### Writing the Measurement Report

When this lesson's tools combine, an aligned report comes out:

```js
const records = [
  { station: "A1", temperature: 21.5, humidity: 48 },
  { station: "A2", temperature: 19.75, humidity: 55 },
  { station: "B1", temperature: 23, humidity: null },
];

const header = `${"station".padEnd(10)}${"temperature".padStart(12)}${"humidity".padStart(9)}`;
const line = "-".repeat(header.length);

const rows = records.map((r) => {
  const humidityText = r.humidity == null ? "-" : String(r.humidity);
  return `${r.station.padEnd(10)}${r.temperature.toFixed(2).padStart(12)}${humidityText.padStart(9)}`;
});

console.log([header, line, ...rows].join("\n"));
```

```
station    temperature humidity
-------------------------------
A1               21.50       48
A2               19.75       55
B1               23.00        -
```

The missing humidity field was shown with `-`; the `== null` check is the exception
from the Equality Comparisons lesson. `toFixed(2)` brought every temperature to the
same digit count — the value `23` was written as `23.00`. The rows were joined into a
single text with `join` and printed in one call.

Alignment is based on code-unit count. If the report contains characters outside the
Latin set, the padding methods balance code-unit count, not visual width, and the
columns can drift.

#### Summary

- Template literals interpolate expressions, are multi-line, and are this course's
  only form of text concatenation.
- `slice` accepts a negative index and gives an empty string on a reversed range;
  `substring` swaps its arguments.
- `replace` changes only the first match; `replaceAll` is needed for all of them.
- Case conversion is locale-dependent; use the locale-aware form for display, the
  language-independent form for comparison.
- `length` counts code units; use spread or `Array.from` for code-point-based
  operations, and normalize before comparing.
- `toFixed` returns text, and rounding is affected by the number's floating-point
  representation.

#### Next Step

The report produced readable text, but not a format the machine can read back. The next
lesson takes up JSON: serialization and parsing, values the format does not support,
the error a circular reference produces, and callbacks that adapt the conversion. The
serialization problem left open in the BigInt lesson will also be finished there.

### JSON

Source: https://academia.sh/en/courses/javascript-fundamentals/json

Serialization and parsing, values the format does not support, circular references, callbacks that adapt the conversion, and the limit of number precision.

The previous lesson produced readable report text. That text was for humans; it is not
a format the machine can read back. This lesson takes up JSON: converting data to text
and reading it back.

JSON is a data format derived from JavaScript syntax but independent of it. This origin
makes the two look close to each other while not fully overlapping. Knowing the
distinction prevents silent data loss.

#### Serialization and Parsing

```js
const record = { station: "A1", temperature: 21.5, humidity: null, validated: false };
const text = JSON.stringify(record);
console.log(text);
console.log(typeof text);
console.log(JSON.stringify(record, null, 2));
const back = JSON.parse(text);
console.log(back, back.temperature + 1);
console.log(record === back);
```

```
{"station":"A1","temperature":21.5,"humidity":null,"validated":false}
string
{
  "station": "A1",
  "temperature": 21.5,
  "humidity": null,
  "validated": false
}
{ station: 'A1', temperature: 21.5, humidity: null, validated: false } 22.5
false
```

`JSON.stringify` converts a value to text; the third argument is the indent amount and
produces readable output. `JSON.parse` produces a new value from text.

The last line matters: the result of the conversion is a **new object**. By the
identity rule from the Equality Comparisons lesson, it is not equal to the original
object. This is also a way of taking a deep copy — but only for values the format
supports.

The third block in the output is JSON text: keys are double-quoted. The object display
at the end is the runtime's inspection format; the unquoted keys and single-quoted text
come from there. The two are different things.

#### Values the Format Does Not Support

JSON's set of values is narrower than the language's set of values. Unsupported values
are handled silently:

```js
const record = {
  station: "A1",
  missing: undefined,
  fn: () => 1,
  symbol: Symbol("x"),
  invalid: NaN,
  infinite: Infinity,
};
console.log(JSON.stringify(record));
console.log(JSON.stringify([undefined, () => 1, NaN]));
```

```
{"station":"A1","invalid":null,"infinite":null}
[null,null,null]
```

Two different behaviors are at play. On object properties, `undefined`, function, and
symbol values are **skipped entirely**: the key never shows up in the output. On array
elements, the same values are converted to `null`, because the array's length has to be
preserved.

`NaN` and `Infinity` become `null` in both cases. This is because the format does not
recognize these values at all. The result is silent: an invalid measurement turns, once
parsed, into `null` — which means "deliberately empty." The `null`–`undefined`
distinction established in the Primitive Types lesson does not survive the conversion.

In two cases, an error is thrown:

```js
const a = { station: "A1" };
a.itself = a;
try {
  JSON.stringify(a);
} catch (error) {
  console.log(error.name);
}
try {
  JSON.stringify({ counter: 1n });
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError
TypeError: Do not know how to serialize a BigInt
```

A **circular reference** — an object directly or indirectly containing itself — would
produce an infinite text. Big integers are rejected because, as discussed in the BigInt
lesson, the format has no precision guarantee for them. The first error's message
varies by runtime, so only its name was printed.

#### Adapting the Conversion

Both methods can take a callback. In serialization, the callback is called for every
key–value pair, and the value it returns gets written; returning `undefined` skips the
field.

```js
const record = {
  station: "A1",
  timeNs: 1700000000123456789n,
  temperature: 21.5,
  secret: "do not write",
};

const text = JSON.stringify(record, (key, value) => {
  if (key === "secret") return undefined;
  if (typeof value === "bigint") return value.toString();
  return value;
});
console.log(text);

const back = JSON.parse(text, (key, value) =>
  key === "timeNs" ? BigInt(value) : value,
);
console.log(back.timeNs === record.timeNs, typeof back.timeNs);
```

```
{"station":"A1","timeNs":"1700000000123456789","temperature":21.5}
true bigint
```

The problem left open in the BigInt lesson is finished here: the value was written as
text, converted back on reading, and preserved down to its last digit. The `secret`
field did not enter the output.

The two callbacks are **symmetric** and should be designed together: whichever field
the writing side converted to which format, the reading side has to convert that same
field back. Otherwise the type changes silently.

Instead of a callback, serialization can also be given a list of fields:

```js
const record = { station: "A1", temperature: 21.5, humidity: 48 };
console.log(JSON.stringify(record, ["station", "temperature"]));
```

```
{"station":"A1","temperature":21.5}
```

#### The Limit of Number Precision

Numbers in JSON text are converted to the language's number type when parsed. The safe
integer range from the Primitive Types lesson applies here too:

```js
console.log(JSON.parse("9007199254740993"));
console.log(JSON.parse("0.1") + JSON.parse("0.2"));
console.log(JSON.stringify(0.1 + 0.2));
```

```
9007199254740992
0.30000000000000004
0.30000000000000004
```

The first line is a silent loss: an integer correctly written in the text landed on a
different value once read. The format could carry this value; what loses it is not the
parser, it is the **target type**. For lossless transfer, the value is written as
text — this is the callback pattern above.

The last two lines show floating-point representation surviving the conversion: the
written text returns to the same bit pattern.

#### Parsing Is Strict

JSON's grammar is narrower than JavaScript's object syntax:

```js
const broken = ['{"a":1,}', "{'a':1}", "{a:1}", ""];
for (const m of broken) {
  try {
    JSON.parse(m);
    console.log(JSON.stringify(m), "-> valid");
  } catch (error) {
    console.log(JSON.stringify(m), "->", error.name);
  }
}
```

```
"{\"a\":1,}" -> SyntaxError
"{'a':1}" -> SyntaxError
"{a:1}" -> SyntaxError
"" -> SyntaxError
```

All four are valid or harmless writings in JavaScript source; not in JSON. A trailing
comma, single quotes, an unquoted key, and empty text are all rejected. This strictness
is what lets the format be read the same way across different languages.

The error name is `SyntaxError`; the message text varies by parser. The rule from the
Error Handling lesson applies here: branching is done on the name.

#### Applying It to the Measurement Script

The course's thread can now write a full round trip: read from text, transform, write
back to text.

```js
const incoming = `[
  {"station":"A1","temperature":21.5,"humidity":48},
  {"station":"A2","temperature":19.75,"humidity":null},
  {"station":"B1","temperature":23,"humidity":41}
]`;

const records = JSON.parse(incoming);
const enriched = records.map((r) => ({
  ...r,
  deviation: Number((r.temperature - 20).toFixed(2)),
  hasHumidity: r.humidity != null,
}));

console.log(JSON.stringify(enriched, null, 2));
```

```
[
  {
    "station": "A1",
    "temperature": 21.5,
    "humidity": 48,
    "deviation": 1.5,
    "hasHumidity": true
  },
  {
    "station": "A2",
    "temperature": 19.75,
    "humidity": null,
    "deviation": -0.25,
    "hasHumidity": false
  },
  {
    "station": "B1",
    "temperature": 23,
    "humidity": 41,
    "deviation": 3,
    "hasHumidity": true
  }
]
```

Three decisions were made together. The transformation produced new objects; by the
rule from the Objects lesson, the input was not changed. The deviation value was
rounded with `toFixed` and converted back to a number with `Number` — otherwise it
would have been written to JSON as text. The `humidity` field's presence was tested
with a `!= null` check, so a `null` value was also counted as missing.

If the text being parsed comes from outside, parsing should be wrapped in a
`try`/`catch`; broken input should not stop the program.

#### Summary

- `JSON.stringify` converts a value to text, `JSON.parse` converts text to a new value;
  the result of the conversion is not equal to the original object.
- On object properties, `undefined`, function, and symbol are skipped; on array
  elements, they are converted to `null`. `NaN` and `Infinity` become `null` in every
  case.
- Circular references and big integers throw during serialization.
- Callbacks make lossless transfer of unsupported types possible; the writing and
  reading sides are written symmetrically.
- JSON numbers are converted to the language's number type; integers above the safe
  range are silently rounded.
- JSON's grammar is strict: a trailing comma, single quotes, and an unquoted key are
  all rejected.

#### Next Step

The measurement records are still timeless: it is not known when any given reading was
taken. The next lesson takes up date and time representation — the timestamp concept,
the timezone/local-time distinction, parsing traps, and formatting. The course's
closing will happen there too.

### Date and Time

Source: https://academia.sh/en/courses/javascript-fundamentals/date-and-time

The timestamp model, the distinction between local time and coordinated universal time, parsing traps, date arithmetic, and locale-aware formatting.

Up to this lesson, the measurement records were timeless: it was not known when any
given reading was taken. This lesson adds time representation and closes the course.

Date–time is the language's most misused area. The reason is a single confusion: an
**instant** and a **representation** of that instant are not the same thing. This
lesson establishes the distinction and derives the traps from it.

The output of the examples below depends on the timezone of the machine they run on. To
make them reproducible, all of them were run in universal time (`TZ=UTC`). If your
machine's timezone is different, the lines that depend on local time will change; the
relevant sections say which lines those are.

#### The Timestamp

The only data a date object carries is the number of milliseconds elapsed since a fixed
starting instant. This number is the **timestamp**, and it is independent of timezone.

```js
const stamp = 1709287200000;
const t = new Date(stamp);
console.log(t.getTime());
console.log(t.toISOString());
console.log(Number(t) === stamp);
```

```
1709287200000
2024-03-01T10:00:00.000Z
true
```

When the same script is run in a different timezone, these three lines do not change.
The stamp is an **instant**; the ISO representation is also fixed, since it is written
relative to coordinated universal time.

In a numeric context, the object converts to its stamp; this is the basis of date
arithmetic.

#### Local Time and Universal Time

Reading methods come in two families. Those with `UTC` in their name give a value
relative to universal time; those without give a value relative to the machine's local
timezone:

```js
const t = new Date(1709287200000);
console.log("ISO        :", t.toISOString());
console.log("local year :", t.getFullYear(), "month:", t.getMonth(), "day:", t.getDate());
console.log("local hour :", t.getHours());
console.log("UTC hour   :", t.getUTCHours());
console.log("offset(min):", t.getTimezoneOffset());
```

With `TZ=UTC`:

```
ISO        : 2024-03-01T10:00:00.000Z
local year : 2024 month: 2 day: 1
local hour : 10
UTC hour   : 10
offset(min): 0
```

With `TZ=Europe/Istanbul`, a zone three hours ahead of universal time, the same script
gives `13` on the local-hour line and `-180` on the offset line; the other lines do not
change.

Two traps meet here. First, **month numbering starts at zero**: March gave the value
`2`. Day and year, on the other hand, start at one. This inconsistency comes from the
language's earliest form and has been kept for backward compatibility.

Second, the **sign of the timezone offset is reversed**: a zone three hours ahead of
universal time gives `-180`. The value is the number of minutes that has to be
**added** to go from local time to universal time.

#### The Parsing Trap

Producing a date from text assumes a different timezone depending on the format:

```js
const formats = [
  "2024-03-01T10:00:00Z",
  "2024-03-01T10:00:00",
  "2024-03-01",
  "2024-03-01T10:00:00+03:00",
];
for (const m of formats) {
  console.log(m.padEnd(26), new Date(m).toISOString());
}
```

With `TZ=UTC`:

```
2024-03-01T10:00:00Z       2024-03-01T10:00:00.000Z
2024-03-01T10:00:00        2024-03-01T10:00:00.000Z
2024-03-01                 2024-03-01T00:00:00.000Z
2024-03-01T10:00:00+03:00  2024-03-01T07:00:00.000Z
```

The four lines show four different rules.

In the first and fourth lines, the timezone is written into the text; the result is
independent of the machine. In the second line, no timezone is written, and **local
time** is assumed; the same text corresponds to a different instant on a different
machine. Here the machine's local timezone is universal time, so the second line looks
the same as the first, but that is a coincidence: with `TZ=Europe/Istanbul` the same text
gives `2024-03-01T07:00:00.000Z`. In the third line, only a date is given, and **universal time** is assumed —
the exact opposite of the second line.

The rule follows from this: **every time text meant to be parsed should carry timezone
information.** If it does not, which rule applies depends on the format, and two
formats behave as opposites.

Text that cannot be parsed does not throw; it produces an invalid date:

```js
const t = new Date("broken");
console.log(String(t));
console.log(Number.isNaN(t.getTime()));
try {
  t.toISOString();
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
Invalid Date
true
RangeError: Invalid time value
```

Invalidity is detected by the stamp being `NaN`. Used without a check, the error only
surfaces at the formatting stage — far from its source.

#### Date Arithmetic

Arithmetic is done through the timestamp; subtracting objects gives the stamp
difference:

```js
const start = new Date("2024-03-01T10:00:00Z");
const end = new Date("2024-03-01T10:00:00.800Z");
console.log(end.getTime() - start.getTime(), "ms");

const oneDayLater = new Date(start.getTime() + 24 * 60 * 60 * 1000);
console.log(oneDayLater.toISOString());
```

```
800 ms
2024-03-02T10:00:00.000Z
```

Adding a fixed number of milliseconds adds **duration**, not a **calendar day**. The
two usually coincide; in zones with daylight-saving transitions, a day's duration may
not equal a calendar day. Calendar operations use setter methods:

```js
const start = new Date("2024-03-01T10:00:00Z");
const copy = new Date(start.getTime());
copy.setUTCDate(copy.getUTCDate() + 1);
console.log(copy.toISOString(), start.toISOString());
```

```
2024-03-02T10:00:00.000Z 2024-03-01T10:00:00.000Z
```

Date objects are **mutable**: setter methods update the object in place. This is why a
copy was taken first; without it, `start` would have changed too. The rule from the
Objects lesson applies here too — a transformation does not change its input.

#### Formatting

Text meant to be shown to a user is produced by giving the locale and timezone
explicitly:

```js
const t = new Date("2024-03-01T10:00:00Z");
const format = new Intl.DateTimeFormat("en-US", {
  dateStyle: "short",
  timeStyle: "medium",
  timeZone: "Europe/Istanbul",
});
console.log(format.format(t));

const utcFormat = new Intl.DateTimeFormat("en-US", {
  dateStyle: "short",
  timeStyle: "medium",
  timeZone: "UTC",
});
console.log(utcFormat.format(t));
console.log(t.toISOString().slice(0, 19).replace("T", " "));
```

```
3/1/24, 1:00:00 PM
3/1/24, 10:00:00 AM
2024-03-01 10:00:00
```

The same instant was shown with two different times; both are correct. The difference
is which timezone it is being looked at from.

The details of the text the formatter produces — separators, digit count, letter
case — depend on locale data and can vary across runtimes. This is why formatted text is
**for display**; it is not stored to be parsed or compared. The format to store is the
ISO representation.

#### Together with JSON

Date objects convert to ISO text on serialization, but they do not come back as objects
when read:

```js
const record = { station: "A1", time: new Date("2024-03-01T10:00:00Z") };
const text = JSON.stringify(record);
console.log(text);
const back = JSON.parse(text);
console.log(typeof back.time, back.time);

const liveBack = JSON.parse(text, (key, value) =>
  key === "time" ? new Date(value) : value,
);
console.log(liveBack.time instanceof Date);
```

```
{"station":"A1","time":"2024-03-01T10:00:00.000Z"}
string 2024-03-01T10:00:00.000Z
true
```

The conversion is one-way **asymmetric**: writing happens on its own, reading requires
a callback. The symmetry rule from the JSON lesson applies here too.

#### The Measurement Script's Final Form

The course's thread completes: a measurement list read from text, sorted by time, and
formatted.

```js
const incoming = [
  { station: "A1", time: "2024-03-01T10:00:00Z", temperature: 21.5 },
  { station: "A1", time: "2024-03-01T10:15:00Z", temperature: 22.75 },
  { station: "B1", time: "2024-03-01T10:05:00Z", temperature: 23 },
];

const format = new Intl.DateTimeFormat("en-US", {
  timeStyle: "short",
  timeZone: "Europe/Istanbul",
});

const records = incoming
  .map((r) => ({ ...r, time: new Date(r.time) }))
  .filter((r) => !Number.isNaN(r.time.getTime()))
  .sort((a, b) => a.time - b.time);

for (const r of records) {
  console.log(
    `${r.station.padEnd(4)} ${format.format(r.time).padStart(9)} ${r.temperature.toFixed(2).padStart(7)}`,
  );
}

const span = records[records.length - 1].time - records[0].time;
console.log("span covered (min):", span / 60000);
```

```
A1     1:00 PM   21.50
B1     1:05 PM   23.00
A1     1:15 PM   22.75
span covered (min): 15
```

Every step of the chain comes from one of the course's lessons. New objects were
produced with spread (Objects), invalid dates were filtered out (Truthy and Falsy
Values, and this lesson), sorting was written with a comparison function (Arrays), the
text was aligned with a template literal (Strings), and the span was computed with
stamp arithmetic.

The sort comparison was written as `a.time - b.time`; since the subtraction operator
converts date objects to their stamps, the result is a number. This is one of the rare
places where the rule from the Type Conversion and Coercion lesson is put to **useful**
work.

#### Summary

- A date object carries a single piece of data: a timestamp independent of timezone.
  Representation, on the other hand, depends on timezone.
- Month numbering starts at zero; day and year start at one. The sign of the timezone
  offset is reversed.
- Time text with no timezone written is assumed local; text with only a date is
  assumed universal time; text meant to be parsed should carry timezone information.
- An invalid date does not throw; its stamp becomes `NaN`, and it only produces an
  error at formatting time.
- Arithmetic is done through the stamp; since setter methods mutate the object in
  place, a copy is taken first.
- Formatted text is for display; storage and comparison are done with the ISO
  representation.

#### Course Wrap-Up

This course established JavaScript's value model and basic structures. Four topics
completed one another: how the language is executed and how names are bound; which
values exist and how they are converted and compared; how control flow and functions
are written; how the built-in data structures are used.

One single example ran through the whole course. The measurement list started as an
array of four numbers; it turned into a data set read from text, validated, grouped,
formatted, and written back as JSON. Every lesson added one capability to that list,
and also showed the limit of the capability it added.

Three areas were deliberately left out. First, the prototype model behind objects: how
properties are looked up, what class syntax corresponds to on that model, and the
rules of `this` binding. Second, using closures to carry state. Third, asynchronous
execution.

The first two are the subject of the Objects and Functions in JavaScript course. That
course answers questions deferred several times in this one: where an array's `map`
method comes from, what an arrow function's missing `prototype` property prevents,
what `this` binds to in object methods, and how a function returned by another
function remembers an outer variable. The value model this course established is the
ground that discussion stands on.

The third comes later. In this course, data was always already in hand; had it come
from a network request or a file read, waiting for the result to be ready would have
been necessary, and the program would not have stopped in the meantime. The event
loop, promises, and asynchronous waiting are the subject of a later course in the
curriculum. The next course, Objects and Functions in JavaScript, opens up the
structure behind values first.

