Free JavaScript reference · Python timeline
JavaScript ES2022: Class privacy, relative indexing, and module await
Private fields and public class fields arrive with at(), Object.hasOwn(), and top-level await in modules.
Edition 13 · Published · Editorial review October 26, 2026
Standardized in ES2022 · Syntax
Public fields and #private fields
Class fields initialize instance state directly in the class body. A hash-prefixed private field is enforced by the language and can be accessed only by code with the matching class declaration's private-name access.
This differs from an underscore convention or a normal string-keyed property. Private fields are not ordinary keys that Object.keys will reveal. Methods still need a valid receiver that carries the expected private state.
The example exposes a public label while protecting the count behind a method. Passing increment around without its receiver can still fail; privacy does not automatically bind methods.
Worked example
Uses ES2022 features · Script
class Counter { label = "visits"; #value = 0; increment() { return ++this.#value; } } const counter = new Counter(); console.log(counter.label, counter.increment()); console.log(Object.keys(counter).join(", "));
Expected output
visits 1
label
Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.
Common mistake
counter["#value"] is an unrelated ordinary property, not a way to read the private field. External counter.#value syntax is invalid.
Try it yourself
Create two counters and show that their private values advance independently.
Read more: ES2022 specification · MDN explanation and compatibility
Standardized in ES2022 · Built-in method
Relative indexing with at()
at accepts negative indices relative to the end of an array, string, or typed array. This makes the final element accessible without repeating the length expression. An out-of-range index produces undefined.
A negative bracket key such as values[-1] is an ordinary property name rather than relative array indexing. For strings, at works in UTF-16 code units; it is not a grapheme-aware character operation.
The example checks a final element and an empty sequence. Decide what an absent element means before applying a fallback.
Worked example
Uses ES2022 features · Script
const values = [4, 8, 12]; console.log(values.at(-1)); console.log(values[-1] === undefined); console.log([].at(-1) === undefined);
Expected output
12
true
true
Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.
Common mistake
at(-1) is not a promise that a sequence is nonempty. Strings can still split surrogate pairs or combining sequences.
Try it yourself
Predict values.at(-4) and explain how it differs from Python indexing that raises an exception.
Read more: ES2022 specification · MDN explanation and compatibility
Standardized in ES2022 · Built-in method
Object.hasOwn() tests presence safely
Object.hasOwn checks whether a property belongs directly to an object. It distinguishes a missing property from a present property whose value is undefined, and it ignores inherited properties.
Using the static method avoids relying on a possibly shadowed hasOwnProperty method. It also works with null-prototype records, which do not inherit that method at all.
The example is a presence check rather than a truthiness check. A present empty or undefined field may have a different meaning from an absent field in a data contract.
Worked example
Uses ES2022 features · Script
const record = Object.create(null); record.name = undefined; console.log(Object.hasOwn(record, "name")); console.log(Object.hasOwn(record, "toString"));
Expected output
true
false
Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.
Common mistake
if (record.name) does not test property presence. The in operator includes inherited properties and answers a different question.
Try it yourself
Create an object with an inherited name and compare Object.hasOwn with the in operator.
Read more: ES2022 specification · MDN explanation and compatibility
Standardized in ES2022 · Modules
Top-level await in modules
ES2022 permits await at the top level of an ES module. A module can finish asynchronous initialization before its dependent modules consume its exports. It remains invalid at the top level of a classic script.
This is useful for initialization that genuinely belongs to a module, but it can delay a dependency graph. Keep the work predictable and think carefully about circular dependencies or services that may never become ready.
Save this example as an .mjs file or use a browser module. The locally resolved promise demonstrates the syntax without requiring a network or filesystem service.
Worked example
Uses ES2022 features · ES module
const factor = await Promise.resolve(2); export const total = factor * 3; console.log(total);
Expected output
6
Run as an ES module. The practice editor expects function submissions, so this entire module is not a drop-in exercise solution.
Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.
Common mistake
A module context is required. Top-level await is not a way to paste arbitrary module code into an existing synchronous function.
Try it yourself
Import total from another module and explain when that importer is allowed to use it.
Read more: ES2022 specification · MDN explanation and compatibility
Recap
Private fields and public class fields arrive with at(), Object.hasOwn(), and top-level await in modules. Check the engines your project supports before adopting new syntax or built-ins.
Read the ES2022 specification →