---
title: 'Objects and Functions in JavaScript'
source: 'https://academia.sh/en/courses/javascript-object-model'
language: en
updated: '2026-08-17T04:41:35+00:00'
license: 'CC BY-SA 4.0'
lessons: 17
outcomes:
    - 'Explains the prototype chain and what class syntax corresponds to on it'
    - 'Distinguishes the four binding rules of the this context'
    - 'Writes state-carrying functions using closures'
---

# Objects and Functions in JavaScript

The prototype chain and the property lookup order, what class syntax corresponds to on that chain, the this binding rules, closures, and the advanced collections.

## Prototype Model

The prototype link and the property lookup chain, object-to-object inheritance patterns, class syntax's counterpart on the chain, property descriptors, and immutability techniques.

### Object Prototype

Source: https://academia.sh/en/courses/javascript-object-model/object-prototype

The prototype link every object carries, the order in which properties are searched through this link, the distinction between an own property and an inherited property, and where the chain ends.

The JavaScript Fundamentals course introduced the object as a key–value mapping:
properties are written, read, deleted. This definition is enough for everyday use, but it
leaves one question unanswered. Create an empty object and read its `toString` property
and you get not `undefined` but a function. No one wrote that function there.

The answer stands at the center of the language's object model: every object carries, in
addition to its own properties, **a link to another object**. If the property you are
looking for is not on the object itself, the search continues through this link. This
lesson builds that link; the rest of the course — including class syntax — sits on top of
it.

#### Two Copies of the Same Behavior

Throughout this course we will work with a single concrete example: a **measurement
record** read from a sensor. A record carries three pieces of data (sensor id, measured
value, moment of measurement) and offers at least one behavior (printing itself in
readable form). The most direct setup is to write each record on its own as an object
literal.

```js
const recordOne = {
  sensor: "S-01",
  value: 21.4,
  time: 1000,
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
};

const recordTwo = {
  sensor: "S-02",
  value: 19.8,
  time: 1060,
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
};

console.log(recordOne.format());
console.log(recordTwo.format());
console.log(recordOne.format === recordTwo.format);
```

```
S-01@1000: 21.4
S-02@1060: 19.8
false
```

The last line makes the problem visible. Both records offer the same behavior, but in
memory there are **two separate function objects**; the identity comparison says so. A
thousand measurements produce a thousand copies, and when the formatting rule changes, it
has to change in a thousand places.

The Introduction to Object-Oriented Programming lesson in the Programming Fundamentals
course answered this problem with the class concept: behavior is written once in the
class, objects share it. JavaScript's answer sits one level lower and requires no class.

#### The Prototype Link

Every object, the moment it is created, is bound to another object — or to the value
`null`. This link is called the **prototype**. The link is not an ordinary property of
the object; in the language's specification it appears as an internal field named
`[[Prototype]]`, and it is read with the `Object.getPrototypeOf` function.

`Object.create` sets up the link directly: it produces a new object whose prototype is
the given object.

```js
const measurementBehavior = {
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
  exceedsThreshold(threshold) {
    return this.value > threshold;
  },
};

function measurementRecord(sensor, value, time) {
  const record = Object.create(measurementBehavior);
  record.sensor = sensor;
  record.value = value;
  record.time = time;
  return record;
}

const recordOne = measurementRecord("S-01", 21.4, 1000);
const recordTwo = measurementRecord("S-02", 19.8, 1060);

console.log(recordOne.format());
console.log(recordTwo.exceedsThreshold(20));
console.log(recordOne.format === recordTwo.format);
console.log(Object.getPrototypeOf(recordOne) === measurementBehavior);
```

```
S-01@1000: 21.4
false
true
true
```

The third line now gives `true`: both records use the same function object, because the
function stands not in the records but in the shared prototype. The fourth line tests the
link itself — every claim about the object model throughout this lesson will be shown
this way, with a directly testable comparison.

Data is separate in each record, behavior is in one place. This split is the entire point
of the prototype model: **the part that changes stands in the object, the part that does
not stands in the chain.**

#### Property Lookup Order

When you read a property, the runtime follows this order:

1. Search among the object's own properties. If found, return the value.
2. If not found, move to its prototype and do the same search there.
3. Continue until the chain reaches `null`. If found nowhere, return `undefined`.

Because the search stops at the first match, an object's own property **shadows** a
prototype property of the same name. The Scope and Lifetime lesson in the Programming
Foundations course described an inner scope shadowing a name in an outer scope; the
mechanism here is the same idea's counterpart for objects.

```js
const measurementBehavior = {
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
};

const record = Object.create(measurementBehavior);
record.sensor = "S-01";
record.value = 21.4;
record.time = 1000;

console.log(record.format());

record.format = function () {
  return `[raw] ${this.value}`;
};

console.log(record.format());
console.log(Object.hasOwn(record, "format"));
console.log(measurementBehavior.format.call(record));

delete record.format;
console.log(record.format());
```

```
S-01@1000: 21.4
[raw] 21.4
true
S-01@1000: 21.4
S-01@1000: 21.4
```

Three observations matter. First, assignment does not **change** the prototype's
function; it creates a new property belonging to the record. A write never climbs the
chain, it always writes to the object itself. Second, the shadowed property does not
disappear; it can still be called with `call` (the details of this operation belong to
the Context and Closures topic). Third, when the own property is deleted, the chain
becomes visible again — deletion looks as if it brought back the prototype's definition,
but it actually only removed the shadow.

#### Own Property and Inherited Property

Properties coming through the chain are not distinguished from own properties in most
read operations; the places the distinction matters are counting and serialization.

```js
const measurementBehavior = {
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
};

const record = Object.create(measurementBehavior);
record.sensor = "S-01";
record.value = 21.4;
record.time = 1000;

console.log("format" in record);
console.log(Object.hasOwn(record, "format"));
console.log(Object.keys(record).join(","));

const forInKeys = [];
for (const key in record) {
  forInKeys.push(key);
}
console.log(forInKeys.join(","));

console.log(JSON.stringify(record));
```

```
true
false
sensor,value,time
sensor,value,time,format
{"sensor":"S-01","value":21.4,"time":1000}
```

The `in` operator looks at the whole chain, `Object.hasOwn` only at the object itself.
`Object.keys` is limited to own properties; the `for...in` loop also walks properties
coming from the chain, which is why it is not a reliable tool for iterating over an
object. `JSON.stringify` also works with own properties only — serialization takes only
the data, leaves the behavior behind. This behavior gives an early hint of the
distinction at the end of the lesson: **an object's data is in itself, its identity is in
its chain.**

The `format` name appearing in the `for...in` output here is because the prototype
property is *enumerable*. How enumerability is controlled is the subject of the Property
Descriptors lesson; class syntax's difference on this point will be shown there too.

#### The End of the Chain

The chain is not infinite. Every object created with an object literal has
`Object.prototype` as its prototype, and that object's prototype is `null`. This is why
names like `toString`, `valueOf`, and `hasOwnProperty` are found on every object.

```js
const measurementBehavior = {
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
};
const record = Object.create(measurementBehavior);

console.log(Object.getPrototypeOf(record) === measurementBehavior);
console.log(Object.getPrototypeOf(measurementBehavior) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));

console.log(typeof record.toString);
console.log(typeof record.unknownProperty);

const dictionary = Object.create(null);
dictionary.sensor = "S-01";
console.log(Object.getPrototypeOf(dictionary));
console.log(typeof dictionary.toString);
console.log("toString" in dictionary);
```

```
true
true
null
function
undefined
null
undefined
false
```

The chain has three links: `record` → `measurementBehavior` → `Object.prototype` →
`null`. When a nonexistent property is requested, the runtime walks all three stops and
returns `undefined`; it does not throw an error. Search cost grows with chain length, but
the work done at each stop is a dictionary lookup — exactly the average constant-cost
access examined in the Hash Tables lesson of the Data Structures course.

An object produced with `Object.create(null)` has no chain at all. Such an object behaves
as a pure dictionary: because inherited names like `toString` are not present, the risk
of externally supplied keys colliding with built-in names is eliminated. In mappings
whose keys come from outside the program, this setup also makes `for...in` and `in`
results predictable. The language's real counterpart for this same need is the `Map`
type, taken up in the Advanced Collections topic.

#### Summary

- Every object carries, in addition to its own properties, a link to a prototype object;
  the link is read with `Object.getPrototypeOf` and can be tested directly.
- Property lookup starts from the object itself, moves up the chain if not found, and
  ends in `undefined` at the `null` stop.
- Writing does not climb the chain; it always writes to the object itself and shadows a
  prototype property of the same name.
- `in` looks at the whole chain; `Object.hasOwn` and `Object.keys` look only at own
  properties; `JSON.stringify` also works with own properties only.
- The chain built by object literals reaches `null` through `Object.prototype`;
  `Object.create(null)` produces a prototype-less object that behaves as a pure
  dictionary.

#### Next Step

In this lesson the prototype was set up by hand: a shared behavior object was written
first, then each record was bound to it with `Object.create`. This pattern works, but it
does not resemble a type definition — deriving a new record type, filling in shared
fields, and sharing behavior between two record types all require a repeating setup. The
next lesson examines the built-in patterns for object-to-object inheritance and shows how
the `new` operator sets up this link in your place.

### Prototypal Inheritance

Source: https://academia.sh/en/courses/javascript-object-model/prototypal-inheritance

The link constructor functions and the new operator set up, extending the prototype chain to two layers, type-query operations, and the link changing at runtime.

The previous lesson set up the prototype link by hand: shared behavior was gathered in
one object, each record was bound to it with `Object.create`, and fields were filled in
one by one. The pattern works, but it has two gaps. Filling in fields and binding
behavior sit in separate places, and there is no built-in setup for deriving one record
type from another.

The language offers a built-in path for both. This lesson opens that path — the
constructor function and the `new` operator — then writes by hand what `new` does, to
show that it is not magic but a shorthand for the three steps you saw in the previous
lesson.

#### The Constructor Function

Every function in JavaScript carries an ordinary property named `prototype`. This
property is **not** the function's own prototype; it is the object that will become the
prototype of the objects the function produces when called with `new`. Because the name
similarity hides this distinction, the two concepts must not be confused.

A function written to be called with `new` is called a **constructor function**. It
fills in fields through `this`, while behaviors are written to the `prototype` object.

```js
function MeasurementRecord(sensor, value, time) {
  this.sensor = sensor;
  this.value = value;
  this.time = time;
}

MeasurementRecord.prototype.format = function () {
  return `${this.sensor}@${this.time}: ${this.value}`;
};

MeasurementRecord.prototype.exceedsThreshold = function (threshold) {
  return this.value > threshold;
};

const recordOne = new MeasurementRecord("S-01", 21.4, 1000);
const recordTwo = new MeasurementRecord("S-02", 19.8, 1060);

console.log(recordOne.format());
console.log(Object.getPrototypeOf(recordOne) === MeasurementRecord.prototype);
console.log(recordOne.format === recordTwo.format);
console.log(Object.keys(recordOne).join(","));
console.log(recordOne.constructor === MeasurementRecord);
console.log(Object.hasOwn(MeasurementRecord.prototype, "constructor"));
```

```
S-01@1000: 21.4
true
true
sensor,value,time
true
true
```

The result is the same as the previous lesson's setup: data separate in each record,
behavior in one place. The fourth line confirms this — the record's own properties are
only the three data fields.

The fifth and sixth lines expose a detail. Every function's `prototype` object, the
moment it is created, carries a property named `constructor`, and this property points
at the function itself. The expression `recordOne.constructor` is not found on the
record; it comes from the chain, through `MeasurementRecord.prototype`. So `constructor`
is not an object's "true type," it is an ordinary property encountered on its chain — the
cost of this distinction will be seen in the next section.

#### What the new Operator Does

The expression `new MeasurementRecord(...)` breaks down into four steps:

1. An empty object is created whose prototype is `MeasurementRecord.prototype`.
2. The constructor function is called with `this` bound to this object.
3. If the constructor function returns an object, that object is the result; if not, the
   object from the first step is the result.
4. The result becomes the value of the expression.

All of these steps are ordinary operations; none of them carries language-specific
privilege. A function doing the same job can be written.

```js
function MeasurementRecord(sensor, value, time) {
  this.sensor = sensor;
  this.value = value;
  this.time = time;
}
MeasurementRecord.prototype.format = function () {
  return `${this.sensor}@${this.time}: ${this.value}`;
};

function newInstance(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype);
  const result = Constructor.apply(obj, args);
  return typeof result === "object" && result !== null ? result : obj;
}

const viaHelper = newInstance(MeasurementRecord, "S-01", 21.4, 1000);
const viaOperator = new MeasurementRecord("S-01", 21.4, 1000);

console.log(viaHelper.format());
console.log(Object.getPrototypeOf(viaHelper) === MeasurementRecord.prototype);
console.log(JSON.stringify(viaHelper) === JSON.stringify(viaOperator));
console.log(viaHelper instanceof MeasurementRecord);
```

```
S-01@1000: 21.4
true
true
true
```

The `newInstance` function stands in for the `new` operator and produces an
indistinguishable result. The `apply` call explicitly sets up the `this` binding; this
operation's rules will be covered in the first two lessons of the Context and Closures
topic. The real observation here is this: `new` is a shorthand laid on top of the
prototype model, not a separate mechanism.

The `newInstance` function we wrote does not cover the whole of `new`: constructors
defined with class syntax cannot be called without `new`, and details like `new.target`
fall outside this shorthand. What matters for the model is the three lines above.

#### Extending the Chain

Consider a subtype of measurement records: a **calibrated measurement** carrying a
sensor's systematic drift. Its fields cover the measurement record's fields and add an
offset value on top; how the read value is computed differs, how it is formatted is the
same.

Extending the chain to two layers requires two jobs: the subtype's prototype has to be
bound to the supertype's prototype, and while the sub-constructor runs, the
super-constructor's field-filling job has to run too.

```js
function MeasurementRecord(sensor, value, time) {
  this.sensor = sensor;
  this.value = value;
  this.time = time;
}
MeasurementRecord.prototype.format = function () {
  return `${this.sensor}@${this.time}: ${this.value}`;
};
MeasurementRecord.prototype.reading = function () {
  return this.value;
};

function CalibratedMeasurement(sensor, value, time, offset) {
  MeasurementRecord.call(this, sensor, value, time);
  this.offset = offset;
}

CalibratedMeasurement.prototype = Object.create(MeasurementRecord.prototype);
CalibratedMeasurement.prototype.constructor = CalibratedMeasurement;

CalibratedMeasurement.prototype.reading = function () {
  return this.value + this.offset;
};

const calibrated = new CalibratedMeasurement("S-03", 21.4, 1120, -0.4);

console.log(calibrated.format());
console.log(calibrated.reading());
console.log(MeasurementRecord.prototype.reading.call(calibrated));
console.log(Object.getPrototypeOf(calibrated) === CalibratedMeasurement.prototype);
console.log(Object.getPrototypeOf(CalibratedMeasurement.prototype) === MeasurementRecord.prototype);
console.log(calibrated.constructor === CalibratedMeasurement);
```

```
S-03@1120: 21.4
21
21.4
true
true
true
```

The chain now has four links: `calibrated` → `CalibratedMeasurement.prototype` →
`MeasurementRecord.prototype` → `Object.prototype` → `null`. `format` is not found on the
second link, it is found on the third; `reading` is found on the second link, so it
shadows the third's definition. The **polymorphism** introduced in the Introduction to
Object-Oriented Programming lesson of the Programming Fundamentals course is exactly this
shadowing here: the same name corresponds to different implementations at different
layers of the chain, and lookup order decides which one runs.

Three lines deserve attention. `MeasurementRecord.call(this, ...)` runs the
super-constructor's field-filling job on the new object. If the link set up with
`Object.create(MeasurementRecord.prototype)` had instead been set up as
`CalibratedMeasurement.prototype = MeasurementRecord.prototype`, the two types would
share the exact same prototype object, and every behavior added to the subtype would leak
into the supertype too. Writing the `constructor` property back by hand is the
consequence of the first section's observation: when the `prototype` object is replaced,
`constructor` is lost along with it, and if it is not rewritten, `calibrated.constructor`
finds `MeasurementRecord` from the chain.

#### Type Querying on the Chain

The `instanceof` operator asks whether the function on its right's `prototype` object is
found on the chain of the object on its left. This query, which looks like class
membership, is actually a chain scan.

```js
function MeasurementRecord(sensor, value) {
  this.sensor = sensor;
  this.value = value;
}
function CalibratedMeasurement(sensor, value, offset) {
  MeasurementRecord.call(this, sensor, value);
  this.offset = offset;
}
CalibratedMeasurement.prototype = Object.create(MeasurementRecord.prototype);
CalibratedMeasurement.prototype.constructor = CalibratedMeasurement;

const calibrated = new CalibratedMeasurement("S-03", 21.4, -0.4);
const plain = new MeasurementRecord("S-01", 21.4);

console.log(calibrated instanceof CalibratedMeasurement);
console.log(calibrated instanceof MeasurementRecord);
console.log(calibrated instanceof Object);
console.log(plain instanceof CalibratedMeasurement);

console.log(MeasurementRecord.prototype.isPrototypeOf(calibrated));
console.log(CalibratedMeasurement.prototype.isPrototypeOf(plain));

let stop = calibrated;
const chain = [];
while (stop !== null) {
  stop = Object.getPrototypeOf(stop);
  chain.push(stop === null ? "null" : stop.constructor.name);
}
console.log(chain.join(" -> "));
```

```
true
true
true
false
true
false
CalibratedMeasurement -> MeasurementRecord -> Object -> null
```

The last block walks the chain from start to end and prints it, making visible where the
`instanceof` results come from. Because all three `prototype` objects are found on the
calibrated measurement's chain, all three queries answer positively; because the plain
record does not carry the subtype's prototype on its chain, the fourth query is negative.
`isPrototypeOf` asks the same question from the reverse direction and looks not at the
function but directly at the prototype object.

#### The Liveness of the Link

The prototype link is not a copy, it is a live reference. A property added to the
prototype after an object is created immediately becomes visible on every existing
object.

```js
function MeasurementRecord(sensor, value) {
  this.sensor = sensor;
  this.value = value;
}
MeasurementRecord.prototype.format = function () {
  return `${this.sensor}: ${this.value}`;
};

const record = new MeasurementRecord("S-01", 21.4);
console.log(record.format());
console.log(typeof record.summarize);

MeasurementRecord.prototype.summarize = function () {
  return `${this.sensor} record`;
};

console.log(typeof record.summarize);
console.log(record.summarize());

const otherBehavior = {
  format() {
    return `[${this.sensor}] ${this.value}`;
  },
};
Object.setPrototypeOf(record, otherBehavior);
console.log(record.format());
console.log(record instanceof MeasurementRecord);
console.log(typeof record.summarize);
```

```
S-01: 21.4
undefined
function
S-01 record
[S-01] 21.4
false
undefined
```

`summarize` was added after the record was created and becomes accessible with no
re-creation at all; because the search is done fresh on every read, the link is live.

`Object.setPrototypeOf` changes an existing object's link. The consequences are broad:
the record no longer sees the supertype's behaviors, the `instanceof` result flips, and
because runtimes optimize based on object shapes, access performance drops. For this
reason the link is set up at the moment the object is created and is not changed
afterward; `Object.create` or `new` exist for exactly this.

#### Summary

- Every function's `prototype` property is the object that will become the prototype of
  the objects it produces with `new`; it is not the function's own prototype.
- `new` creates an empty object whose prototype is bound to the `prototype` object, runs
  the constructor on this object, and returns the result; the same job can be written by
  hand with `Object.create`.
- Extending the chain consists of two jobs: binding the sub-prototype to the
  super-prototype, and calling the super-constructor from inside the sub-constructor.
- A same-named definition at a lower layer shadows the one above it; polymorphism is the
  result of this lookup order.
- `instanceof` and `isPrototypeOf` ask not about type membership but whether a prototype
  object is found on the chain.
- The link is live: properties added to the prototype afterward become visible on
  existing objects.

#### Next Step

In this lesson the chain was set up by hand, and every step of the setup was made
visible: the `prototype` assignment, the `constructor` repair, the super-constructor
call. All three steps being easy to forget is the reason a syntax was added to the
language that gathers them into a single declaration. The next lesson introduces class
syntax and shows, with the same tests, which operation from this lesson each of its lines
corresponds to.

### Class Syntax

Source: https://academia.sh/en/courses/javascript-object-model/class-syntax

Class declaration's exact counterpart on the prototype chain, the static side's inheritance, method enumerability, and the real differences syntax brings.

The previous lesson set up a two-layer chain by hand. It required three separate
operations: binding the sub-prototype to the super-prototype, repairing the `constructor`
property, and calling the super-constructor from inside the sub-constructor. When one of
the three is forgotten, the error shows up at runtime, often in a distant place.

**Class syntax** gathers these three operations into a single declaration. This lesson
shows, with the previous lesson's tests, which prototype operation each line of a class
declaration corresponds to, then separates out the aspects of the syntax that are truly
new. Its main claim has to be stated up front: **a class is the syntactic face of the
prototype chain**; there is no different object model underneath it.

#### Same Chain, Single Declaration

The measurement record and the calibrated measurement, this time written with a class
declaration.

```js
class MeasurementRecord {
  constructor(sensor, value, time) {
    this.sensor = sensor;
    this.value = value;
    this.time = time;
  }

  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  }

  reading() {
    return this.value;
  }
}

class CalibratedMeasurement extends MeasurementRecord {
  constructor(sensor, value, time, offset) {
    super(sensor, value, time);
    this.offset = offset;
  }

  reading() {
    return super.reading() + this.offset;
  }
}

const calibrated = new CalibratedMeasurement("S-03", 21.4, 1120, -0.4);

console.log(calibrated.format());
console.log(calibrated.reading());
console.log(typeof MeasurementRecord);
console.log(Object.getPrototypeOf(calibrated) === CalibratedMeasurement.prototype);
console.log(Object.getPrototypeOf(CalibratedMeasurement.prototype) === MeasurementRecord.prototype);
console.log(calibrated.constructor === CalibratedMeasurement);
console.log(Object.keys(calibrated).join(","));
```

```
S-03@1120: 21.4
21
function
true
true
true
sensor,value,time,offset
```

The output's first two lines are the same as the previous lesson's output. The remaining
lines expose the structure the syntax sets up:

- `typeof MeasurementRecord` is `"function"` — a class declaration does not produce a new
  value kind, it produces a function.
- The instance's prototype is the `CalibratedMeasurement.prototype` object; `extends`
  makes this object's prototype `MeasurementRecord.prototype`.
- The `constructor` property points to the right function; no manual repair is needed.
- The instance's own properties are only the four data fields. The methods stand not on
  the instance, but on the `prototype` object.

The `super.reading()` call is also explained by the chain: it starts the lookup not
through `this`, but from a prototype one layer up. In the previous lesson this job was
written by hand as `MeasurementRecord.prototype.reading.call(this)`; `super` is the name
for the same job.

#### The Static Side

A class declaration writes members marked with the `static` keyword not to instances but
to the class function itself. A real difference from the manually built pattern shows up
here: `extends` binds not only the `prototype` objects but **the class functions too**.

```js
class MeasurementRecord {
  static measurementUnit = "celsius";

  static fromText(line) {
    const [sensor, value, time] = line.split(";");
    return new this(sensor, Number(value), Number(time));
  }

  constructor(sensor, value, time) {
    this.sensor = sensor;
    this.value = value;
    this.time = time;
  }

  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  }
}

class CalibratedMeasurement extends MeasurementRecord {}

const record = MeasurementRecord.fromText("S-01;21.4;1000");
const child = CalibratedMeasurement.fromText("S-03;22.1;1120");

console.log(record.format());
console.log(child.format());
console.log(child instanceof CalibratedMeasurement);
console.log(CalibratedMeasurement.measurementUnit);
console.log(Object.getPrototypeOf(CalibratedMeasurement) === MeasurementRecord);
console.log(Object.hasOwn(CalibratedMeasurement, "measurementUnit"));
```

```
S-01@1000: 21.4
S-03@1120: 22.1
true
celsius
true
false
```

The last two lines sum up what is happening: the `CalibratedMeasurement` class does not
carry the `measurementUnit` property itself, it inherits it through its prototype,
`MeasurementRecord`. So a class declaration sets up **two chains** — one between
`prototype` objects for instances, one between class functions. In the previous lesson's
manual setup, the second one was never set up at all.

The `new this(...)` expression inside the `fromText` method is the result of this second
chain: `this` inside a static method is bound to whichever class it was called through,
so when called from a subclass, an instance of the subclass is produced.

#### Method Enumerability

In the first lesson it was seen that a `for...in` loop also lists the `format` name
coming from the prototype. This does not happen for methods defined with class syntax.

```js
class MeasurementRecord {
  constructor(sensor, value) {
    this.sensor = sensor;
    this.value = value;
  }
  format() {
    return `${this.sensor}: ${this.value}`;
  }
}

function OldMeasurementRecord(sensor, value) {
  this.sensor = sensor;
  this.value = value;
}
OldMeasurementRecord.prototype.format = function () {
  return `${this.sensor}: ${this.value}`;
};

const newRecord = new MeasurementRecord("S-01", 21.4);
const oldRecord = new OldMeasurementRecord("S-01", 21.4);

console.log(Object.keys(MeasurementRecord.prototype).join(",") || "(empty)");
console.log(Object.keys(OldMeasurementRecord.prototype).join(",") || "(empty)");
console.log(Object.getOwnPropertyNames(MeasurementRecord.prototype).join(","));

const newKeys = [];
for (const key in newRecord) newKeys.push(key);
const oldKeys = [];
for (const key in oldRecord) oldKeys.push(key);

console.log(newKeys.join(","));
console.log(oldKeys.join(","));
```

```
(empty)
format
constructor,format
sensor,value
sensor,value,format
```

Class methods **exist** on the prototype — the third line shows this — but they are
defined as *non-enumerable*; this is why `Object.keys` and `for...in` skip them. A
manually written assignment, on the other hand, produces an enumerable property and leaks
into the loop. What enumerability is and how it is manually controlled is the subject of
the next lesson.

#### The Real Differences the Syntax Brings

A class declaration is not only a shorthand; it also changes a few behaviors. The two
most visible ones can be tested directly.

```js
function oldRecord(sensor) {
  this.sensor = sensor;
}
const beforeDeclaration = new oldRecord("S-01");
console.log(beforeDeclaration.sensor);

try {
  new MeasurementRecord("S-01");
} catch (error) {
  console.log(error.constructor.name);
}

class MeasurementRecord {
  constructor(sensor) {
    this.sensor = sensor;
  }
}

console.log(new MeasurementRecord("S-01").sensor);

try {
  MeasurementRecord("S-01");
} catch (error) {
  console.log(error.constructor.name);
}
```

```
S-01
ReferenceError
S-01
TypeError
```

The first difference is **hoisting**. A function declaration can be called before its
definition; a class declaration cannot, because its name is in the temporal dead zone
until the declaration. This is the same behavior described for `let` and `const` in the
Naming and Scope Rules lesson of the JavaScript Fundamentals course.

The second difference is the **call form**. A constructor function can be called without
`new`, and in that case `this` binds to an unexpected value; a class constructor throws
an error when called without `new`. The third difference does not show up in the output:
all code inside a class body runs in strict mode, and silent errors like assigning to an
undeclared variable do not stay silent here.

#### Private Fields

In the manually built pattern, there was no language-level way to hide an object's
internal state; naming conventions (a leading underscore) only signaled intent. Class
syntax brings **private fields**, whose name starts with a hash sign. These are not
ordinary properties — they appear neither on the prototype chain nor in the object's
property listing.

```js
class MeasurementRecord {
  #offset;

  constructor(sensor, value, offset = 0) {
    this.sensor = sensor;
    this.value = value;
    this.#offset = offset;
  }

  get reading() {
    return this.value + this.#offset;
  }

  hasOffset() {
    return this.#offset !== 0;
  }

  static offsetVisible(obj) {
    return #offset in obj;
  }
}

const record = new MeasurementRecord("S-01", 21.4, -0.4);

console.log(record.reading);
console.log(record.hasOffset());
console.log(Object.keys(record).join(","));
console.log(JSON.stringify(record));
console.log(Object.getOwnPropertyNames(record).join(","));
console.log(MeasurementRecord.offsetVisible(record));
console.log(MeasurementRecord.offsetVisible({ sensor: "S-02", value: 19.8 }));
console.log(Object.getOwnPropertyNames(MeasurementRecord.prototype).join(","));
```

```
21
true
sensor,value
{"sensor":"S-01","value":21.4}
sensor,value
true
false
constructor,reading,hasOffset
```

A private field appears in no listing operation and does not enter serialization; trying
to access it from outside the class body does not even produce a runtime error, the code
cannot be parsed at all. Access is possible only from the body of the class that declares
the field. The `#offset in obj` form asks whether an object carries this field, without
raising an error.

The last line shows one more detail: `reading` is defined as an **accessor** and yet it
stands on the prototype. That is, the expression `record.reading` looks like a property
read but runs a function found on the chain. How accessors are defined is the next
lesson's second subject.

The language's second path for encapsulation is closures, and they provide hiding
through an entirely different mechanism, never touching the prototype; a comparison of
the two will be made in the Closures lesson of the Context and Closures topic.

#### Summary

- A class declaration produces a function; instances are bound to a `prototype` object
  and methods stand there.
- `extends` sets up two chains: between instance prototypes and between class functions;
  the second one enables inheriting static members.
- `super` is the expression that starts a lookup from a prototype one layer up; it is the
  counterpart of a manually written `call` invocation.
- Class methods are defined as non-enumerable on the prototype, so they do not enter
  `for...in` and `Object.keys` results.
- The syntax's real differences are: the temporal dead zone, the requirement of `new`,
  strict mode, and private fields.
- Private fields are not properties; they are not listed, not serialized, and accessible
  only from the declaring class's body.

#### Next Step

This lesson ran into enumerability twice: class methods do not enter `for...in` output,
manually written assignments do. An accessor also appeared — a property that looks like a
read but runs a function. Both show that properties are not made up of just a value; they
carry flags alongside that determine their behavior. The next lesson takes up these
flags — writability, enumerability, configurability — and accessors directly.

### Property Descriptors

Source: https://academia.sh/en/courses/javascript-object-model/property-descriptors

The writability, enumerability, and configurability flags a property carries alongside its value; accessor properties and copying that preserves descriptors.

The previous lesson arrived at the same place twice: class methods do not appear in
`for...in` output, manually written assignments do; and `reading`, though written to look
like a read, ran a function. Both are the result of a single fact.

A property is not made up of just a "name → value" pair. Alongside every property stands
a **property descriptor** that determines its behavior. This lesson opens the
descriptor's fields one by one, then takes up accessor properties, which carry a function
in place of a value.

#### Reading the Descriptor

`Object.getOwnPropertyDescriptor` returns the descriptor of an object's **own** property.
Data properties carry four fields: `value`, `writable`, `enumerable`, `configurable`.

```js
function flags(obj, name) {
  const descriptor = Object.getOwnPropertyDescriptor(obj, name);
  if (descriptor === undefined) return `${name}: no descriptor`;
  if ("value" in descriptor) {
    return `${name}: data, writable=${descriptor.writable}, enumerable=${descriptor.enumerable}, configurable=${descriptor.configurable}`;
  }
  return `${name}: accessor, getter=${typeof descriptor.get}, setter=${typeof descriptor.set}, enumerable=${descriptor.enumerable}`;
}

class MeasurementRecord {
  constructor(sensor, value) {
    this.sensor = sensor;
    this.value = value;
  }
  format() {
    return `${this.sensor}: ${this.value}`;
  }
}

const record = new MeasurementRecord("S-01", 21.4);
const plainRecord = { sensor: "S-02", value: 19.8 };

Object.defineProperty(plainRecord, "unit", { value: "celsius" });

console.log(flags(record, "sensor"));
console.log(flags(MeasurementRecord.prototype, "format"));
console.log(flags(plainRecord, "sensor"));
console.log(flags(plainRecord, "unit"));
console.log(flags(record, "format"));
```

```
sensor: data, writable=true, enumerable=true, configurable=true
format: data, writable=true, enumerable=false, configurable=true
sensor: data, writable=true, enumerable=true, configurable=true
unit: data, writable=false, enumerable=false, configurable=false
format: no descriptor
```

Three rules follow. Properties produced by ordinary assignment or an object literal have
all three flags open. Class methods are defined as non-enumerable — this is the source of
the `for...in` difference from the previous lesson. In an `Object.defineProperty` call,
**every flag not specified is counted as closed**; this is why the `unit` property comes
out non-writable, non-enumerable, and non-configurable. The last line is a reminder of the
word "own" in the function's name: because `format` stands on the prototype, the record
has no definition of it on itself.

#### Writability

If the `writable` flag is closed, assignment does not change the value. There are two
different forms of not changing, and the difference depends on whether the code runs in
strict mode.

```js
const record = { sensor: "S-01", value: 21.4 };

Object.defineProperty(record, "unit", {
  value: "celsius",
  writable: false,
  enumerable: true,
  configurable: false,
});

console.log(record.unit);

try {
  record.unit = "fahrenheit";
} catch (error) {
  console.log(`${error.constructor.name} caught`);
}
console.log(record.unit);

function nonStrictWrite(obj) {
  return new Function("n", "n.unit = 'fahrenheit'; return n.unit;")(obj);
}
console.log(nonStrictWrite(record));

try {
  Object.defineProperty(record, "unit", { value: "kelvin" });
} catch (error) {
  console.log(`${error.constructor.name} caught`);
}
console.log(record.unit);
```

```
celsius
TypeError caught
celsius
celsius
TypeError caught
celsius
```

Module files and class bodies run in strict mode; there, assigning to a non-writable
property throws an error. The body produced with `new Function` is not in strict mode, so
the same assignment silently fails — the unchanged value on the fourth line shows this.
Because silent failure lets an error be noticed long after the assignment, strict mode is
preferred.

The last two lines are the effect of the `configurable` flag. Once configurability is
closed, the property cannot be deleted and most flags can never be changed again; if
`writable` is still open the value keeps changing and that flag can only be closed.
`configurable` itself is the one flag that cannot be reversed. For this reason,
`configurable: false` should be a deliberate decision.

#### Enumerability

The `enumerable` flag determines whether a property enters listings. Its effect spreads
across a wide set of operations.

```js
const record = { sensor: "S-01", value: 21.4 };

Object.defineProperty(record, "sourceFile", {
  value: "measurement-2.csv",
  writable: true,
  enumerable: false,
  configurable: true,
});

console.log(record.sourceFile);
console.log(Object.keys(record).join(","));
console.log(Object.getOwnPropertyNames(record).join(","));
console.log(JSON.stringify(record));
console.log(JSON.stringify({ ...record }));
console.log(JSON.stringify(Object.assign({}, record)));
console.log("sourceFile" in record);
console.log(Object.entries(record).length);
```

```
measurement-2.csv
sensor,value
sensor,value,sourceFile
{"sensor":"S-01","value":21.4}
{"sensor":"S-01","value":21.4}
{"sensor":"S-01","value":21.4}
true
2
```

The property is readable and the `in` operator sees it; but `Object.keys`,
`Object.entries`, `JSON.stringify`, object spread, and `Object.assign` skip it.
`Object.getOwnPropertyNames` does not look at enumerability, which is why it is the way
to inspect properties that stay hidden.

A practical rule follows from this: **information that accompanies data without being
part of the data** — a source file name, internal counters, cache fields — when defined
as non-enumerable, does not automatically get caught up in serialization and copying
operations. Symbol-keyed properties give the same result too; they also do not enter
`Object.keys` and `JSON.stringify` output.

#### Accessor Properties

The second descriptor kind carries `get` and `set` functions in place of `value` and
`writable`. A property like this is called an **accessor**: reading runs the `get`
function, writing runs the `set` function.

An accessor's value is presenting a computed magnitude as if it were a property. In a
measurement record, the read value is the sum of the raw value and the calibration
offset; making this an accessor instead of a method leaves the caller unaware that a
computation exists at all.

```js
const measurementBehavior = {};

Object.defineProperty(measurementBehavior, "reading", {
  get() {
    return this.value + this.offset;
  },
  set(newValue) {
    if (typeof newValue !== "number" || Number.isNaN(newValue)) {
      throw new TypeError("the reading must be a number");
    }
    this.value = newValue - this.offset;
  },
  enumerable: false,
  configurable: true,
});

function measurementRecord(sensor, value, offset) {
  const record = Object.create(measurementBehavior);
  record.sensor = sensor;
  record.value = value;
  record.offset = offset;
  return record;
}

const record = measurementRecord("S-01", 21.4, -0.4);

console.log(record.reading);
record.reading = 25;
console.log(record.value);
console.log(record.reading);

try {
  record.reading = "twenty";
} catch (error) {
  console.log(`${error.constructor.name}: ${error.message}`);
}

console.log(Object.hasOwn(record, "reading"));
console.log(Object.hasOwn(measurementBehavior, "reading"));
console.log(JSON.stringify(record));
```

```
21
25.4
25
TypeError: the reading must be a number
false
true
{"sensor":"S-01","value":25.4,"offset":-0.4}
```

The accessor is defined on the prototype; it is not the record's own property. Even so,
the `this` inside the `get` and `set` bodies is bound to the object the access is made
on — that is, the record itself. A function found on the chain works with the data of the
object it is called on; the full rule for this will be covered in the first lesson of the
next topic.

The setter function is the direct way of preserving the **class invariant** introduced in
the Introduction to Object-Oriented Programming lesson of the Programming Fundamentals
course: an invalid value never enters the object at all. Also, because the accessor is
defined as non-enumerable, it does not enter serialization; the last line shows only the
raw data fields. This keeps computed values from mixing with stored data.

The `get` and `set` keywords in a class body are shorthand for this same definition; the
accessor they produce is also written to the `prototype` object, as non-enumerable.

#### Copying That Preserves Descriptors

The usual ways of copying an object do not carry descriptors. Object spread and
`Object.assign` **read** every property on the source and **write** it to the target as
an ordinary data property. Accessors turn into a frozen value in this operation.

```js
const source = {
  sensor: "S-01",
  value: 21.4,
  get label() {
    return `${this.sensor}/${this.value}`;
  },
};

const spread = { ...source };
const definedCopy = Object.defineProperties(
  Object.create(Object.getPrototypeOf(source)),
  Object.getOwnPropertyDescriptors(source),
);

console.log(source.label);
console.log(spread.label);
console.log(definedCopy.label);

source.value = 30;
spread.value = 30;
definedCopy.value = 30;

console.log(source.label);
console.log(spread.label);
console.log(definedCopy.label);

console.log(typeof Object.getOwnPropertyDescriptor(spread, "label").get);
console.log(typeof Object.getOwnPropertyDescriptor(definedCopy, "label").get);
```

```
S-01/21.4
S-01/21.4
S-01/21.4
S-01/30
S-01/21.4
S-01/30
undefined
function
```

In the copy produced by spread, `label` is no longer computed; it is a fixed data
property carrying the text from the moment of copying, which is why it keeps showing the
old one once `value` changes. When `Object.getOwnPropertyDescriptors` is used together
with `Object.defineProperties`, both accessors and flags are preserved; the
`Object.create` call also carries the prototype link. Copying's general problem — which
information is carried, which is dropped — will be taken up in full in the course's last
lesson.

#### Summary

- Every property carries `writable`, `enumerable`, `configurable` flags alongside its
  value; `Object.getOwnPropertyDescriptor` reads them.
- Flags not specified in an `Object.defineProperty` call are counted as closed;
  properties produced by assignment have all three open.
- Assigning to a non-writable property throws an error in strict mode, silently fails in
  non-strict mode; `configurable: false` cannot be reversed.
- Non-enumerable properties do not enter `Object.keys`, `Object.entries`,
  `JSON.stringify`, spread, and `Object.assign` results; they are visible with
  `Object.getOwnPropertyNames`.
- On an accessor property, reading and writing each run a function; the setter preserves
  the class invariant by keeping an invalid value from entering the object.
- Spread and `Object.assign` do not carry descriptors; they turn an accessor into a fixed
  value.

#### Next Step

`writable: false` locks a single property. Preventing an entire object from changing,
forbidding new properties from being added to it, or leaving only existing fields'
values changeable is tedious to write per property. The next lesson takes up the freezing
and sealing operations applied to a whole object, how deep they go, and the patterns for
producing a new object instead of a change when change is needed.

### Immutability Techniques

Source: https://academia.sh/en/courses/javascript-object-model/immutability-techniques

The scope of freezing, sealing, and preventing extensions; the consequences of their staying shallow, deep freezing, and patterns for producing a new object instead of a change.

The previous lesson locked a single property with `writable: false`. Protecting an
entire object requires writing a separate definition for every field, which is tedious
and still does not prevent new fields from being added. The language offers three
operations that work at the object level.

This lesson's subject is **immutability**: an object's content not changing after it is
created. The term should not be confused with the *class invariant* mentioned in the
previous lesson; an invariant is a rule that never changes, immutability is a capability.
The Introduction to Functional Programming lesson in the Programming Fundamentals course
established the reasoning for this capability: an object whose value does not change
holds no surprises no matter how many places reference it.

#### Freezing

`Object.freeze` makes all of an object's own properties non-writable and
non-configurable, and also forbids new properties from being added to the object.

```js
const record = Object.freeze({
  sensor: "S-01",
  value: 21.4,
  time: 1000,
});

console.log(Object.isFrozen(record));
console.log(Object.isExtensible(record));

const attempts = [
  ["writing an existing field", () => { record.value = 30; }],
  ["adding a new field", () => { record.unit = "celsius"; }],
  ["deleting a field", () => { delete record.time; }],
  ["changing the definition", () => { Object.defineProperty(record, "value", { value: 30 }); }],
  ["changing the prototype", () => { Object.setPrototypeOf(record, null); }],
];

for (const [name, action] of attempts) {
  try {
    action();
    console.log(`${name}: passed`);
  } catch (error) {
    console.log(`${name}: ${error.constructor.name}`);
  }
}

console.log(JSON.stringify(record));
const descriptor = Object.getOwnPropertyDescriptor(record, "value");
console.log(`writable=${descriptor.writable}, configurable=${descriptor.configurable}`);
```

```
true
false
writing an existing field: TypeError
adding a new field: TypeError
deleting a field: TypeError
changing the definition: TypeError
changing the prototype: TypeError
{"sensor":"S-01","value":21.4,"time":1000}
writable=false, configurable=false
```

All five change paths are closed; even the prototype link is fixed. Because this lesson
runs in a module file — that is, in strict mode — the attempts throw errors. In
non-strict mode, the same operations fail silently — the same distinction as in the
previous lesson.

Freezing cannot be undone. There is no operation like `Object.unfreeze`, because
`Object.freeze` closes the `writable` and `configurable` flags together; once
configurability is closed, neither can be reopened. Freezing an object is a final decision
made on that object.

#### Freezing Is Shallow

`Object.freeze` touches only the object's **own** properties. Objects inside it and
definitions on the prototype fall outside its scope.

```js
const measurementBehavior = {
  format() {
    return `${this.sensor}: ${this.value}`;
  },
};

const record = Object.create(measurementBehavior);
record.sensor = "S-01";
record.value = 21.4;
record.source = { file: "measurement-2.csv", line: 17 };
Object.freeze(record);

console.log(Object.isFrozen(record));
console.log(Object.isFrozen(record.source));

record.source.line = 99;
console.log(record.source.line);

measurementBehavior.format = function () {
  return `[changed] ${this.sensor}`;
};
console.log(record.format());
console.log(Object.isFrozen(Object.getPrototypeOf(record)));
```

```
true
false
99
[changed] S-01
false
```

There are two leaks. The first is familiar: the record's `source` field is a reference;
what freezes is the reference itself, not the object it points to. This is the same
shallow-copy discussion from the Value and Reference and Passing lesson in the
Programming Fundamentals course.

The second concerns the prototype and is specific to this course. The record's
*prototype link* is frozen, but the *prototype object* is not. Changing a behavior on the
chain changes the frozen object's behavior. If both an object's data and its behavior
need to stay fixed, the prototype object has to be frozen separately.

#### The Scope of the Three Operations

Freezing is the strictest option; it has two looser forms. `Object.preventExtensions`
only forbids adding new properties; `Object.seal` adds deletion to that as well. Running
all three through the same tests is enough to see the difference.

```js
function check(name, obj) {
  const results = [];
  try {
    obj.value = 30;
    results.push(`write=${obj.value === 30 ? "yes" : "no"}`);
  } catch {
    results.push("write=error");
  }
  try {
    obj.unit = "celsius";
    results.push(`add=${Object.hasOwn(obj, "unit") ? "yes" : "no"}`);
  } catch {
    results.push("add=error");
  }
  try {
    delete obj.sensor;
    results.push(`delete=${Object.hasOwn(obj, "sensor") ? "no" : "yes"}`);
  } catch {
    results.push("delete=error");
  }
  console.log(`${name}: ${results.join(", ")}`);
}

check("open           ", { sensor: "S-01", value: 21.4 });
check("non-extensible ", Object.preventExtensions({ sensor: "S-01", value: 21.4 }));
check("sealed         ", Object.seal({ sensor: "S-01", value: 21.4 }));
check("frozen         ", Object.freeze({ sensor: "S-01", value: 21.4 }));
```

```
open           : write=yes, add=yes, delete=yes
non-extensible : write=yes, add=error, delete=yes
sealed         : write=yes, add=error, delete=error
frozen         : write=error, add=error, delete=error
```

The scopes are nested: preventing extensions ⊂ sealing ⊂ freezing. Sealing suits objects
whose field set is fixed but whose values can still change — like locking a
configuration record's shape while leaving its content free. Freezing makes the object
fixed in its entirety.

#### Deep Freezing

Getting past shallowness requires a recursive walk. The pattern from the Recursion lesson
in the Programming Fundamentals course applies directly: the base case is non-object
values, the reduction step is freezing each field itself. To keep circular references
from causing infinite recursion, visited objects are marked.

```js
function deepFreeze(obj, seen = new WeakSet()) {
  if (obj === null || typeof obj !== "object" || seen.has(obj)) {
    return obj;
  }
  seen.add(obj);
  for (const name of Object.getOwnPropertyNames(obj)) {
    const descriptor = Object.getOwnPropertyDescriptor(obj, name);
    if ("value" in descriptor) deepFreeze(descriptor.value, seen);
  }
  return Object.freeze(obj);
}

const record = deepFreeze({
  sensor: "S-01",
  value: 21.4,
  source: { file: "measurement-2.csv", line: 17 },
  tags: ["raw", "temperature"],
});

console.log(Object.isFrozen(record));
console.log(Object.isFrozen(record.source));
console.log(Object.isFrozen(record.tags));

try {
  record.source.line = 99;
} catch (error) {
  console.log(`inner object write: ${error.constructor.name}`);
}
try {
  record.tags.push("new");
} catch (error) {
  console.log(`array add: ${error.constructor.name}`);
}

console.log(JSON.stringify(record));
```

```
true
true
true
inner object write: TypeError
array add: TypeError
{"sensor":"S-01","value":21.4,"source":{"file":"measurement-2.csv","line":17},"tags":["raw","temperature"]}
```

Arrays are objects too, so they go through the same operation; adding to a frozen array
throws an error. Using `getOwnPropertyNames` ensures non-enumerable fields are covered
too; because accessor properties have no `value` field, the getter function is not run.
The choice of `WeakSet` is deliberate: the marking is temporary and should not extend the
lifetime of the objects being walked. This collection's details belong to the Advanced
Collections topic.

Deep freezing's cost is directly proportional to object count; applying it on every
creation is expensive for large trees. The common practice is to use freezing only on
shared, long-lived data.

#### Producing Instead of Changing

Once an object is frozen, an "update this field" request turns into a question: how is an
updated new object produced? Object spread looks sufficient for this, but it carries a
cost related to the chain.

```js
class MeasurementRecord {
  constructor(sensor, value, time) {
    this.sensor = sensor;
    this.value = value;
    this.time = time;
    Object.freeze(this);
  }
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  }
  withValue(newValue) {
    const copy = Object.create(Object.getPrototypeOf(this));
    Object.assign(copy, this, { value: newValue });
    return Object.freeze(copy);
  }
}

const original = new MeasurementRecord("S-01", 21.4, 1000);
const spread = { ...original, value: 30 };
const viaMethod = original.withValue(30);

console.log(original.format());
console.log(JSON.stringify(spread));
console.log(typeof spread.format);
console.log(Object.getPrototypeOf(spread) === MeasurementRecord.prototype);
console.log(Object.isFrozen(spread));

console.log(viaMethod.format());
console.log(Object.getPrototypeOf(viaMethod) === MeasurementRecord.prototype);
console.log(viaMethod instanceof MeasurementRecord);
console.log(Object.isFrozen(viaMethod));
console.log(original.value);
```

```
S-01@1000: 21.4
{"sensor":"S-01","value":30,"time":1000}
undefined
false
false
S-01@1000: 30
true
true
true
21.4
```

The object produced by spread carries the right data but **has lost its chain**: its
prototype becomes `Object.prototype`, `format` is not found, the `instanceof` result is
negative, and the object is not frozen. The sentence from the course's first lesson finds
its counterpart here — copying carries own properties, not the chain.

Because the `withValue` method sets up the chain with
`Object.create(Object.getPrototypeOf(this))`, the result is a genuine measurement record.
Using the current prototype instead of the constructor brings an extra gain too: when
called from a subclass, the subclass's prototype is preserved.

The last line confirms the pattern's purpose: the original record has not changed. When a
change is wanted, the old object stays in place and a new one is produced. This is the
object model's counterpart of the immutability approach introduced in the Introduction to
Functional Programming lesson of the Programming Fundamentals course.

#### Summary

- `Object.freeze` makes own properties non-writable and non-configurable, forbids adding
  new properties and changing the prototype; it cannot be undone.
- Freezing is shallow: inner objects and the prototype object are unaffected, so behavior
  can still be changed.
- The scopes are nested — preventing extensions, sealing, freezing; the middle option
  fixes the field set while leaving values free.
- Deep freezing is a recursive walk; it needs visit-marking against cycles and its cost
  grows with object count.
- An update on a frozen object turns into producing a new object; because object spread
  loses the chain, the prototype must be set up explicitly.

#### Next Step

Throughout this topic, methods always worked through `this`: `format` read the sensor
from the record, the accessor read the raw value from the object itself. But what `this`
turns out to be depends not on where the function is defined, but on **how it is
called** — a function coming from the chain can also run on an object it has never seen.
The next topic will separate these binding rules under four headings and examine the
cases where a method becomes detached from its object.

## Context and Closures

The four rules of this binding, explicit binding operations, lexical context in arrow functions, carrying state with closures, and using a function as a value.

### this Binding

Source: https://academia.sh/en/courses/javascript-object-model/this-binding

Context determined at the call site; the default, implicit, explicit, and constructor binding rules, their priority order, and a method detaching from its object.

Throughout the Prototype Model topic, methods worked through `this`, and this gave the
impression that a function somehow knows the object it was defined on. The impression is
wrong. As in the previous lesson's `Object.assign(copy, this, ...)` call, a function
sitting in a prototype chain can operate on an object it never appeared in at all.

The rule is this: the value of `this` is determined not by where the function is
**defined**, but by **how it is called**. The same function binds to four different
values under four different call forms. This lesson tells the four apart and builds the
priority order among them.

#### Default Binding

The plainest call form is calling a function with no object attached to it at all. In
strict mode, the `this` value is `undefined`.

```js
function readBinding() {
  return this;
}

console.log(readBinding() === undefined);
console.log(typeof globalThis);

const nonStrictReadBinding = new Function("return this;");
console.log(nonStrictReadBinding() === globalThis);
console.log(nonStrictReadBinding() === undefined);
```

```
true
object
true
false
```

In non-strict mode, the same call binds `this` to the global object; `globalThis` is
this object's standard name. The difference matters: a method called by accident
without its object throws an error immediately in strict mode, while in non-strict mode
it silently writes a property onto the global object. Because module files and class
bodies run in strict mode, the first is the behavior that applies throughout the
measurement record examples.

#### Implicit Binding

If a function is called through a property of an object, `this` binds to that object.
What decides this is the object **to the left** of the dot operator at the call site.

```js
const record = {
  sensor: "S-01",
  value: 21.4,
  time: 1000,
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
};

console.log(record.format());

const brokenFormat = record.format;
try {
  console.log(brokenFormat());
} catch (error) {
  console.log(`broken call: ${error.constructor.name}`);
}

const records = [record];
try {
  console.log(records.map(record.format)[0]);
} catch (error) {
  console.log(`callback: ${error.constructor.name}`);
}

console.log(records.map((r) => r.format())[0]);

const measurement = {
  sensor: "S-OUT",
  source: {
    sensor: "S-IN",
    format() {
      return `bind: ${this.sensor}`;
    },
  },
};
console.log(measurement.source.format());
```

```
S-01@1000: 21.4
broken call: TypeError
callback: TypeError
S-01@1000: 21.4
bind: S-IN
```

The second and third lines show the language's most commonly hit trap. The expression
`record.format` gives a function **value**; the bond with the object is not carried
inside this value. Once the value is assigned to a variable or passed as a callback,
the call site changes and binding falls back to the default rule. Because `this` is
`undefined` in strict mode, reading `this.sensor` throws an error.

This is a consequence of the observation in the Programming Fundamentals course's Value
and Reference, and Passing lesson: what is passed is the function itself, not the
context it sits in. The fix on the fourth line is the most direct one — writing a
wrapper that makes the call through the object.

The last line shows that chain length plays no role: only the **last** link counts. In
the call `measurement.source.format()`, `this` is the `source` object; the outer
`measurement` is never considered at all.

#### Explicit Binding

The `call`, `apply`, and `bind` operations state the context explicitly at the call
site. Their details are the next lesson's subject; here, only the fact that this rule
is a third form is shown.

```js
const measurementBehavior = {
  format() {
    return `${this.sensor}@${this.time}: ${this.value}`;
  },
};

const chained = Object.create(measurementBehavior);
chained.sensor = "S-01";
chained.value = 21.4;
chained.time = 1000;

const foreign = { sensor: "S-99", value: 5.5, time: 7 };

console.log(chained.format());
console.log(measurementBehavior.format.call(foreign));
console.log(chained.format.call(foreign));
console.log(Object.getPrototypeOf(foreign) === measurementBehavior);
```

```
S-01@1000: 21.4
S-99@7: 5.5
S-99@7: 5.5
false
```

The last line confirms this lesson's opening claim: the `foreign` object's chain
carries no measurement behavior, yet the same function works on its data without
trouble. The prototype chain determines **where the function is found**; `this`
determines **which data it works on**. The two are independent of each other.

#### Constructor Binding

The `new` operator binds `this` to the newly created object, in the second of the
steps ordered in the second lesson.

```js
function MeasurementRecord(sensor, value) {
  this.sensor = sensor;
  this.value = value;
}
MeasurementRecord.prototype.format = function () {
  return `${this.sensor}: ${this.value}`;
};

const viaConstructor = new MeasurementRecord("S-01", 21.4);
console.log(viaConstructor.format());
console.log(Object.getPrototypeOf(viaConstructor) === MeasurementRecord.prototype);

try {
  MeasurementRecord("S-02", 19.8);
} catch (error) {
  console.log(`without new: ${error.constructor.name}`);
}

class ClassRecord {
  constructor(sensor) {
    this.sensor = sensor;
  }
  format() {
    return `class: ${this.sensor}`;
  }
}

const instance = new ClassRecord("S-03");
console.log(instance.format());

const broken = instance.format;
try {
  broken();
} catch (error) {
  console.log(`class method broken: ${error.constructor.name}`);
}
```

```
S-01: 21.4
true
without new: TypeError
class: S-03
class method broken: TypeError
```

The third line shows how calling a constructor function without `new` turns out in
strict mode: because `this` is `undefined`, the first assignment throws an error. Class
syntax forbidding this call outright exists to raise the error at the call itself rather
than partway through the body.

The last line repeats an important point: class syntax does not solve the detachment
problem. Class methods are also plain functions sitting on the prototype, and they lose
their binding the same way once separated from their object.

#### Losing Context in a Callback

The four rules also explain where errors come from. When a method passes a function
into another function inside its body, that function becomes a **separate call**; the
outer method's context does not carry over to it.

```js
const collector = {
  unit: "C",
  measurements: [21.4, 22.8, 20.1],
  formattedList() {
    return this.measurements.map(function (value) {
      return `${value}${this.unit}`;
    });
  },
  formattedListThisArg() {
    return this.measurements.map(function (value) {
      return `${value}${this.unit}`;
    }, this);
  },
};

try {
  console.log(collector.formattedList());
} catch (error) {
  console.log("in callback:", error.constructor.name, "-", error.message);
}

console.log(collector.formattedListThisArg());
```

```
in callback: TypeError - Cannot read properties of undefined (reading 'unit')
[ '21.4C', '22.8C', '20.1C' ]
```

In the first call, the callback function falls to default binding; because `this` is
undefined in strict mode, reading the property throws an error. Some array methods
take a second parameter that binds the callback to a given object; the second call
uses it.

This parameter is not present in every interface. The general fix is one of two steps:
fixing context with explicit binding (next lesson) or using a function form that
establishes no `this` binding of its own (third lesson). The root of the problem is
that the call site is independent of where the function was defined.

#### Priority Order

When more than one rule looks like it applies at once, which one wins is fixed:
constructor binding overrides explicit binding; explicit binding overrides implicit
binding; implicit binding overrides default binding.

```js
function writeSensor(sensor) {
  this.sensor = sensor;
  return this;
}

const target = { sensor: "start" };
const otherTarget = { sensor: "other" };

const bound = writeSensor.bind(target);

bound("explicit-bound");
console.log(target.sensor);

otherTarget.run = bound;
otherTarget.run("implicit-attempt");
console.log(target.sensor);
console.log(otherTarget.sensor);

const produced = new bound("constructor");
console.log(produced.sensor);
console.log(target.sensor);
console.log(Object.getPrototypeOf(produced) === writeSensor.prototype);

const obj = {
  sensor: "implicit",
  read() {
    return this.sensor;
  },
};
console.log(obj.read());
console.log(obj.read.call(otherTarget));
```

```
explicit-bound
implicit-attempt
other
constructor
implicit-attempt
true
implicit
other
```

The second and third lines say explicit binding beats implicit binding: even though the
bound function is called as a property of an object, the write lands on `target`,
`otherTarget` stays unchanged. The fourth and fifth lines show constructor binding
beating explicit binding: called with `new`, the bound target is ignored, a new object
is used, and the chain is set up to the constructor's `prototype` object.

The order can be summarized in one sentence: **`new` > `bind`/`call`/`apply` > dot
operator > none.** There is a fifth case — functions that fit none of the rules and
have no `this` value of their own — and it will be covered two lessons from now.

#### Summary

- The `this` value is determined by looking at how a function is called, not where it
  is defined.
- In default binding, `this` is `undefined` in strict mode and the global object in
  non-strict mode.
- In implicit binding, the object to the left of the dot operator is bound; only the
  last link counts.
- A method's bond breaks when it is assigned to a variable or passed as a callback;
  class methods are not exempt from this behavior.
- The prototype chain determines where a function will be found, `this` determines
  which data it operates on; the two are independent.
- Priority order: constructor binding, explicit binding, implicit binding, default
  binding.

#### Next Step

In this lesson, explicit binding was introduced only as a rule; the differences among
the three operations that establish it were not covered. How do `call` and `apply` pass
arguments differently, why does `bind` produce a permanent bond, and how do these
operations lend a method not in an object's chain to that object? The next lesson
examines these three operations and the function-borrowing pattern.

### call, apply, and bind

Source: https://academia.sh/en/courses/javascript-object-model/call-apply-bind

Three operations that determine context at the call site, argument-passing forms, the function-borrowing pattern, partial application, and the identity cost of a permanent bond.

The previous lesson introduced explicit binding as a rule and showed that the
prototype chain determines where a function will be found while `this` determines
which data it operates on. Because the two are independent, an object can be run with
a method it has never heard of.

Three operations use this independence. `call` and `apply` fix context only for that
one call and run the function immediately; `bind` produces a **new function** that
carries the bond permanently. This lesson covers the differences among the three, the
borrowing pattern, and the cost of a permanent bond.

#### Passing Arguments

The only difference between `call` and `apply` is how arguments are given: `call`
takes them one by one, `apply` as an array.

```js
const measurementBehavior = {
  summarize(prefix, separator) {
    return `${prefix}${separator}${this.sensor}${separator}${this.value}`;
  },
};

const record = { sensor: "S-01", value: 21.4 };
const args = ["measurement", " | "];

console.log(measurementBehavior.summarize.call(record, "measurement", " | "));
console.log(measurementBehavior.summarize.apply(record, args));
console.log(measurementBehavior.summarize.call(record, ...args));

const values = [21.4, 19.8, 25.1, 18.2];
console.log(Math.max.apply(null, values));
console.log(Math.max(...values));

try {
  measurementBehavior.summarize.call(null, "measurement", "-");
} catch (error) {
  console.log(`null context: ${error.constructor.name}`);
}
```

```
measurement | S-01 | 21.4
measurement | S-01 | 21.4
measurement | S-01 | 21.4
25.1
25.1
null context: TypeError
```

Once the spread operator entered the language, `apply`'s argument-unpacking role became
largely unnecessary; the third and fifth lines do the same job with spread. `apply`
still offers a direct path when the argument list is computed at runtime and held as an
array.

The last line fixes a detail: in strict mode, a `null` or `undefined` context is not
converted to the global object, it is passed as is. This is why
`Math.max.apply(null, ...)` works — `Math.max` never uses its `this` value — but a
function that reads `this` throws an error in the same kind of call.

#### Function Borrowing

Explicit binding's real use is **function borrowing**: running a method not present in
an object's chain, on that object.

```js
const arrayLike = { 0: "S-01", 1: "S-02", 2: "S-03", length: 3 };

console.log(Array.prototype.join.call(arrayLike, ";"));
console.log(Array.prototype.map.call(arrayLike, (a) => a.toLowerCase()).join(","));
console.log(Array.from(arrayLike).join(","));
console.log(Array.isArray(arrayLike));

const dict = Object.create(null);
dict.sensor = "S-01";

console.log(typeof dict.hasOwnProperty);
console.log(Object.prototype.hasOwnProperty.call(dict, "sensor"));
console.log(Object.hasOwn(dict, "sensor"));

console.log(Object.prototype.toString.call([1, 2]));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(new Date(0)));
console.log(Object.prototype.toString.call({ sensor: "S-01" }));
```

```
S-01;S-02;S-03
s-01,s-02,s-03
S-01,S-02,S-03
false
undefined
true
true
[object Array]
[object Null]
[object Date]
[object Object]
```

The first three lines show **array-like** objects: objects carrying numeric keys and a
`length` property, but with no `Array.prototype` in their chain. Array methods only
look at these two things, so they can be borrowed. The fourth line confirms the object
is not really an array. `Array.from` does the same job more readably and is preferred
over borrowing in most cases.

The fifth and sixth lines connect to the first lesson's null-prototype dictionary. Such
an object has no `hasOwnProperty`, because it has no chain; the check can only be made
by borrowing from `Object.prototype`. `Object.hasOwn` is this borrowing built into the
language, giving the same result with less indirection.

The last four lines take advantage of the fact that `Object.prototype.toString`
produces a distinctive tag for every value type. This borrowing tells apart cases
`typeof` cannot — array versus object, `null` versus object.

#### Permanent Bond and Partial Application

`bind` does not run the function; it returns a **new function** with the context, and
optionally the leading arguments, fixed. Fixing arguments in advance is called
**partial application**.

```js
function calibrate(offset, multiplier, rawValue) {
  return (rawValue + offset) * multiplier;
}

const sensorS01 = calibrate.bind(null, -0.4, 1.02);

console.log(calibrate(-0.4, 1.02, 21.4).toFixed(3));
console.log(sensorS01(21.4).toFixed(3));
console.log(sensorS01(19.8).toFixed(3));

console.log(calibrate.length);
console.log(sensorS01.length);
console.log(sensorS01.name);

const record = {
  sensor: "S-01",
  value: 21.4,
  format() {
    return `${this.sensor}: ${this.value}`;
  },
};

const boundFormat = record.format.bind(record);
const rebound = boundFormat.bind({ sensor: "S-99", value: 0 });

console.log(boundFormat());
console.log(rebound());
```

```
21.420
21.420
19.788
3
1
bound calibrate
S-01: 21.4
S-01: 21.4
```

Partial application is the direct way to produce a separate calibration function for
each sensor: the fixed parameters are given once, the varying one on every call. The
function's `length` property drops by the number of fixed arguments; its `name`
property states that it was derived.

The last two lines show a critical rule: **a bound function cannot be rebound.** The
second `bind` call produces a new function, but the inner bond does not change, so the
result is unchanged. This same rule was part of the previous lesson's priority order;
`new` is its one exception.

Formatting the floating-point outputs with `toFixed` is deliberate: printing the
expression `(21.4 - 0.4) * 1.02` directly would also show digits coming from the
binary-fraction representation. The How Computers Work course's Floating-Point Numbers
lesson explains this behavior.

#### Preserving Context in Callbacks

The place the previous lesson's detachment problem shows up most is a method being
passed as a callback. There are three fixes, and all three give the same result.

```js
class MeasurementCollector {
  constructor(sensor) {
    this.sensor = sensor;
    this.values = [];
  }
  add(value) {
    this.values.push(value);
  }
  summary() {
    return `${this.sensor}: ${this.values.join(",")}`;
  }
}

const collector = new MeasurementCollector("S-01");
const incoming = [21.4, 19.8, 25.1];

try {
  incoming.forEach(collector.add);
} catch (error) {
  console.log(`no context: ${error.constructor.name}`);
}

incoming.forEach(collector.add.bind(collector));
console.log(collector.summary());

const second = new MeasurementCollector("S-02");
incoming.forEach(second.add, second);
console.log(second.summary());

const third = new MeasurementCollector("S-03");
incoming.forEach((value) => third.add(value));
console.log(third.summary());
```

```
no context: TypeError
S-01: 21.4,19.8,25.1
S-02: 21.4,19.8,25.1
S-03: 21.4,19.8,25.1
```

The first fix produces a permanent bond with `bind`. The second uses the context
argument some array methods, such as `forEach`, take as a second parameter — this
option is also found in `map`, `filter`, `some`, and `every`, but not in every
higher-order function. The third wraps the call in an arrow function and requires no
binding at all; the reason is the next lesson's subject.

#### The Identity Cost of a Bond

Every time `bind` is called, it produces a new function object. This has consequences
everywhere identity comparison is relied on.

```js
const record = {
  sensor: "S-01",
  format() {
    return `record: ${this.sensor}`;
  },
};

const first = record.format.bind(record);
const second = record.format.bind(record);

console.log(first() === second());
console.log(first === second);
console.log(first === record.format);

class Listener {
  constructor(name) {
    this.name = name;
    this.handle = this.handle.bind(this);
  }
  handle(event) {
    return `${this.name} <- ${event}`;
  }
}

const a = new Listener("A");
const b = new Listener("B");

console.log(a.handle("measurement"));
console.log(Object.hasOwn(a, "handle"));
console.log(a.handle === b.handle);
console.log(Listener.prototype.handle === Object.getPrototypeOf(a).handle);
console.log(a.handle === Listener.prototype.handle);
```

```
true
false
false
A <- measurement
true
false
true
false
```

Two `bind` calls give two functions that produce the same result but are not equal to
each other. Because removing an event listener after registering it requires the same
function reference, the result of `bind` must be stored; a function rebound each time
cannot be removed.

The second part shows the effect binding done in a class constructor has on the
prototype model. The line `this.handle = this.handle.bind(this)` creates an **own
property** that shadows the prototype's method. The result: every instance carries its
own bound function, sharing across instances is lost, and the number of functions in
memory grows with the instance count. The original definition on the prototype stays
in place but is no longer used.

This is the reverse side of the first lesson's trade-off: shared behavior is cheap
when it stays on the chain, expensive but context-guaranteed once copied to the
instance. The Closures lesson will cover this trade-off again, in a more general form.

#### Summary

- `call` and `apply` fix context only for that one call; the only difference is
  whether arguments are given one by one or as an array.
- In strict mode, a `null` or `undefined` context is not converted to the global
  object.
- Function borrowing runs a method on an object it is not in the chain of;
  array-like objects and null-prototype dictionaries are the typical cases.
- `bind` produces a new function, can partially fix arguments, and the bond it
  produces cannot be replaced by rebinding.
- Every `bind` call produces a new identity; for removable listeners, the result must
  be stored.
- Binding done in a constructor shadows the prototype's method with a per-instance
  copy and removes sharing.

#### Next Step

In the previous section, the third fix for the callback problem was an arrow function,
and it required no binding operation at all. The reason is that arrow functions do not
take their `this` value from the call site — they have no context of their own. The
next lesson examines this behavior, the convenience it provides, and the cases where an
arrow function cannot be used as a method.

### Context in Arrow Functions

Source: https://academia.sh/en/courses/javascript-object-model/context-in-arrow-functions

Arrow functions having no context of their own, lexical resolution, the absence of a constructor and prototype, the cost of arrows defined as class fields, and cases of misuse.

In the previous lesson's last example, the third fix for the callback problem was an
arrow function, and it required no binding operation at all. The reason is that an
arrow function **does not take** its `this` value from the call site.

Arrow functions have no `this` binding of their own. The `this` used in their body is
resolved like an ordinary variable: searched from the inside out, through the scope
chain of where it was defined. The lexical scope rule covered in the Programming
Foundations course's Scope and Lifetime lesson applies here to `this` as well. This one
difference is why none of the previous lesson's four rules apply to arrow functions.

#### A Function With No Context of Its Own

The shortest way to see the distinction is to place the two definition forms side by
side on the same object.

```js
const recordWithMethod = {
  sensor: "S-01",
  value: 21.4,
  format() {
    return `${this.sensor}: ${this.value}`;
  },
};

const recordWithArrow = {
  sensor: "S-02",
  value: 19.8,
  format: () => {
    return this === undefined ? "this: undefined" : "this: something else";
  },
};

console.log(recordWithMethod.format());
console.log(recordWithArrow.format());

const brokenArrow = recordWithArrow.format;
console.log(brokenArrow());
console.log(recordWithArrow.format.call(recordWithMethod));
```

```
S-01: 21.4
this: undefined
this: undefined
this: undefined
```

An object literal establishes no scope. Because the arrow function is defined in the
module body, its `this` value comes from there, and in the module body this value is
`undefined`. Calling it through the object changes nothing.

The last line settles the rule: even `call` cannot change an arrow function's context.
Because there is no place to bind to, explicit binding silently has no effect — and
throws no error either. `bind` is just as ineffective in the same way; it produces a
new function but does not touch the inner resolution of `this`.

#### The Benefit of Lexical Resolution

Having no context of its own looks like a shortcoming; its real value shows up in
callbacks given from inside a method. The method's `this` value is automatically
carried into an arrow function defined inside it.

```js
class MeasurementSeries {
  constructor(sensor, values) {
    this.sensor = sensor;
    this.values = values;
  }

  labeledArrow() {
    return this.values.map((value) => `${this.sensor}:${value}`).join(" ");
  }

  labeledFunctionExpr() {
    return this.values
      .map(function (value) {
        return `${this.sensor}:${value}`;
      })
      .join(" ");
  }

  labeledFallback() {
    const self = this;
    return this.values
      .map(function (value) {
        return `${self.sensor}:${value}`;
      })
      .join(" ");
  }
}

const series = new MeasurementSeries("S-01", [21.4, 19.8]);

console.log(series.labeledArrow());
try {
  console.log(series.labeledFunctionExpr());
} catch (error) {
  console.log(`function expression: ${error.constructor.name}`);
}
console.log(series.labeledFallback());
```

```
S-01:21.4 S-01:19.8
function expression: TypeError
S-01:21.4 S-01:19.8
```

The function expression in the second method establishes its own `this` binding, and
because it is called as a callback, this binding falls to the default rule; in strict
mode it is `undefined` and the read throws an error. The third method copies the
context into an ordinary variable — this is the common fix from before arrow functions
entered the language, and it still works correctly. The first method does the same job
without an extra name.

The rule can be summarized as: **if the caller provides no context, an arrow function
is the right choice.** Callbacks, functions given to array methods, and helper
functions defined inside a method all fit this description.

#### The Other Things That Are Missing

`this` is not the only thing missing from arrow functions. The `arguments` object,
`new.target`, and the `prototype` property are not present either. The last of these
is the direct reason arrow functions cannot be used as constructors.

```js
const arrowCalibrate = (value) => value * 1.02;
function functionCalibrate(value) {
  return value * 1.02;
}

console.log("prototype" in arrowCalibrate);
console.log("prototype" in functionCalibrate);
console.log(typeof arrowCalibrate);

try {
  new arrowCalibrate(1);
} catch (error) {
  console.log(`new with arrow: ${error.constructor.name}`);
}

function outerFunction() {
  const innerArrow = () => arguments[0];
  return innerArrow();
}
console.log(outerFunction("outer-arg"));

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

console.log(arrowCalibrate.name);
console.log(arrowCalibrate.length);
```

```
false
true
function
new with arrow: TypeError
outer-arg
3
arrowCalibrate
1
```

The second lesson wrote the first step of the `new` operator as "create an empty
object whose prototype is bound to the function's `prototype` object." Because no such
object exists for an arrow function, that step can never run at all; the error is a
consequence of this absence.

`arguments` is also resolved lexically: used inside an arrow function, it gives the
arguments of the function that wraps it. Rather than relying on this behavior, rest
parameters should be used; they work the same way in both arrow functions and regular
functions and produce a real array. The last two lines show that arrow functions still
carry ordinary function properties like name and parameter count.

#### Arrow Function as a Class Field

Assigning an arrow function to a field in a class body solves the binding problem
definitively. Its cost is paid in the prototype model.

```js
class MeasurementCollector {
  constructor(sensor) {
    this.sensor = sensor;
    this.values = [];
  }

  add(value) {
    this.values.push(value);
    return this.values.length;
  }

  addArrow = (value) => {
    this.values.push(value);
    return this.values.length;
  };
}

const a = new MeasurementCollector("S-01");
const b = new MeasurementCollector("S-02");

console.log(Object.getOwnPropertyNames(MeasurementCollector.prototype).join(","));
console.log(Object.getOwnPropertyNames(a).join(","));
console.log(a.add === b.add);
console.log(a.addArrow === b.addArrow);

const detachedArrow = a.addArrow;
console.log(detachedArrow(21.4));
console.log(a.values.join(","));

console.log(a.addArrow.call(b, 99));
console.log(a.values.join(","));
console.log(b.values.join(",") || "(empty)");
```

```
constructor,add
addArrow,sensor,values
true
false
1
21.4
2
21.4,99
(empty)
```

The first two lines show the distinction: `add` sits on the prototype, `addArrow` is
every instance's own property. The third and fourth lines measure the consequence —
the prototype's method is the same function across every instance, the arrow field is
separate on every instance.

The fifth and sixth lines show the gain: the arrow field, detached from its object,
keeps writing to the correct collector. The last three lines are the other side of that
same trait — trying to redirect it to another collector with `call` has no effect; the
value is still written to object `a`.

The trade-off is clear. The arrow field guarantees context but produces one function
object per instance, removes sharing, and makes redirection impossible. The
prototype's method is shared and redirectable but entrusts context to the call site.
The `bind` pattern in the previous lesson's constructor is another spelling of this
same trade-off.

#### Where the Arrow Function Is Wrong

As a rule: **if a function needs to reach its object through `this`, it cannot be an
arrow function.** This covers methods in object literals and behavior written onto a
prototype.

```js
const measurementBehavior = {
  format: () => `${this?.sensor}`,
};

const record = Object.create(measurementBehavior);
record.sensor = "S-01";
console.log(record.format());

const correctBehavior = {
  format() {
    return `${this.sensor}`;
  },
};
const correctRecord = Object.create(correctBehavior);
correctRecord.sensor = "S-01";
console.log(correctRecord.format());

const counterFactory = () => {
  let count = 0;
  return {
    increment: () => (count += 1),
    read: () => count,
  };
};

const counter = counterFactory();
counter.increment();
counter.increment();
console.log(counter.read());
```

```
undefined
S-01
2
```

The first record correctly has its prototype set, but the function in the chain reads
`this` not from the chain but from its own defining scope; the result is `undefined`.
Correct behavior is obtained just by changing the definition form.

The last section previews the second pattern where arrow functions are used correctly
in advance: when an object's data is held in a **closed-over variable** instead of
`this`, `this` is never needed at all and the binding problem disappears. This setup is
called a closure and is the next lesson's subject.

#### Summary

- Arrow functions have no `this` binding of their own; the `this` in their body is
  resolved lexically in the scope they are defined in.
- `call`, `apply`, and `bind` cannot change an arrow function's context; they silently
  have no effect.
- An arrow function automatically carries context in callbacks given from inside a
  method; it is the right choice when the caller provides no context.
- Arrow functions have no `prototype`, `arguments`, or `new.target`; this is why they
  cannot be used as constructors.
- An arrow function defined as a class field produces one copy per instance: it
  guarantees context but loses sharing and redirectability.
- Methods in object literals and behavior written onto a prototype cannot be arrow
  functions.

#### Next Step

In the last example, a counter kept its state not in an object property but in a local
variable of the function that produced it. That variable kept living even after the
function returned, and was visible only to the functions returned from it. The next
lesson covers this structure — closures; it examines the lifetime of closed-over
variables and compares this way of hiding state with the prototype chain.

### Closures

Source: https://academia.sh/en/courses/javascript-object-model/closures

The lifetime and sharing of closed-over variables, encapsulation through closures, comparing three constructions of the same kind, the loop-variable trap, and the memoization pattern.

In the previous lesson's last example, a counter kept its state not in an object
property but in a local variable of the function that produced it. The function
returned, but the variable kept living and was visible only to the functions returned
from it.

A **closure** is a function's ability to keep reaching the variables of the scope it
was defined in, even after that scope has ended. The Programming Fundamentals course's
Scope and Lifetime lesson introduced the concept; this lesson places it inside the
object model. The result that follows is decisive for the course's axis: a closure
offers a second way of construction that hides state without ever touching the
prototype chain.

#### Encapsulation With a Closure

This time, the measurement record is produced with a factory function. The raw value
and calibration offset stay in the function's parameters; they are never written onto
the returned object at all.

```js
function createMeasurementRecord(sensor, rawValue, offset) {
  let readCount = 0;

  function readValue() {
    readCount += 1;
    return rawValue + offset;
  }

  return {
    sensor,
    read: readValue,
    format() {
      return `${sensor}: ${readValue().toFixed(2)}`;
    },
    check() {
      return `reads=${readCount}`;
    },
  };
}

const record = createMeasurementRecord("S-01", 21.4, -0.4);

console.log(record.read().toFixed(2));
console.log(record.format());
console.log(record.check());
console.log(JSON.stringify(record));
console.log(Object.keys(record).join(","));
console.log(record.rawValue);
console.log(record.readCount);
```

```
21.00
S-01: 21.00
reads=2
{"sensor":"S-01"}
sensor,read,format,check
undefined
undefined
```

The raw value, the offset, and the read counter appear in no listing operation; they
do not enter serialization; they cannot be read or written from outside. This is the
same result as class syntax's private fields, but the mechanism is entirely
different: privacy comes not from property descriptors but from scope. A variable is
accessible only to the functions that can see it, so the scope boundary itself
provides encapsulation.

The check counter reading `2` shows that the `format` call used the same inner
function too: all three functions share the same scope — and therefore the same
variables.

#### Three Constructions of the Same Kind

The measurement record used throughout the course can now be built three separate
ways. Placing all three side by side makes the trade-off visible in a single output.

```js
function plainRecord(sensor, value) {
  return {
    sensor,
    value,
    format() {
      return `${this.sensor}: ${this.value}`;
    },
  };
}

function closureRecord(sensor, value) {
  return {
    format() {
      return `${sensor}: ${value}`;
    },
  };
}

class ClassRecord {
  constructor(sensor, value) {
    this.sensor = sensor;
    this.value = value;
  }
  format() {
    return `${this.sensor}: ${this.value}`;
  }
}

const plainA = plainRecord("S-01", 21.4);
const plainB = plainRecord("S-02", 19.8);
const closureA = closureRecord("S-01", 21.4);
const closureB = closureRecord("S-02", 19.8);
const classA = new ClassRecord("S-01", 21.4);
const classB = new ClassRecord("S-02", 19.8);

console.log([plainA.format(), closureA.format(), classA.format()].join(" | "));

console.log(`plain  : shared=${plainA.format === plainB.format}, own=${Object.keys(plainA).join("+")}, json=${JSON.stringify(plainA)}`);
console.log(`closure: shared=${closureA.format === closureB.format}, own=${Object.keys(closureA).join("+")}, json=${JSON.stringify(closureA)}`);
console.log(`class  : shared=${classA.format === classB.format}, own=${Object.keys(classA).join("+")}, json=${JSON.stringify(classA)}`);

console.log(Object.getPrototypeOf(plainA) === Object.prototype);
console.log(Object.getPrototypeOf(closureA) === Object.prototype);
console.log(Object.getPrototypeOf(classA) === ClassRecord.prototype);

const brokenClass = classA.format;
const brokenClosure = closureA.format;
try {
  brokenClass();
} catch (error) {
  console.log(`class broken: ${error.constructor.name}`);
}
console.log(`closure broken: ${brokenClosure()}`);
```

```
S-01: 21.4 | S-01: 21.4 | S-01: 21.4
plain  : shared=false, own=sensor+value+format, json={"sensor":"S-01","value":21.4}
closure: shared=false, own=format, json={}
class  : shared=true, own=sensor+value, json={"sensor":"S-01","value":21.4}
true
true
true
class broken: TypeError
closure broken: S-01: 21.4
```

The first line confirms all three constructions produce **the same behavior**. The
remaining lines list the differences:

- **Sharing.** Only in the class construction is the function a single one; in the
  other two, every object carries its own function. For a thousand records, the class
  produces one function, the others a thousand.
- **Visibility.** In the closure construction, since the data is never a property at
  all, serialization gives an empty object. If the data needs to leave, an explicit
  method has to be written.
- **Chain.** Because factory functions return an ordinary object literal, their
  prototype is `Object.prototype`; none of the paths for type querying, adding shared
  behavior, or deriving a subtype works.
- **Binding.** In the closure construction, since `this` is never used, the method
  keeps working even when detached from its object; a class method throws an error in
  the same situation.

This is where the selection criterion comes from. If many objects, type queries,
subtypes, and shared behavior are needed, a class; if a small number of objects, strict
privacy, and context-independent calls are needed, a closure. The two are not
interchangeable options but two constructions with different cost profiles.

#### The Lifetime of a Closed-Over Variable

A closure closes over not a **value** but a **variable**. Functions sharing the same
scope see the same variable; closures produced from different calls have separate
scopes.

```js
function createMeasurementCounter(start) {
  let count = start;
  return {
    increment: () => (count += 1),
    decrement: () => (count -= 1),
    read: () => count,
  };
}

const first = createMeasurementCounter(0);
const second = createMeasurementCounter(100);

first.increment();
first.increment();
second.decrement();

console.log(first.read());
console.log(second.read());

function createBuffer() {
  const values = [];
  return {
    add(value) {
      values.push(value);
      return values.length;
    },
    average() {
      if (values.length === 0) return 0;
      return values.reduce((total, v) => total + v, 0) / values.length;
    },
    size: () => values.length,
  };
}

const buffer = createBuffer();
buffer.add(21.4);
buffer.add(19.8);
buffer.add(25.1);

console.log(buffer.size());
console.log(buffer.average().toFixed(3));
console.log(Object.keys(buffer).join(","));
```

```
2
99
3
22.100
add,average,size
```

The two counters are independent of each other; every `createMeasurementCounter` call
produces a new scope. In contrast, the three functions returned from the same call
share a single `count` variable — when one changes it, the other sees the change.

The lifetime rule follows from this: **a closed-over variable lives as long as the
last function that can see it remains reachable.** After the `createBuffer` function
returns, there is no outside reference to the `values` array at all, but because the
three functions can see it, the array stays in memory. This is where a closure's
memory cost comes from: storing a small function that closes over a large data
structure keeps that entire data structure alive. Memory lifecycle and
reachability-based collection are the subject of the Asynchronous JavaScript and the
Runtime course.

#### The Loop Variable Trap

The best-known consequence of the rule that a closure closes over a variable shows up
in functions produced inside a loop.

```js
const varFns = [];
for (var counter = 0; counter < 3; counter += 1) {
  varFns.push(() => `sensor-${counter}`);
}

const letFns = [];
for (let counter = 0; counter < 3; counter += 1) {
  letFns.push(() => `sensor-${counter}`);
}

console.log(varFns.map((f) => f()).join(","));
console.log(letFns.map((f) => f()).join(","));

const manualFns = [];
for (var i = 0; i < 3; i += 1) {
  manualFns.push(
    (function (fixed) {
      return () => `sensor-${fixed}`;
    })(i),
  );
}
console.log(manualFns.map((f) => f()).join(","));
```

```
sensor-3,sensor-3,sensor-3
sensor-0,sensor-1,sensor-2
sensor-0,sensor-1,sensor-2
```

The counter declared with `var` is function-scoped: there is a **single variable**
across the whole loop, and all three closures close over it. Because the variable's
value is `3` when the loop ends, all three functions give the same result. `let`
produces a new binding on every iteration, so each closure closes over its own
variable.

The third block shows the fix used before block-scoped declarations entered the
language: opening a new scope with an immediately invoked function on every iteration
and fixing the counter as a parameter. This pattern is the next lesson's subject.

#### A State-Carrying Wrapper

A closure's most productive use is adding state to a function without modifying it.
**Memoization**, introduced in the Programming Fundamentals course's Recursion lesson,
is exactly this: computed results are stored in a dictionary held in the closure.

```js
function memoize(fn) {
  const cache = Object.create(null);
  let realCalls = 0;

  function wrapped(key) {
    if (key in cache) return cache[key];
    realCalls += 1;
    const result = fn(key);
    cache[key] = result;
    return result;
  }

  wrapped.realCallCount = () => realCalls;
  return wrapped;
}

const openTable = { "S-01": -0.4, "S-02": 0.15, "S-03": 0 };
const safeTable = Object.assign(Object.create(null), openTable);

const openRead = memoize((sensor) => openTable[sensor] ?? 0);
const safeRead = memoize((sensor) => safeTable[sensor] ?? 0);

console.log(openRead("S-01"));
console.log(openRead("S-01"));
console.log(openRead("S-02"));
console.log(openRead("S-01"));
console.log(openRead.realCallCount());

console.log(typeof openRead("toString"));
console.log(typeof safeRead("toString"));
console.log(safeRead("toString"));
console.log(safeRead.realCallCount());
console.log(typeof openRead.cache);
```

```
-0.4
-0.4
0.15
-0.4
2
function
number
0
1
undefined
```

Only two of the four calls actually run the underlying function; the rest come back
from the cache. The counter is attached to the function as a property, but the counter
itself still sits in the closure — the last line confirms the `cache` dictionary is
not visible from outside.

The sixth and seventh lines show the cost of the first lesson's warning. In a
calibration table built with a plain object literal, asking for the `"toString"` key
finds the function coming from the chain, and the `??` operator does not treat it as
empty. In a table built with `Object.create(null)` there is no chain, so the correct
result comes back. The same precaution is required in any mapping whose keys come from
outside the program; the type the language sets aside for this job, `Map`, is covered
in the Advanced Collections topic.

#### Summary

- A closure is a function's ability to keep reaching the variables of the scope it was
  defined in, after that scope has ended.
- The privacy a closure establishes comes not from property descriptors but from the
  scope boundary; because the data is never a property, it is not listed and not
  serialized.
- A closure construction is context-independent but shares no behavior and sets up no
  prototype chain; a class construction is the opposite.
- A closure closes over a variable, not a value; functions produced from the same
  scope share the same variable, separate calls produce independent scopes.
- A closed-over variable lives as long as the last function that can see it is
  reachable; this is where memory cost comes from.
- A loop counter declared with `var` is a single variable; `let` produces a new
  binding on every iteration.

#### Next Step

In the loop trap's old fix, a function was called the instant it was defined, and it
was used only to open a new scope. This pattern has a name, and it was also the only
way to draw library boundaries before block-scoped declarations. The next lesson
covers immediately invoked functions, the scope-isolation job they do, and how far
that job has been taken over by the language's later abilities.

### Immediately Invoked Functions

Source: https://academia.sh/en/courses/javascript-object-model/immediately-invoked-functions

The spellings that turn a function declaration into an expression, scope isolation and the revealing module pattern, one-time setup, and comparison with block scope.

In the previous lesson's old fix for the loop trap, a function was called the instant
it was defined. Its purpose was not to produce a value but to open a new scope. This
pattern's name is the **immediately invoked function expression**.

The pattern does two jobs: it isolates the declarations in its body from the outer
scope, and it hands only selected values back out from the inside. It can also be read
as the Closures lesson's factory functions, run once for a single object.

#### Turning a Function Into an Expression

When the parser sees the word `function` at the start of a statement, it treats it as a
**declaration**, and declarations cannot be immediately invoked. This is why the
pattern's wrapping parentheses exist: they move the function out of statement position
and into expression position.

```js
try {
  new Function("function () { return 1; }();");
} catch (error) {
  console.log(`as declaration: ${error.constructor.name}`);
}

const first = (function () {
  return "wrapped call";
})();

const second = (function () {
  return "closed from inside";
}());

const third = (() => "arrow form")();

const fourth = void (function () {
  console.log("converted to expression by operator");
})();

console.log(first);
console.log(second);
console.log(third);
console.log(fourth);
```

```
as declaration: SyntaxError
converted to expression by operator
wrapped call
closed from inside
arrow form
undefined
```

The first attempt shows that the declaration form cannot be parsed; because
`new Function` parses the text, the error can be caught at runtime. The remaining
three forms are valid and behave identically: the call parentheses can sit inside or
outside the wrapper, and an arrow function does the same job.

The fourth form shows forcing expression position with an operator. Because the `void`
operator makes the result `undefined`, it used to be preferred where the return value
is not used. The output order also reveals a detail: the print inside the body runs
before the `console.log(first)` line, because the function is called immediately where
it is defined.

#### Scope Isolation and the Module Pattern

The pattern's real use is gathering a group of related functions around shared, hidden
state. What is defined inside is not visible from outside; only the names on the
returned object become accessible. This construction is called the **revealing module
pattern**.

```js
const measurementLog = (function () {
  const records = [];
  let nextOrder = 1;

  function add(sensor, value) {
    const record = { order: nextOrder, sensor, value };
    nextOrder += 1;
    records.push(record);
    return record.order;
  }

  function bySensor(sensor) {
    return records.filter((r) => r.sensor === sensor).length;
  }

  function summary() {
    return `${records.length} records, next order ${nextOrder}`;
  }

  return { add, bySensor, summary };
})();

console.log(measurementLog.add("S-01", 21.4));
console.log(measurementLog.add("S-02", 19.8));
console.log(measurementLog.add("S-01", 22.0));
console.log(measurementLog.bySensor("S-01"));
console.log(measurementLog.summary());
console.log(Object.keys(measurementLog).join(","));
console.log(typeof measurementLog.records);
console.log(JSON.stringify(measurementLog));
```

```
1
2
3
2
3 records, next order 4
add,bySensor,summary
undefined
{}
```

The records array and the order counter cannot be reached from outside; the log's
interface consists of only three functions. That the order number cannot be corrupted
from outside is the preservation of the class invariant introduced in the Programming
Foundations course's Introduction to Object-Oriented Programming lesson — here,
encapsulation is provided by a scope boundary instead of a class.

Where the pattern parts ways with a class is in number: this construction produces a
**single object**. If a second log of the same kind is wanted, it should be used as an
ordinary factory function, not called immediately. Single-instance structures — an
application configuration, a log writer, a counter — are this pattern's natural
territory.

#### One-Time Setup

The second common use is preventing the intermediate steps needed to set up a value
from leaking out. The value is computed once; intermediate variables stay inside the
pattern.

```js
const CALIBRATION = (function () {
  const rawLines = ["S-01;-0.4", "S-02;0.15", "S-03;0"];
  const table = Object.create(null);
  for (const line of rawLines) {
    const [sensor, offset] = line.split(";");
    table[sensor] = Number(offset);
  }
  return Object.freeze(table);
})();

console.log(CALIBRATION["S-01"]);
console.log(CALIBRATION["S-02"]);
console.log(Object.isFrozen(CALIBRATION));
console.log(Object.getPrototypeOf(CALIBRATION));
console.log(typeof rawLines);

try {
  CALIBRATION["S-04"] = 1;
} catch (error) {
  console.log(`add: ${error.constructor.name}`);
}
console.log(Object.keys(CALIBRATION).join(","));
```

```
-0.4
0.15
true
null
undefined
add: TypeError
S-01,S-02,S-03
```

The raw lines and the loop variable are never visible in the outer scope at all; what is
left is only a table ready for use. Because the table is built with `Object.create(null)`
it has no chain, and because it is frozen with `Object.freeze` it cannot be changed
afterward — the two decisions from the Object Prototype and Immutability Techniques lesson
are applied together here.

This pattern fits constants that have a setup cost. Doing the setup once instead of on
every use is a small-scale example of the preprocessing idea from the Algorithms
course.

#### Passing a Dependency In

The pattern's second common use is the wrapper's **parameter list**. If every name
used from outside is passed explicitly, what the body depends on becomes readable, and
name lookup does not reach into outer scopes.

```js
const settings = { threshold: 25 };

const report = (function (source, unit) {
  const measurements = [21.4, 26.8, 23.1];
  const exceeding = measurements.filter((d) => d > source.threshold);
  return `${exceeding.length} measurement exceeded the ${source.threshold}${unit} threshold`;
})(settings, "C");

console.log(report);

(function setupCheck() {
  try {
    null.value;
  } catch (error) {
    const firstLine = error.stack.split("\n")[1].trim().split(" ")[1];
    console.log("name in stack trace:", firstLine);
  }
})();
```

```
1 measurement exceeded the 25C threshold
name in stack trace: setupCheck
```

The second call shows a detail: the wrapper can be given a name. The name is not
accessible from outside — it appears only in the function's own body and in the
**stack trace**. In an unnamed wrapper, the same line is listed as anonymous; the
named form of the pattern is preferred while debugging.

The stack trace's format varies by runtime; here, only the name appearing in the
listing matters.

#### Comparison With Block Scope and Modules

Both of the pattern's functions are largely covered by the language's later
abilities. Block-scoped declarations provide isolation; the module system gives a
namespace at the file level.

```js
let blockResult;
{
  const temp = [21.4, 19.8, 25.1];
  blockResult = temp.length;
}
console.log(blockResult);
console.log(typeof temp);

const expressionResult = (() => {
  const temp = [21.4, 19.8, 25.1];
  return temp.reduce((total, d) => total + d, 0) / temp.length;
})();
console.log(expressionResult.toFixed(3));

var scopeTest = "outside";
(function () {
  var scopeTest = "inside";
  console.log(scopeTest);
})();
console.log(scopeTest);
```

```
3
undefined
22.100
inside
outside
```

A block isolates `const` and `let` declarations, and if isolation is the only goal, it
is a plainer path than an immediately invoked function. The second section shows the
difference: a block is a statement, it produces no value; carrying its result out
requires assigning to a variable declared beforehand. An immediately invoked function
is an expression and can be bound directly to a constant.

The third section shows the one case the pattern is still needed for: names declared
with `var` do not enter block scope, they are isolated only by function scope.

The module system, meanwhile, has taken over the namespace job. Every file is its own
scope; no name that is not exported is seen from another file. Reaching for the
pattern to prevent file-level name collisions is no longer necessary. Module system
details are the subject of the Modules, Tooling, and Ecosystem course.

The remaining use cases gather into three headings: setting up a single-instance
interface together with hidden state, isolating a constant's setup steps, and confining
`var` declarations that block scope cannot reach.

#### Summary

- Because the word `function` at the start of a statement is parsed as a declaration,
  an immediately invoked function is moved into expression position with a wrapper.
- The pattern opens a new scope; what is defined inside is not visible from outside,
  only the returned value becomes accessible.
- The revealing module pattern produces a single object with hidden state; a factory
  function is used when more than one instance is needed.
- In one-time setup, intermediate variables are isolated and the result is handed out
  as a frozen constant.
- Block scope covers the isolation job but produces no value; the module system takes
  over the namespace job.

#### Next Step

Throughout this topic, functions have constantly been used as values: passed as
callbacks, returned from another function, written as a property onto an object,
wrapped to change behavior. All of these uses come from a single ability — a function
also being a value. The next lesson takes up this ability directly and shows, while
building filter, map, and reduce operations on measurement records, where the binding
rules come into play.

### Higher-Order Functions

Source: https://academia.sh/en/courses/javascript-object-model/higher-order-functions

Using a function as a value, the filter-map-reduce chain, functions that produce a comparator, composition, and behavior-adding wrappers.

Earlier lessons constantly used functions as values: passed as a callback, returned
from a factory, written as a property onto an object, a new one produced with `bind`.
All of this rests on a single fact — in JavaScript a function is an object; aside from
being callable, it is no different from a number or a string.

A function that takes a function as an argument or returns a function is called a
**higher-order function**. The Programming Fundamentals course's Introduction to
Functional Programming lesson introduced the concept. This lesson applies it to
measurement records and shows where the previous lessons' binding rules touch this
usage.

#### Giving a Function as an Argument

The most common form is an operation on a collection taking **what to do** from
outside. The filter criterion, the transformation rule, and the accumulator operation
can each be held in separately named functions.

```js
const records = [
  { sensor: "S-01", value: 21.4, time: 1000 },
  { sensor: "S-02", value: 19.8, time: 1060 },
  { sensor: "S-01", value: 25.1, time: 1120 },
  { sensor: "S-03", value: 18.2, time: 1180 },
  { sensor: "S-01", value: 22.0, time: 1240 },
];

const aboveThreshold = (record) => record.value > 20;
const getValue = (record) => record.value;
const sum = (total, value) => total + value;

const selected = records.filter(aboveThreshold);
const values = selected.map(getValue);
const total = values.reduce(sum, 0);

console.log(selected.length);
console.log(values.join(","));
console.log(total.toFixed(1));
console.log((total / values.length).toFixed(3));

const groupedBySensor = records.reduce((group, record) => {
  (group[record.sensor] ??= []).push(record.value);
  return group;
}, Object.create(null));

console.log(Object.keys(groupedBySensor).join(","));
console.log(groupedBySensor["S-01"].join(","));
console.log(typeof aboveThreshold);
console.log(aboveThreshold.length);
```

```
3
21.4,25.1,22
68.5
22.833
S-01,S-02,S-03
21.4,25.1,22
function
1
```

Filter, map, and reduce separate three distinct responsibilities: which records are of
interest, which piece of information is taken from those records, and how that
information gets combined. Each can be tested separately and reused in other contexts.

The grouping example shows the generality of reduce: the accumulator does not have to
be a number, it can also be an object. Building the accumulator with
`Object.create(null)` is deliberate — because sensor names come from the data source,
this removes any risk of colliding with names coming from the chain.

The last two lines confirm the function is an ordinary value: its type can be queried,
its parameter count read, and it can be stored in a variable.

#### Functions That Return a Function

The second form is a function that produces a function. The produced function carries
the producer's parameters through a closure, giving a specialized function whose
behavior is configured from outside.

```js
function byKey(keyFn, reverse = false) {
  const direction = reverse ? -1 : 1;
  return (a, b) => {
    const leftKey = keyFn(a);
    const rightKey = keyFn(b);
    if (leftKey < rightKey) return -1 * direction;
    if (leftKey > rightKey) return 1 * direction;
    return 0;
  };
}

const records = [
  { sensor: "S-02", value: 19.8 },
  { sensor: "S-01", value: 25.1 },
  { sensor: "S-03", value: 18.2 },
  { sensor: "S-01", value: 21.4 },
];

const format = (list) => list.map((k) => `${k.sensor}:${k.value}`).join(" ");

console.log(format([...records].sort(byKey((k) => k.value))));
console.log(format([...records].sort(byKey((k) => k.value, true))));
console.log(format([...records].sort(byKey((k) => k.sensor))));
console.log(format(records));
```

```
S-03:18.2 S-02:19.8 S-01:21.4 S-01:25.1
S-01:25.1 S-01:21.4 S-02:19.8 S-03:18.2
S-01:25.1 S-01:21.4 S-02:19.8 S-03:18.2
S-02:19.8 S-01:25.1 S-03:18.2 S-01:21.4
```

A single `byKey` function produces three separate sort criteria. The comparison logic
is written once; the only thing that changes is how the key is extracted. This is the
function-level counterpart of the compound-key idea from the Algorithms course's
Choosing a Sorting Algorithm lesson.

The third line shows the sort's **stability**: sorted by sensor name, the two `S-01`
records keep their relative order from the input. The last line confirms the source is
unchanged — because sort works in place, a copy was taken with spread on every call.

#### Composition

The second use of returning a function is **composing** several transformations into a
single function. Each step is written on its own; the order is set up once.

```js
function createCalibrator(offset, multiplier) {
  return (rawValue) => (rawValue + offset) * multiplier;
}

function compose(...fns) {
  return (start) => fns.reduce((value, fn) => fn(value), start);
}

const sensorS01 = createCalibrator(-0.4, 1.02);
const sensorS02 = createCalibrator(0.15, 1.0);

console.log(sensorS01(21.4).toFixed(3));
console.log(sensorS02(19.8).toFixed(3));

const round = (value) => Math.round(value * 10) / 10;
const celsiusToFahrenheit = (value) => value * 1.8 + 32;

const pipeline = compose(sensorS01, round, celsiusToFahrenheit, round);

console.log(pipeline(21.4));
console.log(pipeline(19.8));
console.log(compose()(5));
```

```
21.420
19.950
70.5
67.6
5
```

`compose` is itself a reduce: the starting value is the input, the accumulator
operation is "apply the next function." Called with no arguments, it produces the
identity function — no transformation applied — and this is the sign that the
composition is correctly defined.

`createCalibrator` and partial application done with `bind` give the same result. The
difference is in readability and context: the closure form uses no `this`, which
parameter was fixed is clear from its name, and it is subject to none of the binding
rules.

#### Passing a Method as a Value

When a **method** is given to a higher-order function, the previous lessons'
detachment problem returns: what is passed is the function, not the object it sits on.

```js
class MeasurementRecord {
  constructor(sensor, value) {
    this.sensor = sensor;
    this.value = value;
  }
  label() {
    return `${this.sensor}:${this.value}`;
  }
}

const records = [new MeasurementRecord("S-01", 21.4), new MeasurementRecord("S-02", 19.8)];

try {
  console.log(records.map(MeasurementRecord.prototype.label).join(" "));
} catch (error) {
  console.log(`direct method: ${error.constructor.name}`);
}

console.log(records.map((record) => record.label()).join(" "));
console.log(records.map(Function.prototype.call, MeasurementRecord.prototype.label).join(" "));

function applyToAll(list, fn, context) {
  const result = [];
  for (const item of list) result.push(fn.call(context, item));
  return result;
}

console.log(applyToAll([1, 2, 3], function (n) {
  return n * this.multiplier;
}, { multiplier: 10 }).join(","));
```

```
direct method: TypeError
S-01:21.4 S-02:19.8
S-01:21.4 S-02:19.8
10,20,30
```

When the prototype's method is given directly, `this` is not bound. The most readable
fix is to wrap the call in an arrow function. The third line is a more indirect path
using `map`'s context parameter: the mapped function becomes `call`, and the context
becomes the actual method; every element is passed as `call`'s first argument. Short to
write, hard to read — a good example of trading readability for brevity.

The last section shows how to offer context support in your own higher-order
functions: the form `fn.call(context, item)` establishes the same contract as built-in
array methods' second parameter.

#### Behavior-Adding Wrappers

The third form takes a function and returns a function that offers the same interface
but with extended behavior. The memoization wrapper in the Closures lesson was an
example of this; the pattern is general.

```js
function once(fn) {
  let called = false;
  let result;
  return function (...args) {
    if (!called) {
      called = true;
      result = fn.apply(this, args);
    }
    return result;
  };
}

function wrapValidated(fn, isValid, message) {
  return function (...args) {
    if (!args.every(isValid)) throw new RangeError(message);
    return fn.apply(this, args);
  };
}

let setupCount = 0;
const setup = once((sensor) => {
  setupCount += 1;
  return `${sensor} set up`;
});

console.log(setup("S-01"));
console.log(setup("S-02"));
console.log(setupCount);

const safeAverage = wrapValidated(
  (...values) => values.reduce((t, d) => t + d, 0) / values.length,
  (value) => typeof value === "number" && Number.isFinite(value),
  "all values must be finite numbers",
);

console.log(safeAverage(21.4, 19.8, 25.1).toFixed(3));
try {
  safeAverage(21.4, "twenty");
} catch (error) {
  console.log(`${error.constructor.name}: ${error.message}`);
}

const record = {
  sensor: "S-01",
  summary: wrapValidated(
    function (prefix) {
      return `${prefix}-${this.sensor}`;
    },
    (a) => typeof a === "string",
    "prefix must be a string",
  ),
};
console.log(record.summary("measurement"));
```

```
S-01 set up
S-01 set up
1
22.100
RangeError: all values must be finite numbers
measurement-S-01
```

Both wrappers follow three rules. They accept the same parameters — this is why rest
parameters are used. They give the same return value. And they preserve context: their
bodies are function expressions, not arrow functions, and `this` is passed along in the
`apply` call. The last section is a consequence of this third rule — when the wrapped
function is called as an object's method, `this` binds to the correct object.

Had the wrapper been written as an arrow function, `this` would have come from the
scope the wrapper was defined in, and it could not have been used as a method. The
boundary on arrow functions applies here too.

#### Summary

- A function is a value: it is stored in a variable, given as an argument, returned,
  and carries properties.
- Filter, map, and reduce separate responsibilities by taking a collection operation's
  criterion from outside; reduce's accumulator can also be an object.
- Functions that return a function produce specialized functions that carry their
  parameters through a closure; sort criteria and calibrators are typical examples.
- Composition is a reduce that applies transformations, each written on its own, in
  sequence.
- Giving a method directly to a higher-order function breaks its bond; an arrow
  function wrapping the call is the most readable fix.
- Wrappers must preserve the same parameters and return value and pass context along
  with `apply`; this is why they cannot be written as arrow functions.

#### Next Step

In this lesson, grouping was done with the accumulator built as `Object.create(null)`;
the reason was that sensor names could collide with names coming from the chain. The
same precaution was needed in this course's first lesson too, and in the closures'
cache. Using an object as a dictionary has this and other limits: keys are converted to
strings, order guarantees are restricted, and element count cannot be read directly.
The next topic opens by introducing the types the language sets aside for this job —
keyed and single-value collections.

## Advanced Collections

Keyed and unique collections, weak references, the iteration protocol, lazy sequences with generators, typed arrays for binary data, and deep copying.

### Map and Set

Source: https://academia.sh/en/courses/javascript-object-model/map-and-set

The limits of using an object as a dictionary; a mapping type that preserves key identity, sets of unique values, and a comparison of the two setups on grouping.

The prototype-less object appeared in this course's first lesson, and the same
precaution came up again in later lessons: in the calibration table, in the memoization
cache, in the grouping accumulator. The reason was the same every time — in a mapping
whose keys come from outside the program, names coming from the object's chain corrupted
the result.

This is not the only limit of using an object as a dictionary. Keys are converted to
strings, the ordering rule is different from what you'd expect, and element count cannot
be read directly. The language offers two types for this job: `Map` for key–value pairs,
`Set` for unique values. The lesson first shows the limits, then introduces the two
types.

#### The Limits of an Object as a Dictionary

An object property's key can only be a string or a symbol. When a key of another type is
given, it is converted to a string.

```js
const dictionary = {};
const keyObject = { sensor: "S-01" };
const otherObject = { sensor: "S-02" };

dictionary[1] = "number one";
dictionary["1"] = "string one";
dictionary[true] = "boolean";
dictionary[keyObject] = "first object";
dictionary[otherObject] = "second object";

console.log(Object.keys(dictionary).join(" | "));
console.log(dictionary[1]);
console.log(dictionary[keyObject]);
console.log(Object.keys(dictionary).length);

const ordered = {};
ordered["S-02"] = 1;
ordered[10] = 2;
ordered["S-01"] = 3;
ordered[2] = 4;
console.log(Object.keys(ordered).join(","));

const dirty = { sensor: "S-01" };
console.log("toString" in dirty);
console.log(Object.hasOwn(dirty, "toString"));
console.log(typeof dirty["toString"]);
```

```
1 | true | [object Object]
string one
second object
3
2,10,S-02,S-01
true
false
function
```

Three limits are visible. First, `1` and `"1"` are the same key; the second assignment
overwrote the first. The second is heavier: two different objects convert to the same
`"[object Object]"` string, so they collapse into a single key — objects cannot be used
as keys. The third is ordering: keys that look like integers are moved to the front in
ascending numeric order, others keep insertion order.

The last three lines repeat a familiar problem. There is no key named `toString` in the
dictionary, but the query finds it; whenever keys come from outside, `Object.hasOwn` or a
prototype-less object has to be used.

#### A Mapping That Preserves Key Identity

`Map` does not convert keys. A value of any type can be a key, and comparison is done by
identity.

```js
const keyObject = { sensor: "S-01" };
const otherObject = { sensor: "S-02" };

const ledger = new Map();
ledger.set(1, "number one");
ledger.set("1", "string one");
ledger.set(keyObject, "first object");
ledger.set(otherObject, "second object");
ledger.set(NaN, "undefined measurement");

console.log(ledger.size);
console.log(ledger.get(1));
console.log(ledger.get("1"));
console.log(ledger.get(keyObject));
console.log(ledger.get({ sensor: "S-01" }));
console.log(ledger.get(NaN));
console.log(ledger.has(otherObject));

ledger.delete(otherObject);
console.log(ledger.size);

const keyTypes = [...ledger.keys()].map((a) => typeof a).join(",");
console.log(keyTypes);

const ordered = new Map();
ordered.set("S-02", 1);
ordered.set(10, 2);
ordered.set("S-01", 3);
ordered.set(2, 4);
console.log([...ordered.keys()].join(","));
```

```
5
number one
string one
first object
undefined
undefined measurement
true
4
number,string,object,number
S-02,10,S-01,2
```

`1` and `"1"` are separate keys. An object key is stored by its **identity**: querying
with another object of the same content finds nothing. The identity–equality distinction
from the Variables and Binding lesson in the Programming Fundamentals course applies
directly here.

The `NaN` key being findable points to a detail: comparison is not done with strict
equality, it is done with a rule that counts `NaN` as equal to itself. The same rule
applies to `Set` too.

The last line shows the ordering rule: `Map` preserves only insertion order, it does not
move numeric keys to the front. The `size` property gives the element count directly; the
object needs all its keys listed for the same information.

Its relationship to hash tables is set up here too. The average constant-cost access
examined in the Hash Tables lesson of the Data Structures course is the behavior
underlying both object properties and the `Map` structure; `Map` is its form that
preserves key identity.

#### Conversions and Traversal

Two-way conversion between `Map` and an object is built in. In serialization, though, the
difference matters.

```js
const recordObject = { "S-01": 21.4, "S-02": 19.8, "S-03": 18.2 };

const mapFromObject = new Map(Object.entries(recordObject));
console.log(mapFromObject.size);
console.log(mapFromObject.get("S-02"));

const backToObject = Object.fromEntries(mapFromObject);
console.log(JSON.stringify(backToObject));

console.log(JSON.stringify(mapFromObject));
console.log(JSON.stringify([...mapFromObject]));

const fromPairs = new Map([
  ["S-01", 21.4],
  ["S-02", 19.8],
]);
console.log([...fromPairs.entries()].map(([a, d]) => `${a}=${d}`).join(" "));

let total = 0;
for (const [, value] of mapFromObject) total += value;
console.log(total.toFixed(1));

mapFromObject.forEach((value, key) => {
  if (key === "S-01") console.log(`forEach: ${key} -> ${value}`);
});
```

```
3
19.8
{"S-01":21.4,"S-02":19.8,"S-03":18.2}
{}
[["S-01",21.4],["S-02",19.8],["S-03",18.2]]
S-01=21.4 S-02=19.8
59.4
forEach: S-01 -> 21.4
```

The fourth line is critical: `JSON.stringify` serializes a `Map` object as an empty
object. The reason was established in the first lesson — serialization works with own
**enumerable properties**, and a `Map`'s content is not held as a property. If the data
needs to travel, it has to be converted to an array of pairs. The general solution to the
same problem is taken up in the course's last lesson.

The `for...of` loop gives one pair per step; destructuring can separate the key and
value. Because the `forEach` method's argument order is reversed — value first, then key
— it is open to being mixed up.

#### Unique Values

`Set` is a collection that does not hold the same value more than once. It is the
language's counterpart to the set introduced in the Sets and Multisets lesson of the Data
Structures course.

```js
const readSensors = ["S-01", "S-02", "S-01", "S-03", "S-02", "S-01"];

const unique = new Set(readSensors);
console.log(unique.size);
console.log([...unique].join(","));
console.log(unique.has("S-02"));
console.log(unique.has("S-99"));

const numberSet = new Set([1, 1, NaN, NaN, 0, -0]);
console.log(numberSet.size);
console.log([...numberSet].join(","));

const objectSet = new Set([{ sensor: "S-01" }, { sensor: "S-01" }]);
console.log(objectSet.size);

const underMaintenance = new Set(["S-02", "S-04"]);
const intersection = [...unique].filter((s) => underMaintenance.has(s));
const difference = [...unique].filter((s) => !underMaintenance.has(s));
const union = [...new Set([...unique, ...underMaintenance])];

console.log(intersection.join(","));
console.log(difference.join(","));
console.log(union.join(","));
```

```
3
S-01,S-02,S-03
true
false
3
1,NaN,0
2
S-02
S-01,S-03
S-01,S-02,S-03,S-04
```

Deduplication reduces to a single line, and insertion order is preserved. The numeric
example clarifies the comparison rule: two `NaN` values are counted as the same — a
behavior that does not hold under strict equality — and, likewise, `0` and `-0` are also
counted as the same. Objects are still compared by identity; two objects with the same
content are separate elements.

Set operations benefit from the `has` query being average constant cost. Doing the same
operation with arrays requires a linear search for every element and pushes the cost up
to the product of the two sets' sizes.

#### Comparison in Grouping

The grouping example from the Higher-Order Functions lesson is a suitable test for
placing the two setups side by side. Since sensor names come from a data source, a name
like `constructor` could well be among them.

```js
const records = [
  { sensor: "S-01", value: 21.4 },
  { sensor: "S-02", value: 19.8 },
  { sensor: "S-01", value: 25.1 },
  { sensor: "constructor", value: 0.5 },
];

function naiveGroup(list) {
  const group = {};
  for (const record of list) {
    if (!group[record.sensor]) group[record.sensor] = [];
    group[record.sensor].push(record.value);
  }
  return group;
}

function groupWithObject(list) {
  const group = {};
  for (const record of list) {
    if (!Array.isArray(group[record.sensor])) group[record.sensor] = [];
    group[record.sensor].push(record.value);
  }
  return group;
}

function groupWithMap(list) {
  const group = new Map();
  for (const record of list) {
    if (!group.has(record.sensor)) group.set(record.sensor, []);
    group.get(record.sensor).push(record.value);
  }
  return group;
}

try {
  naiveGroup(records);
} catch (error) {
  console.log(`naive version: ${error.constructor.name}`);
}

const objectGroup = groupWithObject(records);
const mapGroup = groupWithMap(records);

console.log(Object.keys(objectGroup).join(","));
console.log([...mapGroup.keys()].join(","));
console.log(objectGroup["S-01"].join(","));
console.log(mapGroup.get("S-01").join(","));
console.log(objectGroup["constructor"].join(","));
console.log(mapGroup.get("constructor").join(","));
console.log(Object.keys(objectGroup).length === mapGroup.size);
```

```
naive version: TypeError
S-01,S-02,constructor
S-01,S-02,constructor
21.4,25.1
21.4,25.1
0.5
0.5
true
```

The naive version throws an error: the expression `group["constructor"]` finds the
function coming from the chain, this value is counted as true so no array is ever
created, and `push` is called on the function. The fixed version works because it
validates with a type check — but you have to know the fix is needed.

The version written with `Map` has no such trap; the `has` query does not look at the
chain, because `Map` content is not held as a property. As a rule: **use an object if
keys are fixed and written by the programmer, use `Map` if they come from a data
source.**

#### Summary

- Object keys are converted to strings; different objects fall onto the same key, and
  keys that look like integers are moved to the front in ordering.
- `Map` does not convert keys, stores them by identity, preserves insertion order, and
  gives element count directly with `size`.
- The value `NaN` is counted as equal to itself in both `Map` and `Set`; objects are
  compared by identity.
- `JSON.stringify` serializes a `Map` object as an empty object; its content has to be
  converted to an array of pairs.
- `Set` reduces deduplication to a single step; set operations benefit from constant-cost
  queries.
- Use `Map` if keys come from a data source, an object if they are fixed and written in
  the program.

#### Next Step

As long as a `Map` holds a key, that key object stays in memory. Tables set up to attach
extra information to an object therefore extend those objects' lifetime: every entry not
removed from the table keeps a no-longer-used object alive. The next lesson takes up weak
collections, which do not prevent the objects they hold from being collected, and the
role they play in private data storage.

### Weak Collections

Source: https://academia.sh/en/courses/javascript-object-model/weak-collections

Mapping and set types that do not prevent their keys from being collected, the pattern for attaching data to an object from outside, why they cannot be iterated, and weak references.

The previous lesson ended with `Map` and left a problem behind: as long as a mapping
holds an object as a key, that object stays reachable. Tables set up to attach extra
information to an object are therefore a source of leaks — every entry not removed from
the table keeps a no-longer-used object alive.

`WeakMap` and `WeakSet` solve this problem. The reference they hold to the objects they
keep is **weak**: an object unreachable from anywhere else does not stay alive just
because it is in a weak collection. In exchange, a few capabilities are given back; the
lesson examines this trade-off.

#### Weak Mapping

`WeakMap` is a restricted form of the `Map` structure: its keys can only be objects
(and, since ES2023, non-registered symbols), element count cannot be read, and its
content cannot be iterated.

```js
const record = { sensor: "S-01", value: 21.4 };
const otherRecord = { sensor: "S-02", value: 19.8 };

const weakLedger = new WeakMap();
weakLedger.set(record, { sourceFile: "measurement-2.csv", line: 17 });
weakLedger.set(otherRecord, { sourceFile: "measurement-3.csv", line: 4 });

console.log(weakLedger.has(record));
console.log(weakLedger.get(record).line);
console.log(weakLedger.get({ sensor: "S-01", value: 21.4 }));

try {
  weakLedger.set("S-01", { line: 1 });
} catch (error) {
  console.log(`primitive key: ${error.constructor.name}`);
}

console.log(weakLedger.size);
console.log(typeof weakLedger.keys);
console.log(typeof weakLedger[Symbol.iterator]);

try {
  for (const _ of weakLedger) console.log(_);
} catch (error) {
  console.log(`iteration: ${error.constructor.name}`);
}

console.log(weakLedger.delete(record));
console.log(weakLedger.has(record));
console.log(JSON.stringify(weakLedger));
```

```
true
17
undefined
primitive key: TypeError
undefined
undefined
undefined
iteration: TypeError
true
false
{}
```

The key-identity rule is the same as `Map`'s: another object with the same content is a
different key. When a primitive value other than a non-registered symbol is given as a
key, an error is thrown, because "collectibility" is not defined for those values —
strings are shared values, not objects whose identity is tracked.

The three missing capabilities — `size`, `keys`, and iteration — are not a shortcoming,
they are a direct result of the design. Being able to list the content would mean the
program could observe when the garbage collector runs; the same program run twice could
give a different result. Non-iterability is the price paid to preserve determinism.

#### Attaching Data to an Object from Outside

The most common use of a weak mapping is keeping information belonging to an object
**outside** the object. Private fields in class syntax are the language-level
counterpart of this need; a weak mapping produces the same result with library code.

```js
const privateData = new WeakMap();

class MeasurementRecord {
  constructor(sensor, rawValue, offset) {
    this.sensor = sensor;
    privateData.set(this, { rawValue, offset, readCount: 0 });
  }

  get reading() {
    const data = privateData.get(this);
    data.readCount += 1;
    return data.rawValue + data.offset;
  }

  status() {
    return `reads=${privateData.get(this).readCount}`;
  }

  static hasData(obj) {
    return privateData.has(obj);
  }
}

const record = new MeasurementRecord("S-01", 21.4, -0.4);

console.log(record.reading);
console.log(record.reading);
console.log(record.status());
console.log(Object.keys(record).join(","));
console.log(JSON.stringify(record));
console.log(MeasurementRecord.hasData(record));
console.log(MeasurementRecord.hasData({ sensor: "S-02" }));

const detachedStatus = record.status;
try {
  detachedStatus();
} catch (error) {
  console.log(`detached call: ${error.constructor.name}`);
}
```

```
21
21
reads=2
sensor
{"sensor":"S-01"}
true
false
detached call: TypeError
```

The raw value, offset, and read counter are not properties of the record: they are not
listed, not serialized, and cannot be read by code with no access to the `privateData`
reference. The result is the same as private fields in the Class Syntax lesson and the
factory setup in the Closures lesson.

The difference among the three paths is where they are stored. A private field is stored
inside the object, a closure in scope, a weak mapping outside the object but keyed to it.
What sets the third apart is that it can attach data **without changing the object**:
even an object whose class you did not write, even a frozen one, can have information
bound to it this way.

The last line repeats a shared limit: because the data is found through `this`, the
method still throws an error when detached from its object. A weak mapping does not
change binding rules.

#### Weak Set

`WeakSet` applies the same principle to a set: it is used for marking objects, its
content cannot be iterated.

```js
const processed = new WeakSet();

function process(record) {
  if (processed.has(record)) return "already processed";
  processed.add(record);
  return `processed: ${record.sensor}`;
}

const first = { sensor: "S-01", value: 21.4 };
const second = { sensor: "S-02", value: 19.8 };

console.log(process(first));
console.log(process(first));
console.log(process(second));
console.log(processed.has(first));
console.log(processed.has({ sensor: "S-01", value: 21.4 }));

try {
  processed.add("S-03");
} catch (error) {
  console.log(`primitive value: ${error.constructor.name}`);
}

console.log(processed.size);
console.log(typeof processed.values);
```

```
processed: S-01
already processed
processed: S-02
true
false
primitive value: TypeError
undefined
undefined
```

The usage pattern is singular: "has this object been processed before?" This was the
reason `WeakSet` was chosen for visit-marking in the deep-freeze function of the
Immutability Techniques lesson — the marking is temporary and should not extend the
lifetime of the objects being walked.

Cycle detection based on object identity, preventing a repeated operation, and testing
whether an object was produced by a specific constructor are different names for the same
pattern.

#### The Cost of a Strong Reference

The memory difference between the two collection types can be seen without observing
garbage collection at all: a strong mapping can count and list its content, a weak
mapping cannot.

```js
const strongCache = new Map();
const weakCache = new WeakMap();

function processMeasurement(record, cache) {
  if (cache.has(record)) return cache.get(record);
  const result = `${record.sensor}:${record.value.toFixed(1)}`;
  cache.set(record, result);
  return result;
}

for (let i = 0; i < 1000; i += 1) {
  const temp = { sensor: `S-${i}`, value: i / 10 };
  processMeasurement(temp, strongCache);
  processMeasurement(temp, weakCache);
}

console.log(strongCache.size);
console.log([...strongCache.values()][0]);
console.log([...strongCache.keys()][999].sensor);
console.log(typeof weakCache.size);

const persistent = { sensor: "S-K", value: 30 };
console.log(processMeasurement(persistent, weakCache));
console.log(weakCache.has(persistent));
```

```
1000
S-0:0.0
S-999
undefined
S-K:30.0
true
```

Once the loop ends, the thousand temporary records inside it are, from the program's
point of view, unusable — the `temp` variable is rebound on every iteration, and none of
the earlier ones is reachable from anywhere after the last iteration. Despite this, because
the strong mapping holds all of them as keys, both the thousand objects and the thousand
result strings stay reachable; the `keys` call shows this directly.

In the weak mapping, the same entries are meaningful only as long as the key object stays
reachable from somewhere else. The last two lines show this: because the `persistent`
object is held in a variable, its entry is found. When entries for keys that become
unreachable get cleaned up is not scheduled in the specification and cannot be observed
by the program; no output in this lesson depends on the moment of collection.

How reachability-based garbage collection works is the subject of the Asynchronous
JavaScript and the Runtime course.

#### Weak References

Alongside weak collections, two more constructs offer the same principle for a single
object. `WeakRef` holds a weak reference to an object; a `deref` call gives the object if
it is still reachable, `undefined` if not. `FinalizationRegistry` registers a callback to
run after an object is collected.

```js
const record = { sensor: "S-01", value: 21.4 };
const weakRef = new WeakRef(record);

console.log(weakRef.deref() === record);
console.log(weakRef.deref().sensor);

const log = [];
const registry = new FinalizationRegistry((label) => {
  log.push(label);
});
registry.register(record, "S-01 collected");

console.log(typeof registry.register);
console.log(typeof registry.unregister);
console.log(log.length);
console.log(weakRef.deref() !== undefined);
```

```
true
S-01
function
function
0
true
```

The output's last two lines hold as long as the `record` variable stays alive: the log
is empty and the reference resolves. **When** the callback runs — even whether it runs at
all before the program ends — is not guaranteed by the specification. For this reason,
these two constructs are not made the foundation of program logic; their use cases are
diagnostics, measurement, and optional caches.

For the same reason, no block in this lesson shows an output that depends on the moment
of collection: such an output would vary with the runtime and the memory state at that
moment.

#### Summary

- Weak collections hold a weak reference to the objects they keep; being present there
  alone does not keep an object alive.
- `WeakMap` and `WeakSet` accept only objects and non-registered symbols; they do not
  support `size`, iteration, or serialization — this restriction preserves determinism.
- A weak mapping attaches data to an object without changing it at all; it is the third
  option alongside the hiding set up with private fields and closures.
- A weak set is used to mark objects: cycle detection, preventing a repeated operation,
  membership testing.
- A strong mapping keeps its keys reachable; in caches keyed to objects, this is the
  source of unbounded growth.
- `WeakRef` and `FinalizationRegistry` are not made the foundation of program logic
  because their timing is not guaranteed by the specification.

#### Next Step

In this lesson, a `for...of` attempt on `WeakMap` threw an error, while the same loop
works on `Map`, `Set`, an array, and a string. The difference is not a list of types, it
is a single contract: whether an object is iterable depends on it offering a specific
method. The next lesson opens that contract — the iteration protocol — and gives the
measurement series its own traversal behavior.

### Iterators

Source: https://academia.sh/en/courses/javascript-object-model/iterators

The iteration protocol's two contracts, making a type traversable through its prototype, the distinction between iterator and iterable, cleanup on early exit, and lazy production.

In the previous lesson, a `for...of` attempt on `WeakMap` threw an error, while the same
loop works on `Map`, `Set`, an array, and a string. The difference is not a list of
types: the `for...of` loop, the spread operator, destructuring, and `Array.from` all look
at a single contract.

The **iteration protocol** consists of two contracts. If an object offers a method named
`[Symbol.iterator]`, it is **iterable**; that method has to return an **iterator**
offering `next`. The lesson first sets up the contract by hand, then binds the
measurement series to this contract.

#### The next Contract

An iterator is an ordinary object with a single method. A `next` call returns a two-field
object: the next value, and whether the sequence has ended.

```js
function measurementIterator(values) {
  let index = 0;
  return {
    next() {
      if (index >= values.length) return { value: undefined, done: true };
      const value = values[index];
      index += 1;
      return { value, done: false };
    },
  };
}

const iterator = measurementIterator([21.4, 19.8, 25.1]);

console.log(JSON.stringify(iterator.next()));
console.log(JSON.stringify(iterator.next()));
console.log(JSON.stringify(iterator.next()));
console.log(JSON.stringify(iterator.next()));

const second = measurementIterator([21.4, 19.8]);
let step = second.next();
const collected = [];
while (!step.done) {
  collected.push(step.value);
  step = second.next();
}
console.log(collected.join(","));
console.log(typeof iterator[Symbol.iterator]);
```

```
{"value":21.4,"done":false}
{"value":19.8,"done":false}
{"value":25.1,"done":false}
{"done":true}
21.4,19.8
undefined
```

Position information is held in the `index` variable and is invisible from outside — the
pattern from the Closures lesson works here too. The `value` field not appearing in the
fourth output is a result of serialization: fields with an `undefined` value do not enter
`JSON.stringify` output.

The `while` loop is a `for...of` loop written by hand. The last line tells you why this
object cannot be used with `for...of`: it offers `next` but not `[Symbol.iterator]`, so
it is an iterator but not iterable.

#### Making a Type Iterable

The second contract is an object offering a `[Symbol.iterator]` method. When the method
is written in a class body, it stands on the prototype and all instances become
iterable.

```js
class MeasurementSeries {
  constructor(sensor, values) {
    this.sensor = sensor;
    this.values = values;
  }

  [Symbol.iterator]() {
    let index = 0;
    const values = this.values;
    return {
      next() {
        if (index >= values.length) return { value: undefined, done: true };
        const value = values[index];
        index += 1;
        return { value, done: false };
      },
    };
  }
}

const series = new MeasurementSeries("S-01", [21.4, 19.8, 25.1]);

const collected = [];
for (const value of series) collected.push(value);
console.log(collected.join(","));

console.log([...series].join(","));
console.log(Array.from(series).length);

const [first, second] = series;
console.log(`${first} ${second}`);

console.log(Math.max(...series));
console.log(Object.hasOwn(series, Symbol.iterator));
console.log(Object.hasOwn(MeasurementSeries.prototype, Symbol.iterator));
console.log(Object.keys(series).join(","));
console.log(JSON.stringify(series));
```

```
21.4,19.8,25.1
21.4,19.8,25.1
3
21.4 19.8
25.1
false
true
sensor,values
{"sensor":"S-01","values":[21.4,19.8,25.1]}
```

One method was added; five separate language features started working. `for...of`,
spread, `Array.from`, array destructuring, and argument spreading all use the same
contract.

The sixth and seventh lines tie back to this lesson's axis: the method stands not on the
instance, but on `MeasurementSeries.prototype`. Iterability is not a flag belonging to
the object, it is a method found on the chain. This is why making a type iterable
afterward is possible by adding this method to its prototype.

Because it is symbol-keyed, it does not appear in `Object.keys` output and does not enter
serialization; this property of symbol keys, covered in the Property Descriptors lesson,
keeps protocol methods separate from data fields.

#### The Iterator–Iterable Distinction

The two contracts most often merge in the same object. Built-in iterators offer a
`[Symbol.iterator]` method that returns themselves, which is what makes them both
iterator and iterable.

```js
const values = [21.4, 19.8, 25.1];

const iterator = values[Symbol.iterator]();
console.log(typeof iterator.next);
console.log(typeof iterator[Symbol.iterator]);
console.log(iterator[Symbol.iterator]() === iterator);

console.log(JSON.stringify(iterator.next()));

const remaining = [];
for (const value of iterator) remaining.push(value);
console.log(remaining.join(","));

const emptyReturn = [];
for (const value of iterator) emptyReturn.push(value);
console.log(emptyReturn.length);

const firstPass = [];
for (const value of values) firstPass.push(value);
const secondPass = [];
for (const value of values) secondPass.push(value);
console.log(firstPass.length === secondPass.length);

const text = "measurement";
console.log([...text].join("-"));
const map = new Map([["S-01", 21.4]]);
console.log([...map][0].join("="));
```

```
function
function
true
{"value":21.4,"done":false}
19.8,25.1
0
true
m-e-a-s-u-r-e-m-e-n-t
S-01=21.4
```

The practical consequence of the distinction is exhaustion. Traversing an iterator
**consumes** it: the first `for...of` reads the remaining two values, the second finds
nothing. An iterable object, on the other hand, produces a new iterator on every
traversal, which is why it can be traversed over and over.

This explains why it matters whether an iterable object or an iterator is given to a
function. If an iterator is given to a function that needs to traverse it twice, the
second pass returns empty; no error message is produced either.

The last two lines show how widespread the protocol is. Traversing a string gives code
points, not code units — the distinction introduced in the Character Encodings lesson of
the How Computers Work course is observable here. Traversing a `Map` gives key–value
pairs.

#### Early Exit and Cleanup

When a `for...of` loop is ended with `break`, or an error is thrown, the protocol calls
the iterator's `return` method. This is the reserved point for closing open resources.

```js
const log = [];

function resourceIterator(values) {
  let index = 0;
  let closed = false;
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      if (closed || index >= values.length) return { value: undefined, done: true };
      const value = values[index];
      index += 1;
      return { value, done: false };
    },
    return(value) {
      closed = true;
      log.push(`resource closed, read=${index}`);
      return { value, done: true };
    },
  };
}

const resource = resourceIterator([21.4, 19.8, 25.1, 18.2]);

for (const value of resource) {
  if (value > 20 && value < 22) {
    console.log(`found: ${value}`);
    break;
  }
}

console.log(log.join(" | "));
console.log(JSON.stringify(resource.next()));

const secondResource = resourceIterator([21.4, 19.8]);
const [singleValue] = secondResource;
console.log(singleValue);
console.log(log.length);
```

```
found: 21.4
resource closed, read=1
{"done":true}
21.4
2
```

A `break` statement does not just break the loop, it also notifies the iterator. A closed
iterator answers as finished on subsequent `next` calls. The last section shows the same
call is made during array destructuring too: once the requested number of values is
taken, the resource is closed.

This behavior matters for iterators bound to resources like file and network streams;
early exit does not leave the resource open.

#### Lazy Production

The protocol's last property is that values are produced **as requested**. Even if the
source is an array, the computation done on each value only runs when that value is
read.

```js
let computeCount = 0;

function calibratedSeries(rawValues, offset) {
  return {
    [Symbol.iterator]() {
      let index = 0;
      return {
        [Symbol.iterator]() {
          return this;
        },
        next() {
          if (index >= rawValues.length) return { value: undefined, done: true };
          computeCount += 1;
          const value = rawValues[index] + offset;
          index += 1;
          return { value: Number(value.toFixed(2)), done: false };
        },
      };
    },
  };
}

const series = calibratedSeries([21.4, 19.8, 25.1, 18.2, 30.0], -0.4);

console.log(computeCount);

let firstAboveThreshold;
for (const value of series) {
  if (value > 24) {
    firstAboveThreshold = value;
    break;
  }
}

console.log(firstAboveThreshold);
console.log(computeCount);

const all = [...series];
console.log(all.join(","));
console.log(computeCount);
```

```
0
24.7
3
21,19.4,24.7,17.8,29.6
8
```

No computation has been done when the series is set up. Three computations run until the
first value above the threshold is found; the remaining two values are never computed. When
the same series is traversed from the start again, five more computations run and the
total climbs to eight.

If the same job were written with `map`, all five computations would run up front. This
is the **lazy evaluation** introduced in the Introduction to Functional Programming
lesson of the Programming Fundamentals course, and it is the only workable approach for
large or endless sequences.

The cost of writing the protocol by hand is also visible in this example: position, the
end condition, and the self-returning `[Symbol.iterator]` method are repeated every
time. The next lesson introduces a syntax that eliminates this repetition.

#### Summary

- The iteration protocol consists of two contracts: an iterable object offers
  `[Symbol.iterator]`, an iterator offers `next`.
- `for...of`, spread, destructuring, `Array.from`, and argument spreading all use the
  same contract; adding a single method makes all of them work.
- `[Symbol.iterator]` written in a class body stands on the prototype; iterability is a
  method found on the chain.
- An iterator gets exhausted, an iterable object produces a new iterator on every
  traversal; this distinction is decisive wherever something needs to be traversed twice.
- `break` and early destructuring call the iterator's `return` method, allowing for
  resource cleanup.
- Values are produced as requested; an unread value is never computed at all.

#### Next Step

The same three pieces kept repeating in hand-written iterators: the variable holding
position, the end condition, and the self-returning `[Symbol.iterator]` method. The
language offers a function type that produces all three itself — functions that can pause
their execution and resume where they left off. The next lesson takes up generators and
reduces this lesson's lazy series to a few lines.

### Generators

Source: https://academia.sh/en/courses/javascript-object-model/generators

Pausable functions, the generator object's place in the iteration protocol, endless sequences, delegation, two-way communication, and lazy pipelines.

In the previous lesson, the same three pieces kept repeating in hand-written iterators:
the variable holding position, the end condition, and the self-returning
`[Symbol.iterator]` method. All three are mechanical and overshadow the function's real
job.

A **generator** is a function that can pause its execution and resume later from where it
left off. It is defined in the `function*` form and pauses by giving a value with the
`yield` statement. When called, its body does not run; it returns an object satisfying
both of the protocol's contracts. The lesson opens up this object's behavior and reduces
the previous lesson's lazy series to a few lines.

#### A Pausable Function

A generator function's body does not run at all until the first `next` call. Every
`next` advances to the next `yield` statement and stops there.

```js
let computeCount = 0;

function* calibratedSeries(rawValues, offset) {
  for (const raw of rawValues) {
    computeCount += 1;
    yield Number((raw + offset).toFixed(2));
  }
}

const generator = calibratedSeries([21.4, 19.8, 25.1], -0.4);

console.log(computeCount);
console.log(typeof generator.next);
console.log(generator[Symbol.iterator]() === generator);

console.log(JSON.stringify(generator.next()));
console.log(computeCount);
console.log(JSON.stringify(generator.next()));
console.log(JSON.stringify(generator.next()));
console.log(JSON.stringify(generator.next()));
console.log(computeCount);

console.log([...calibratedSeries([21.4, 19.8], -0.4)].join(","));
console.log(typeof calibratedSeries);
```

```
0
function
true
{"value":21,"done":false}
1
{"value":19.4,"done":false}
{"value":24.7,"done":false}
{"done":true}
3
21,19.4
function
```

The first line shows the body has not run at all. The third line confirms the generator
object is both iterator and iterable: it carries a `[Symbol.iterator]` method that
returns itself. All three pieces written by hand in the previous lesson are produced here
by the language itself.

The fifth line measures laziness: the first `next` has done only one computation. Once
the values run out, the `done` field becomes true and the `value` field stays
`undefined`. The last line fixes a distinction — the generator function itself is an
ordinary function; what is a generator is the object the call returns.

A generator object gets exhausted too. Traversing it again requires calling the function
again; the spread in the tenth line sets up a new generator for exactly this.

#### Generator as a Class Method

A generator can also be defined in a class body by putting an asterisk before the method
name. The `MeasurementSeries` class from the previous lesson is shortened by half this
way.

```js
class MeasurementSeries {
  constructor(sensor, values, offset = 0) {
    this.sensor = sensor;
    this.values = values;
    this.offset = offset;
  }

  *[Symbol.iterator]() {
    for (const raw of this.values) {
      yield Number((raw + this.offset).toFixed(2));
    }
  }

  *labeledPairs() {
    let index = 0;
    for (const value of this) {
      yield [`${this.sensor}#${index}`, value];
      index += 1;
    }
  }
}

const series = new MeasurementSeries("S-01", [21.4, 19.8, 25.1], -0.4);

console.log([...series].join(","));
console.log([...series].join(","));

for (const [label, value] of series.labeledPairs()) {
  console.log(`${label} -> ${value}`);
}

console.log(Object.getOwnPropertyNames(MeasurementSeries.prototype).join(","));
console.log(Object.hasOwn(MeasurementSeries.prototype, Symbol.iterator));
console.log(new Map(series.labeledPairs()).get("S-01#1"));
```

```
21,19.4,24.7
21,19.4,24.7
S-01#0 -> 21
S-01#1 -> 19.4
S-01#2 -> 24.7
constructor,labeledPairs
true
19.4
```

The series can be traversed twice, because what is being traversed is the series itself
and every traversal produces a new generator object. The `labeledPairs` method writes
`for (const value of this)` and benefits from its own class's iterability; it does not
access the raw data a second time.

The sixth and seventh lines repeat the tie to the chain: generator methods also stand on
the prototype, and being symbol-keyed, `Symbol.iterator` does not enter the name listing —
the `Object.getOwnPropertyNames` output. The last
line shows the protocol's unifying power — any iterable object giving key–value pairs can
be handed directly to the `Map` constructor.

#### Endless Sequences and Delegation

Because a generator only runs when requested, defining an endless sequence is no problem.
`yield*` lets a generator delegate its work to another iterable object.

```js
function* increasingTime(start, step) {
  let time = start;
  while (true) {
    yield time;
    time += step;
  }
}

function* firstN(iterable, count) {
  if (count <= 0) return;
  let remaining = count;
  for (const value of iterable) {
    yield value;
    remaining -= 1;
    if (remaining <= 0) return;
  }
}

console.log([...firstN(increasingTime(1000, 60), 4)].join(","));

function* frontSeries() {
  yield 21.4;
  yield 19.8;
}

function* backSeries() {
  yield 25.1;
}

function* allSeries() {
  yield* frontSeries();
  yield* backSeries();
  yield* [18.2, 30.0];
  return "done";
}

const combined = allSeries();
console.log([...allSeries()].join(","));

let step = combined.next();
const values = [];
while (!step.done) {
  values.push(step.value);
  step = combined.next();
}
console.log(values.length);
console.log(step.value);
```

```
1000,1060,1120,1180
21.4,19.8,25.1,18.2,30
5
done
```

`increasingTime` contains an infinite loop, but the program does not hang: values are
produced only when requested, and `firstN` stops after the fourth value. The infinite
loop being harmless is a direct result of laziness; producing the same measurement times
in an array would be impossible.

`yield*` accepts any kind of iterable object — another generator or an ordinary array.
The value of the `return` statement is **skipped** by `for...of` and spread; only code
that manually continues the `next` calls can see it, in the finishing step's `value`
field. This is why generators are used to stream values, not to return a result.

#### Two-Way Communication

The argument given to a `next` call becomes the value of the `yield` expression the
generator is paused at. This way, a generator does not just produce values, it also
receives values from outside.

```js
function* thresholdMonitor(startThreshold) {
  let threshold = startThreshold;
  const exceeding = [];
  try {
    while (true) {
      const incoming = yield `threshold=${threshold}, exceeding=${exceeding.length}`;
      if (typeof incoming === "number") {
        if (incoming > threshold) exceeding.push(incoming);
      } else if (typeof incoming === "object" && incoming !== null) {
        threshold = incoming.newThreshold;
      }
    }
  } finally {
    console.log(`closing: total exceeding=${exceeding.length}`);
  }
}

const monitor = thresholdMonitor(20);

console.log(monitor.next("this value is ignored").value);
console.log(monitor.next(21.4).value);
console.log(monitor.next(19.8).value);
console.log(monitor.next({ newThreshold: 25 }).value);
console.log(monitor.next(25.1).value);
console.log(JSON.stringify(monitor.return("manually stopped")));
console.log(JSON.stringify(monitor.next(30)));
```

```
threshold=20, exceeding=0
threshold=20, exceeding=1
threshold=20, exceeding=1
threshold=25, exceeding=1
threshold=25, exceeding=2
closing: total exceeding=2
{"value":"manually stopped","done":true}
{"done":true}
```

The first `next` call's argument is ignored: the body has not yet paused at any `yield`
expression, so there is no place to receive a value. In subsequent calls, the value sent
falls into the `incoming` variable.

The `return` method ends the generator from outside and runs the `finally` block — this
is the generator counterpart of the previous lesson's cleanup point. A finished generator
answers as done on subsequent `next` calls. The `throw` method similarly raises an error
at the point the generator is paused.

Compared with the counter in the Closures lesson, the difference is clear: a closure
holds state but does not hold execution. A generator, alongside state, also stores
**where in the program it left off**.

#### A Lazy Pipeline

Generators' most concrete gain is that transformation chains can be built without
producing intermediate arrays. When filtering and mapping are each written as a
generator, every value passes through the entire pipeline one at a time.

```js
let readCount = 0;
let transformCount = 0;

function* rawSource(values) {
  for (const value of values) {
    readCount += 1;
    yield value;
  }
}

function* filterGen(iterable, predicate) {
  for (const value of iterable) {
    if (predicate(value)) yield value;
  }
}

function* mapGen(iterable, transform) {
  for (const value of iterable) {
    transformCount += 1;
    yield transform(value);
  }
}

function* firstN(iterable, count) {
  if (count <= 0) return;
  let remaining = count;
  for (const value of iterable) {
    yield value;
    remaining -= 1;
    if (remaining <= 0) return;
  }
}

const raw = [21.4, 19.8, 25.1, 18.2, 30.0, 27.3, 15.0];
const pipeline = firstN(
  mapGen(
    filterGen(rawSource(raw), (d) => d > 20),
    (d) => Number((d * 1.8 + 32).toFixed(1)),
  ),
  2,
);

console.log(readCount);
console.log([...pipeline].join(","));
console.log(readCount);
console.log(transformCount);

const withArrays = raw
  .filter((d) => d > 20)
  .map((d) => Number((d * 1.8 + 32).toFixed(1)))
  .slice(0, 2);
console.log(withArrays.join(","));
```

```
0
70.5,77.2
3
2
70.5,77.2
```

The two paths give the same result; the work they do is different. The generator
pipeline reads only three of seven raw values and does two transforms; it stops the
moment the two needed results are obtained. The version written with array methods
first filters all five values, then transforms all five, then takes the first two —
and produces two intermediate arrays along the way.

The difference sharpens as the data source grows or the transform gets more expensive.
The generator setup, in exchange, has a fixed cost: several execution passes happen per
value. On small arrays, array methods are both shorter and faster; the criterion is the
number of unprocessed elements.

#### Summary

- A generator function's body does not run when called; it returns a generator object
  that is both iterator and iterable.
- Every `next` call advances to the next `yield` statement; execution pauses there and
  state is preserved.
- A starred method can be defined in a class body; generator methods stand on the
  prototype too.
- `yield*` delegates work to another iterable object; the `return` statement's value is
  visible only in manually continued `next` calls.
- The `next` argument becomes the value of the `yield` expression the generator is
  paused at; the `return` method performs cleanup by running the `finally` block.
- A pipeline built from generators produces no intermediate array and processes only as
  many values as requested.

#### Next Step

All the collections in this topic have held arbitrary JavaScript values: numbers,
strings, objects. When a measurement stream needs to be written to a file or sent over a
network, though, the data has to be represented as a byte sequence. The next lesson takes
up fixed-size binary buffers and the typed views over them; byte order and overflow
behavior will be tested there too.

### Typed Arrays

Source: https://academia.sh/en/courses/javascript-object-model/typed-arrays

Fixed-size binary buffers and the typed views over them, overflow and clamping behavior, explicitly specifying byte order, and how typed arrays differ from ordinary arrays.

All the collections in this topic have held arbitrary JavaScript values. When a
measurement stream needs to be written to a file or sent over a network, though, the data
has to be represented in a fixed layout that fits a specific number of bytes.

**Typed arrays** exist for this job. Two things work together: `ArrayBuffer`, which holds
raw bytes, and a **view**, which interprets those bytes as a specific number type. The
representations established in the Signed Integers, Floating-Point Numbers, and Byte
Order lessons of the How Computers Work course become directly observable here.

#### Buffer and View

`ArrayBuffer` holds only bytes; its content cannot be accessed directly. A view is set up
over it for access, and the view's type determines how those same bytes are read.

```js
const buffer = new ArrayBuffer(16);

console.log(buffer.byteLength);

const byteView = new Uint8Array(buffer);
const floatView = new Float64Array(buffer);

console.log(byteView.length);
console.log(floatView.length);
console.log(Uint8Array.BYTES_PER_ELEMENT);
console.log(Float64Array.BYTES_PER_ELEMENT);

floatView[0] = 21.4;
console.log(floatView[0]);
console.log([...byteView.slice(0, 8)].join(","));

byteView[0] = 0;
console.log(floatView[0]);
console.log(byteView.buffer === floatView.buffer);

const standalone = new Float64Array(2);
console.log(standalone.buffer === buffer);
console.log(standalone[0]);
```

```
16
16
2
1
8
21.4
102,102,102,102,102,102,53,64
21.399999999999636
true
false
0
```

The same sixteen bytes are sixteen integers in one view, two decimal numbers in the
other. Both views look at the same memory: the bytes of a value written to the float
view are read in the byte view, and a single change made in the byte view corrupts the
decimal value. The deviation in the eighth line is the floating-point representation's
counterpart of the least significant byte being zeroed out.

Byte layout depends on the platform's **native byte order**; the output above was
produced on a little-endian machine. The same program would show a different layout on a
big-endian machine. How to achieve portability is the subject of this lesson's third
section.

A view set up without a given buffer creates its own buffer and starts filled with
zeros.

#### Overflow and Clamping

A typed array's slot cannot hold a value outside the range its type allows. What happens
to out-of-range values differs by type.

```js
const wrapping = new Uint8Array(4);
const clamping = new Uint8ClampedArray(4);
const signed = new Int8Array(4);

const attempts = [300, -1, 255.9, 128];

attempts.forEach((value, i) => {
  wrapping[i] = value;
  clamping[i] = value;
  signed[i] = value;
});

console.log([...wrapping].join(","));
console.log([...clamping].join(","));
console.log([...signed].join(","));

const integer = new Int32Array(1);
integer[0] = 21.9;
console.log(integer[0]);

const decimal = new Float32Array(1);
decimal[0] = 21.4;
console.log(decimal[0]);
console.log(decimal[0] === 21.4);

const doublePrecision = new Float64Array(1);
doublePrecision[0] = 21.4;
console.log(doublePrecision[0] === 21.4);
```

```
44,255,255,128
255,0,255,128
44,-1,-1,-128
21
21.399999618530273
false
true
```

`Uint8Array` wraps the value around 256: 300 becomes 44, -1 becomes 255.
`Uint8ClampedArray` **clamps** to the range instead: 300 stops at 255, -1 stops at 0.
`Int8Array` interprets the same bytes as signed; 128 is read as -128 — the direct result
of two's complement representation.

Integer views drop the fractional part, no rounding is done: 21.9 becomes 21. The clamped
type, though, rounds to the nearest integer: 255.9 becomes 256 and is then clamped to the
range, staying at 255.

The last three lines show the precision difference. `Float32Array` stores the number in
single precision and the value read back differs from the original; `Float64Array` uses
JavaScript numbers' own representation, so it preserves the value exactly. In binary
record formats, the choice of field width directly determines this trade-off.

#### Explicitly Specifying Byte Order

Typed-array views use the native byte order; this causes problems when the data needs to
be read on another machine. `DataView` lets byte order be explicitly specified on every
read and write.

Consider placing measurement records into a fixed twelve-byte layout: four bytes for the
sensor number, eight bytes for the measured value.

```js
const RECORD_SIZE = 12;

function writeRecords(records, littleEndian) {
  const buffer = new ArrayBuffer(RECORD_SIZE * records.length);
  const view = new DataView(buffer);
  records.forEach((record, i) => {
    const offset = i * RECORD_SIZE;
    view.setUint32(offset, record.sensorId, littleEndian);
    view.setFloat64(offset + 4, record.value, littleEndian);
  });
  return buffer;
}

function readRecords(buffer, littleEndian) {
  const view = new DataView(buffer);
  const result = [];
  for (let offset = 0; offset < buffer.byteLength; offset += RECORD_SIZE) {
    result.push({
      sensorId: view.getUint32(offset, littleEndian),
      value: view.getFloat64(offset + 4, littleEndian),
    });
  }
  return result;
}

const records = [
  { sensorId: 1, value: 21.4 },
  { sensorId: 2, value: 19.8 },
];

const littleEndianBuffer = writeRecords(records, true);
const bigEndianBuffer = writeRecords(records, false);

console.log(littleEndianBuffer.byteLength);
console.log([...new Uint8Array(littleEndianBuffer).slice(0, 4)].join(","));
console.log([...new Uint8Array(bigEndianBuffer).slice(0, 4)].join(","));

console.log(JSON.stringify(readRecords(littleEndianBuffer, true)));
console.log(JSON.stringify(readRecords(bigEndianBuffer, false)));
console.log(readRecords(bigEndianBuffer, true)[0].sensorId);
```

```
24
1,0,0,0
0,0,0,1
[{"sensorId":1,"value":21.4},{"sensorId":2,"value":19.8}]
[{"sensorId":1,"value":21.4},{"sensorId":2,"value":19.8}]
16777216
```

The two buffers carry the same records but their bytes are laid out in reverse order.
This output is platform-independent: because `DataView` explicitly takes the order on
write and read, it does not look at the machine's native order at all.

The last line shows the cost of a wrong assumption. Data written as big-endian, when read
as little-endian, gives `16777216` instead of `1`; no error is thrown, the number just
becomes meaningless. The network-byte-order discussion in the Computer Networks
curriculum is the same problem's counterpart at the protocol level: when a binary format
is defined, byte order has to be written as part of the format.

#### Differences from an Ordinary Array

Typed arrays are not a subtype of the `Array` type. Their prototype chains are separate,
and they do not carry some array behaviors.

```js
const typed = new Float64Array([21.4, 19.8, 25.1]);
const ordinary = [21.4, 19.8, 25.1];

console.log(Array.isArray(typed));
console.log(Array.isArray(ordinary));
console.log(typeof typed.push);
console.log(typeof ordinary.push);

typed[5] = 99;
console.log(typed.length);
console.log(typed[5]);

const superPrototype = Object.getPrototypeOf(Float64Array.prototype);
console.log(superPrototype === Object.getPrototypeOf(Uint8Array.prototype));
console.log(superPrototype === Array.prototype);
console.log(Object.getPrototypeOf(superPrototype) === Object.prototype);

const mapped = typed.map((d) => d * 2);
console.log(mapped.constructor === Float64Array);
console.log([...mapped].join(","));
console.log([...typed].join(","));
console.log(JSON.stringify(typed));
console.log(JSON.stringify(Array.from(typed)));
```

```
false
true
undefined
function
3
undefined
true
false
true
true
42.8,39.6,50.2
21.4,19.8,25.1
{"0":21.4,"1":19.8,"2":25.1}
[21.4,19.8,25.1]
```

Length is fixed: writing out of bounds is silently ignored, growing methods like `push`
are not present. This is the language's counterpart of the Arrays vs. Dynamic Arrays
distinction from the Data Structures course — a typed array is the first, an ordinary
array the second.

The chain structure is separate too. All typed-array types share a common intermediate
prototype; methods like `map`, `filter`, `reduce` are defined there and have no
connection to `Array.prototype`. The intermediate prototype's prototype is
`Object.prototype` directly. `map` returning a typed array again is also the result of
this separate implementation.

The difference in serialization is clear: `JSON.stringify` writes a typed array as an
object with numeric keys, not as an array. If binary data needs to be carried in text
form, it has to be converted to an ordinary array first.

#### View or Copy

Two methods working on the same buffer establish a distinction that connects directly to
this course's last lesson: `subarray` returns a view, `slice` returns a copy.

```js
const allMeasurements = new Float64Array([21.4, 19.8, 25.1, 18.2, 30.0]);

const windowView = allMeasurements.subarray(1, 4);
const windowCopy = allMeasurements.slice(1, 4);

console.log([...windowView].join(","));
console.log([...windowCopy].join(","));
console.log(windowView.buffer === allMeasurements.buffer);
console.log(windowCopy.buffer === allMeasurements.buffer);

windowView[0] = 0;
windowCopy[1] = 0;

console.log([...allMeasurements].join(","));
console.log([...windowCopy].join(","));

console.log(windowView.byteOffset);
console.log(windowCopy.byteOffset);
console.log(allMeasurements.byteLength);
```

```
19.8,25.1,18.2
19.8,25.1,18.2
true
false
21.4,0,25.1,18.2,30
19.8,0,18.2
8
0
40
```

The two windows show the same values at the start. Writing to the view changes the
original array, writing to the copy does not. They differ in memory too: the view
allocates no new bytes and starts at an offset inside the original buffer; the copy
creates its own buffer.

Using a view for windowing operations on large measurement blocks allows working without
duplicating the data. In exchange, because a view keeps the original buffer reachable, a
small window can keep a large buffer alive in memory — another form of the reachability
discussion from the Weak Collections lesson.

#### Summary

- `ArrayBuffer` holds raw bytes; access is done through views that interpret the bytes as
  a specific number type.
- More than one view can be set up over the same buffer, and all of them share the same
  memory.
- Integer views wrap out-of-range values, the clamped type clamps to the range; the
  fractional part is dropped in integer types.
- Typed-array views use the native byte order; for portable binary formats, order is
  explicitly specified with `DataView`.
- Typed arrays have a chain separate from the `Array` type: their length is fixed, they
  cannot be grown, and they are serialized as objects.
- `subarray` returns a view looking at the same memory, `slice` returns an independent
  copy.

#### Next Step

In this lesson, the copy–view distinction showed up at the byte level; the same question
has been open for objects since the start of the course. Object spread lost the chain,
`Object.assign` turned accessors into plain values, `JSON.stringify` dropped methods and
`Map` content. The last lesson takes up this distinction in full: what gets carried and
what gets dropped when copying an object, and by which paths — with which losses — a deep
copy is done.

### Structured Cloning

Source: https://academia.sh/en/courses/javascript-object-model/structured-cloning

The references shallow copying shares, the losses of copying through serialization, the scope of structured cloning, and a deep copy that preserves the prototype and descriptors.

The previous lesson showed the copy–view distinction at the byte level. The same question
has been open for objects since the start of the course: object spread lost the chain,
`Object.assign` turned accessors into plain values, `JSON.stringify` dropped methods and
`Map` content.

This lesson answers that question in full. Three copying paths — spread, a round trip
through serialization, and structured cloning — are examined separately; what each one
carries and what it drops is shown. Then a deep copy preserving both is written.

#### The Limit of Shallow Copy

Object spread and `Object.assign` write the source's own enumerable properties to the
target. If a value is an object, what gets written is that object's **reference**; the
object itself is not copied.

```js
const source = {
  sensor: "S-01",
  value: 21.4,
  sourceInfo: { file: "measurement-2.csv", line: 17 },
  tags: ["raw", "temperature"],
};

const spread = { ...source };
const assigned = Object.assign({}, source);

console.log(spread.sensor);
console.log(spread.sourceInfo === source.sourceInfo);
console.log(assigned.tags === source.tags);

spread.sensor = "S-99";
spread.sourceInfo.line = 99;
spread.tags.push("changed");

console.log(source.sensor);
console.log(source.sourceInfo.line);
console.log(source.tags.join(","));
console.log(JSON.stringify(source.sourceInfo) === JSON.stringify(spread.sourceInfo));
```

```
S-01
true
true
S-01
99
raw,temperature,changed
true
```

A top-level field is independent: when `sensor` changes in the copy, the source is
unaffected. Inner objects, though, are shared; every change made through the copy is also
visible in the source. This is the shallow-copy–deep-copy distinction introduced in the
Value and Reference and Passing lesson of the Programming Fundamentals course.

The reason freezing stayed shallow in the Immutability Techniques lesson is the same:
both operations are limited to the object's own properties and do not descend into the
objects they point to.

#### Copying Through Serialization

Converting an object to text and reading it back recreates every inner object anew. The
losses are large, and these losses are silent.

```js
const source = {
  sensor: "S-01",
  value: 21.4,
  offset: undefined,
  measuredAt: new Date(0),
  tagSet: new Set(["raw", "temperature"]),
  calibration: new Map([["S-01", -0.4]]),
  format() {
    return this.sensor;
  },
};

const jsonCopy = JSON.parse(JSON.stringify(source));

console.log(Object.keys(jsonCopy).join(","));
console.log(typeof jsonCopy.format);
console.log("offset" in jsonCopy);
console.log(typeof jsonCopy.measuredAt);
console.log(jsonCopy.measuredAt);
console.log(JSON.stringify(jsonCopy.tagSet));
console.log(JSON.stringify(jsonCopy.calibration));

const cyclic = { sensor: "S-02" };
cyclic.self = cyclic;

try {
  JSON.stringify(cyclic);
} catch (error) {
  console.log(`cycle: ${error.constructor.name}`);
}
```

```
sensor,value,measuredAt,tagSet,calibration
undefined
false
string
1970-01-01T00:00:00.000Z
{}
{}
cycle: TypeError
```

Five losses can be listed. Functions and fields with an `undefined` value drop entirely.
A date object turns into a string, and reading it back gives text, not a date. `Set` and
`Map` content collapses to an empty object — the behavior seen in the first lesson of the
Advanced Collections topic. Non-enumerable properties and symbol keys are also skipped.
Finally, circular references throw an error.

The prototype chain is lost too: the object read back is always an ordinary object. This
path is suitable only for plain, nested objects that carry data alone — which is, after
all, JSON format's own scope.

#### Structured Cloning

The `structuredClone` function offered by the runtime deep-copies a far wider set of
values than serialization does and preserves cycles.

```js
class MeasurementRecord {
  constructor(sensor, value) {
    this.sensor = sensor;
    this.value = value;
    this.tagSet = new Set(["raw"]);
    this.measuredAt = new Date(0);
  }
  format() {
    return `${this.sensor}: ${this.value}`;
  }
}

const record = new MeasurementRecord("S-01", 21.4);
record.self = record;

const copy = structuredClone(record);

console.log(copy.sensor);
console.log(copy.self === copy);
console.log(copy.tagSet instanceof Set);
console.log(copy.tagSet.has("raw"));
console.log(copy.measuredAt instanceof Date);
console.log(copy.measuredAt.getTime());
console.log(copy.tagSet === record.tagSet);

console.log(Object.getPrototypeOf(copy) === MeasurementRecord.prototype);
console.log(copy instanceof MeasurementRecord);
console.log(typeof copy.format);

try {
  structuredClone({ fn: () => 1 });
} catch (error) {
  console.log(`function: ${error.name}`);
}

const typed = new Float64Array([21.4, 19.8]);
const typedCopy = structuredClone(typed);
console.log(typedCopy instanceof Float64Array);
console.log(typedCopy.buffer === typed.buffer);
```

```
S-01
true
true
true
true
0
false
false
false
undefined
function: DataCloneError
true
false
```

The gains are clear: circular references are preserved — the copy's field pointing back
to itself points to the copy, not the source. `Set`, `Date`, and typed arrays are
copied along with their types, and inner objects are not shared.

There is only one loss, but it is decisive for this course's axis: **the prototype chain
is not carried.** The eighth, ninth, and tenth lines show this in three separate ways —
the copy's prototype is not the class's prototype, the type query is negative, and the
method is not found. When a class instance is cloned, only the data remains.

Functions cannot be cloned either and throw an error. These two limits come from the same
reasoning: structured cloning is defined to transfer values into another execution
context; code and the chain are things that cannot be transferred.

#### A Copy That Preserves the Chain and Descriptors

If both depth and the chain are needed, the copy has to be written by hand. The pattern
is the sibling of deep freezing in the Immutability Techniques lesson: a recursive walk,
a visit table for cycles, and rebuilding the prototype at every node.

```js
function deepCopy(value, seen = new WeakMap()) {
  if (value === null || typeof value !== "object") return value;
  if (seen.has(value)) return seen.get(value);

  if (value instanceof Date) return new Date(value.getTime());
  if (value instanceof Set) {
    const set = new Set();
    seen.set(value, set);
    for (const item of value) set.add(deepCopy(item, seen));
    return set;
  }
  if (value instanceof Map) {
    const map = new Map();
    seen.set(value, map);
    for (const [key, content] of value) {
      map.set(deepCopy(key, seen), deepCopy(content, seen));
    }
    return map;
  }

  const copy = Array.isArray(value)
    ? []
    : Object.create(Object.getPrototypeOf(value));
  seen.set(value, copy);

  for (const name of Reflect.ownKeys(value)) {
    const descriptor = Object.getOwnPropertyDescriptor(value, name);
    if ("value" in descriptor) descriptor.value = deepCopy(descriptor.value, seen);
    Object.defineProperty(copy, name, descriptor);
  }
  return copy;
}

class MeasurementRecord {
  constructor(sensor, value) {
    this.sensor = sensor;
    this.value = value;
    this.sourceInfo = { file: "measurement-2.csv", line: 17 };
  }
  format() {
    return `${this.sensor}: ${this.value}`;
  }
}

const record = new MeasurementRecord("S-01", 21.4);
record.self = record;
Object.defineProperty(record, "hiddenCounter", {
  value: 3,
  enumerable: false,
  writable: true,
  configurable: true,
});

const copy = deepCopy(record);

console.log(copy.format());
console.log(copy instanceof MeasurementRecord);
console.log(copy.self === copy);
console.log(copy.sourceInfo === record.sourceInfo);
console.log(copy.sourceInfo.line);
console.log(copy.hiddenCounter);
console.log(Object.keys(copy).join(","));
console.log(Object.getOwnPropertyDescriptor(copy, "hiddenCounter").enumerable);

copy.sourceInfo.line = 99;
console.log(record.sourceInfo.line);
```

```
S-01: 21.4
true
true
false
17
3
sensor,value,sourceInfo,self
false
17
```

The copy is a genuine measurement record: its method works, the type query is positive,
its cycle points back to itself, its inner object is independent, and its non-enumerable
field is preserved along with its flag. Choosing `Reflect.ownKeys` ensures symbol keys are
covered too; copying the descriptor keeps accessors from collapsing into plain values.

This pattern has its limits too. Private fields cannot be copied — because they cannot be
read from outside, a library function has no access to them. State held in a closure is
not carried for the same reason. The general rule is: **what can be copied is what can be
seen from outside.**

#### Three Setups Under Copying

Throughout the course, the measurement record was built in three forms. Copying is the
test that lays the difference between them bare most sharply.

```js
function plainRecord(sensor, value) {
  return {
    sensor,
    value,
    format() {
      return `${this.sensor}: ${this.value}`;
    },
  };
}

function closureRecord(sensor, value) {
  return {
    format() {
      return `${sensor}: ${value}`;
    },
  };
}

class ClassRecord {
  constructor(sensor, value) {
    this.sensor = sensor;
    this.value = value;
  }
  format() {
    return `${this.sensor}: ${this.value}`;
  }
}

function check(name, obj) {
  const spread = { ...obj };
  let cloneResult;
  try {
    const clone = structuredClone(obj);
    cloneResult = typeof clone.format === "function" ? clone.format() : "no behavior";
  } catch (error) {
    cloneResult = `error:${error.name}`;
  }
  const spreadResult =
    typeof spread.format === "function" ? spread.format() : "no behavior";
  console.log(`${name}: json=${JSON.stringify(obj)}, spread=${spreadResult}, clone=${cloneResult}`);
}

check("plain   ", plainRecord("S-01", 21.4));
check("closure ", closureRecord("S-01", 21.4));
check("class   ", new ClassRecord("S-01", 21.4));

const classInstance = new ClassRecord("S-01", 21.4);
const chainedCopy = Object.defineProperties(
  Object.create(Object.getPrototypeOf(classInstance)),
  Object.getOwnPropertyDescriptors(classInstance),
);
console.log(chainedCopy.format());
console.log(chainedCopy instanceof ClassRecord);
```

```
plain   : json={"sensor":"S-01","value":21.4}, spread=S-01: 21.4, clone=error:DataCloneError
closure : json={}, spread=S-01: 21.4, clone=error:DataCloneError
class   : json={"sensor":"S-01","value":21.4}, spread=no behavior, clone=no behavior
S-01: 21.4
true
```

The three setups show three separate behaviors. In the plain object, because data and
behavior stand in the same object, serialization takes the data, spread also carries the
behavior, and structured cloning throws an error because of the function. In the
closure-based setup, because the data is never a property at all, serialization comes out
empty; spread copies only the function, and because the function carries its own closure,
the result is still correct.

In the class setup, data and behavior are in separate places: data in the instance,
behavior in the chain. Because both copying paths take only the instance, the behavior
drops — but no error is thrown either. If the chain is needed, it has to be set up
explicitly, as in the last two lines.

The sentence from the course's first lesson closes here: **an object's data is in
itself, its identity is in its chain.** Copying operations carry own properties; the
chain is carried only when it is set up explicitly.

#### Summary

- Shallow copy separates only top-level fields; inner objects are shared with the
  source.
- Copying through serialization drops functions, `undefined` fields, non-enumerable and
  symbol-keyed properties; it corrupts `Set`, `Map`, and `Date` types, and throws an
  error on cycles.
- `structuredClone` preserves cycles and copies many built-in types along with their
  type, but does not carry the prototype chain and throws an error on functions.
- A copy preserving the chain and descriptors is written by hand: the prototype is
  rebuilt at every node, descriptors are carried exactly, cycles are tracked with a visit
  table.
- Private fields and state in a closure cannot be copied; what can be copied is what can
  be seen from outside.

#### Course Wrap-Up

The course started with a single question: how is a property that was never written at
all found? The answer was the prototype link every object carries, and this link was
measured across seventeen lessons. Property lookup started from the object itself and
proceeded up the chain; writing never climbed upward, it always shadowed. The job the
`new` operator does was rewritten in three lines. Which prototype operation each line of
a class declaration corresponds to was shown with the same tests — a class is the
syntactic face of the chain; there is no different object model underneath it.

The second topic took up the question the chain does not answer. A prototype determines
where a function will be found; `this` determines which data it will work on. Because the
two are independent, a method can be detached from its object, lent to another object, or
permanently bound. Arrow functions fall outside these four rules and take their context
from the scope they are defined in. Closures, meanwhile, offered a second construction
path that hides state without touching the chain at all.

The third topic examined collections by the same criteria: `Map` preserves key identity,
weak collections do not extend objects' lifetime, the iteration protocol is a single
method found on the chain, generators produce that method themselves, typed arrays have a
separate chain. The last lesson set up the copying test: structures that carry a type are
copied with it, but the prototype chain is not.

The measurement record's three setups — plain object, factory with closure, class — were
held side by side throughout the course and compared at every new concept: sharing,
visibility, binding, privacy, serialization, and copying. There is no single correct
choice among them; the criteria are explicit, and the comparisons can be measured with
the outputs in these lessons.

One thing is left out. Every example in this course resolved instantly: every
`console.log` call ran only after the line before it had finished. This assumption
breaks down once measurements come from a file or a network — the result is not ready the
moment it is requested. The **Asynchronous JavaScript and the Runtime** course takes up
this situation: the single-threaded execution model, the event loop's task and microtask
queues, promise-based flows, and the memory life cycle. The chain and context rules you
learned in this course apply there too; the only thing that changes is **when** the code
runs.

