Modern JavaScript & ECMAScript
You can already write JavaScript. This chapter gives you the map: how the language is versioned, what ES6 changed, where your code runs, and the two modes (scripts and modules) that quietly change the rules.
You will learn
- The difference between JavaScript, ECMAScript, engines and runtimes.
- How a feature becomes part of the language (TC39 stages) and the ES5 → ES2026 timeline.
- ES6 (ES2015) at a glance: the release that changed how JavaScript is written.
- How to run code in Node.js and in the browser console.
- Strict mode, and how scripts differ from modules.
JavaScript, ECMAScript, engines and runtimes
Four words get mixed up all the time. Keeping them apart explains a lot of "why does this work here but not there?" moments.
| Word | Meaning | Examples |
|---|---|---|
| ECMAScript | The language specification (ECMA-262): syntax, types, built-in objects like Array and Promise | ES5, ES2015 (ES6), ES2025 |
| JavaScript | The everyday name for ECMAScript plus whatever the host adds | — |
| Engine | The program that parses and runs the language | V8 (Chrome, Node), SpiderMonkey (Firefox), JavaScriptCore (Safari) |
| Runtime / host | An engine plus extra APIs for its environment | Browsers add document, fetch; Node adds fs, process; also Deno and Bun |
So Array.prototype.map is language and works everywhere, while document.querySelector is a browser API and doesn't exist in Node. This book teaches the language and uses Node 26 to run it. When an example needs a browser, it says so.
☕ Java: despite the name, JavaScript is not related to Java. The syntax looks similar (braces, if, for), but JavaScript is dynamically typed, uses prototypes instead of class-based inheritance under the hood, and has first-class functions. The name was a 1995 marketing decision.
How the language evolves
A committee called TC39 designs new features. Each proposal moves through numbered stages: 0 (idea), 1 (proposal), 2 (draft), 2.7 (approved in principle, needs tests), 3 (ready to implement), and 4 (finished). Everything that reaches stage 4 by early in the year goes into that year's edition. Engines often ship a feature at stage 3, before it's officially "in" the standard.
| Edition | Year | Highlights |
|---|---|---|
| ES3 | 1999 | regular expressions, try/catch |
| ES5 | 2009 | strict mode, JSON, Array.prototype.forEach/map/filter, Object.defineProperty |
| ES6 / ES2015 | 2015 | the big one: let/const, arrows, classes, modules, promises, template literals, destructuring, spread/rest, default parameters, Map/Set, Symbol, iterators and generators, for…of, Proxy/Reflect |
| ES2016 | 2016 | **, Array.prototype.includes |
| ES2017 | 2017 | async/await, Object.entries/values, padStart |
| ES2018 | 2018 | object rest/spread, async iteration, regex named groups and lookbehind |
| ES2019 | 2019 | flat/flatMap, Object.fromEntries, optional catch binding |
| ES2020 | 2020 | ?., ??, BigInt, Promise.allSettled, dynamic import(), globalThis |
| ES2021 | 2021 | ??= ||= &&=, replaceAll, Promise.any, numeric separators 1_000, WeakRef |
| ES2022 | 2022 | class fields and #private, static blocks, top-level await, .at(), Error cause, Object.hasOwn |
| ES2023 | 2023 | toSorted/toReversed/toSpliced/with, findLast |
| ES2024 | 2024 | Object.groupBy, Promise.withResolvers, regex v flag |
| ES2025 | 2025 | iterator helpers, Set methods (union, …), Promise.try, RegExp.escape, JSON import attributes, Float16Array |
| ES2026 | 2026 | (finalised or shipping in engines during 2025–26) using / DisposableStack (resource management), Error.isError, Array.fromAsync |
Throughout the book, a badge like ES2015 or ES2023 shows when a feature arrived. Blue badges mark ES6 features; the yellow ones are later.
📝 Why "ES6" and "ES2015" both exist: the sixth edition was released in 2015. After it, TC39 switched to yearly releases named after the year, so there is no "ES7" in official use, though people still say it.
ES6 at a glance ES2015
If your JavaScript habits were formed before 2015, or by tutorials that copied that style, this table is your upgrade path. Each row is covered in depth in the chapter listed.
| Feature | Before (ES5) | After (ES6+) | Chapter |
|---|---|---|---|
| Block-scoped variables | var x = 1; | let x = 1; const y = 2; | 2 |
| Arrow functions | function (x) { return x * 2; } | x => x * 2 | 4 |
| Default and rest parameters | b = b || 1; arguments | (a, b = 1, ...rest) => {} | 4 |
| Template literals | "Hi " + name + "!" | `Hi ${name}!` | 6 |
| Destructuring | var a = obj.a, b = obj.b; | const { a, b } = obj; | 5 |
| Spread | arr1.concat(arr2) | [...arr1, ...arr2] | 5 |
| Shorthand object literals | { name: name, f: function () {} } | { name, f() {} } | 10 |
for…of | for (var i = 0; i < a.length; i++) | for (const x of a) | 3 |
| Classes | constructor functions + prototype | class A extends B {} | 11 |
| Promises | nested callbacks | fetchX().then(…) | 12 |
| Iterators and generators | hand-written index loops | function* gen() { yield 1; } | 13 |
| Modules | IIFEs, global variables, CommonJS | import / export | 14 |
Map, Set, WeakMap | plain objects as maps | new Map() | 9 |
Symbol, Proxy, Reflect | — | metaprogramming | 15 |
Running JavaScript
In Node.js
Node.js runs JavaScript outside the browser. This book uses Node 26. Check your version:
Typing node alone opens the REPL (read–eval–print loop). Type an expression and Node prints its value:
📝 About REPL blocks in this book: lines starting with > are what you type, and the lines below are Node's answer. The real REPL also prints undefined after statements like const x = 1; the book leaves those out to reduce noise. Appendix B covers the REPL in detail.
To run a file, pass its name:
In the browser
Every browser has a JavaScript console in its developer tools: press F12, or Cmd+Option+J on a Mac in Chrome. It works like the Node REPL, but with browser APIs such as document available. In a web page, you load code with a <script> tag:
Strict mode
ES5 introduced strict mode, an opt-in variant of the language that turns silent mistakes into errors and removes a few confusing features. You turn it on by putting the string "use strict"; at the very top of a script or a function.
| Situation | Sloppy mode | Strict mode |
|---|---|---|
| Assigning to an undeclared variable | silently creates a global | ReferenceError |
this in a plain function call | the global object | undefined |
| Writing to a read-only property | silently ignored | TypeError |
Duplicate parameter names function f(a, a) | allowed | SyntaxError |
with statement, octal literals like 010 | allowed | SyntaxError |
💡 Good news: you rarely write "use strict" yourself today. Modules and class bodies are always strict. Since this book uses modules everywhere, you get strict mode for free.
Scripts vs modules
The same code can behave differently depending on whether it's loaded as a classic script or as a module (an ES module, or ESM, covered fully in Chapter 14).
| Classic script | Module | |
|---|---|---|
| Strict mode | off unless "use strict" | always on |
| Top-level variables | shared between all scripts (global) | private to the module |
Top-level this | the global object (browser) / module.exports (Node CJS) | undefined |
import / export | not allowed (only dynamic import()) | allowed |
Top-level await | no | yes ES2022 |
| In Node | .cjs, or .js without "type": "module" | .mjs, or .js with "type": "module" in package.json |
⚠️ Pitfall: copy a snippet that uses import into a plain .js file, run it in a folder with no package.json, and older Node versions throw SyntaxError: Cannot use import statement outside a module. Node 22+ detects ESM syntax automatically, but the clear fix is to use .mjs, or to add "type": "module" to package.json, as the book's playground does.
More examples
1. Feature detection instead of version checks
2. ES5 vs ES6 in one small function
3. Which runtime am I in?
4. A tiny module pair
5. The global object, everywhere
globalThis ES2020 is the standard name for the global object. Before it, you needed window in browsers, global in Node, and self in workers.
Recap
- ECMAScript is the spec, engines run it, and runtimes (browsers, Node) add host APIs.
- TC39 ships a new edition every year. ES6/ES2015 was the big modernisation.
- Run code with the Node REPL,
node file.js, or the browser console. - Strict mode turns silent bugs into errors, and modules and classes are always strict.
- Modules have private top-level scope,
this === undefinedat the top,import/exportand top-levelawait.
Exercises
Exercise 1 🟢 easy explain why
Why does document.title fail with ReferenceError: document is not defined in Node, while [1, 2].includes(2) works in both Node and the browser?
Show solution
document is a browser host API, not part of ECMAScript, so Node doesn't provide it. Array.prototype.includes is part of the language itself (ES2016), so every modern engine has it.
Exercise 2 🟢 easy build it · playground
In playground/, create hello.js that prints the Node major version and the current year, like Node 26 in 2026. Run it with node hello.js.
Show solution
process is a Node host object, and the template literal (Chapter 6) inserts the values.
Exercise 3 🟢 easy predict the output
Which edition introduced each feature? let, async/await, ??, Object.groupBy
Show solution
let: ES2015. async/await: ES2017. ??: ES2020. Object.groupBy: ES2024.
Exercise 4 🟢 easy fix the bug
This classic script has a typo that strict mode would catch. Add strict mode and say what changes.
Show solution
With "use strict" and the original typo, the call throws ReferenceError: quantiy is not defined instead of quietly creating a global. Declaring the variable fixes it.
Exercise 5 🟡 medium predict the output
What does this print when saved as a.mjs and run with node a.mjs?
Show solution
undefined undefined. At the top of a module, this is undefined, and even var at module level doesn't create a property on the global object, because module scope is private.
Exercise 6 🟡 medium refactor it
Rewrite this ES5 function in modern style: default parameter, arrow function, template literal and const.
Show solution
A default parameter only applies when the argument is undefined. The ES5 || trick also replaced "", which could be a bug.
Exercise 7 🟡 medium build it · playground
Create playground/features.js that checks for Object.groupBy, Set.prototype.union, Iterator.prototype.map, Promise.try and DisposableStack, and prints a ✓ or ✗ line for each.
Show solution
Each check is a function, so a missing global like Iterator can't crash the script before the check runs: ?. stops early instead.
Exercise 8 🟡 medium explain why
A teammate says: "This feature is stage 3, so it's part of ES2025." Is that right?
Show solution
No. Stage 3 means the design is finished and engines may ship it, but it only joins an edition after reaching stage 4 (two shipping implementations and tests). Many stage-3 features are already usable in Node or browsers, but they're not "in" the standard yet.
Exercise 9 🔴 hard predict the output
Same code, two files. What does each print?
Show solution
As t.cjs (sloppy mode) it prints object: a plain call gets the global object as this. As t.mjs (a module, so strict) it prints undefined. Chapter 4 explains this fully.
Exercise 10 🔴 hard mini-project · playground
Create playground/env-report.js, a module that prints a small report: the Node version, whether the file runs as a module (typeof this === "undefined" at the top), whether top-level await works, and a sorted list of three ES2023+ features that are available. Use template literals, const, arrows and Object.entries.
Show solution
It combines this chapter's ideas: module facts (this, top-level await), feature detection, and a first taste of ES6+ syntax that later chapters explain.