JavaScript Essentials
JavaScript is the language of Node.js
Section titled “JavaScript is the language of Node.js”Node.js is a runtime that executes JavaScript outside the browser. Everything you write in Node — from a simple HTTP server to a complex CLI tool — is JavaScript. That means a solid grip on the language itself is non-negotiable before diving into Node’s APIs.
This module covers the JavaScript fundamentals that trip up developers most in the Node ecosystem:
| Lesson | Topic |
|---|---|
| Types and Coercion | Primitives vs objects, typeof, == vs ===, truthy/falsy |
| Scope and Closures | var/let/const, block scope, hoisting, lexical scope |
this and Context | Dynamic this, call/apply/bind, arrow functions |
| Modules | ESM import/export vs CommonJS require, when to use each |
Modern JavaScript means ES2022 and later throughout — const/let, arrow functions, destructuring, async/await, optional chaining, and native ES modules. This module targets Node 24 (the active LTS); everything also runs on Node 26.
A first runnable demo
Section titled “A first runnable demo”The snippet below demonstrates several JS concepts at once: a const declaration, a function, a template-string-free concatenation, and a closure over the outer variable. Run it and inspect the output.
const greet = function(name) { return 'Hello, ' + name + '!';};
const languages = ['JavaScript', 'TypeScript', 'Node.js'];
for (const lang of languages) { console.log(greet(lang));}Newer language features you will meet
Section titled “Newer language features you will meet”Node upgrades V8 with every major release, so the language keeps growing:
- Node 24 —
using/await usingfor automatic resource cleanup,RegExp.escape(),Error.isError(),Float16Array. - Node 26 — the Temporal date/time API is enabled by default, plus
Map.prototype.getOrInsert()andIterator.concat().
// @skip-verify Node 26-only (Temporal) + illustrative openConnection()// Node 26: Temporal replaces error-prone Date arithmeticconst due = Temporal.Now.plainDateISO().add({ days: 30 });console.log(due.toString()); // e.g. 2026-10-12
// Node 24: using — the resource is disposed when the block exits{ using conn = openConnection(); // the object has a [Symbol.dispose]() method conn.query('SELECT 1');} // conn[Symbol.dispose]() runs here, even on throwScript mode: sloppy vs strict
Section titled “Script mode: sloppy vs strict”Every piece of JavaScript runs in one of two modes, decided by how the file is loaded, not by what it does:
- Sloppy mode — the historical default. A classic
<script>tag in a browser, or a.jsfile in a package without"type": "module", runs sloppy unless it opts out. Sloppy mode forgives mistakes that would otherwise be bugs: assigning to a name that was never declared silently creates a global variable instead of throwing, and a plain function call setsthisto the global object instead ofundefined. - Strict mode — turns those silent forgivenesses into thrown errors. You get strict mode in three ways: writing
'use strict';as the very first statement in a script or function, loading the file as an ES module (.mjs, or.jswith"type": "module"— always strict, no directive needed), or writing code inside aclassbody (class bodies are strict even in an otherwise sloppy file).
flowchart TD A["How is this code loaded?"] --> B["Classic script / CommonJS .js file"] A --> C["ES module: .mjs, or .js with package.json type: module"] A --> D["Any code inside a class body"] B -->|"no directive"| E["Sloppy mode"] B -->|"'use strict' as first line"| F["Strict mode"] C --> F D --> F E --> G["Undeclared assignment creates a global; this === global object in a plain call"] F --> H["Undeclared assignment throws ReferenceError; this === undefined in a plain call"]
This is exactly why the this and Context demo later in this module opens with 'use strict';: without it, a detached method call would silently fail by mutating the wrong object instead of throwing an error you can see and fix.
// @skip-verify illustrative — contrasts what a plain (sloppy) script allows// vs what strict mode (any ES module, any class, or an explicit directive) rejectsfunction setTotal() { total = 42; // no const/let/var}setTotal();console.log(total); // sloppy mode: 42 — an implicit global was createdMental Model
Section titled “Mental Model”Think of sloppy mode as JavaScript running with a permissive safety net: typos and missing declarations get quietly absorbed instead of stopping the program. Strict mode removes that net. It does not add new features — it removes silent failure paths, converting them into thrown errors at the exact line where the mistake happened. Because ES modules and classes are strict unconditionally, and essentially all new Node.js code is written as ES modules, you should default to assuming strict mode and treat sloppy mode as the legacy exception you occasionally still meet in old CommonJS files.
Trade-offs
Section titled “Trade-offs”| Mode | Benefit | Cost |
|---|---|---|
| Sloppy (legacy default) | Old scripts keep running unmodified; nothing new to learn | Typos become invisible global variables instead of errors; this in a plain call silently becomes the global object |
Strict (ESM, classes, 'use strict') | Mistakes surface immediately as ReferenceError/TypeError at the point of the bug | A handful of legacy patterns (some arguments tricks, with) stop working — rarely missed in modern code |
Common Misconceptions
Section titled “Common Misconceptions”- “
'use strict'is an old ES5 relic nobody needs anymore.” You still need it in plain CommonJS.js/.cjsfiles if you want strict guarantees there — but you get it for free, with no directive, in every ES module and every class. - “Strict mode changes what the code computes.” It does not add or change arithmetic or object semantics; it only turns previously-silent mistakes into thrown errors.
- “If my file has
"type": "module"inpackage.json, I still need'use strict'at the top.” No — the directive is redundant there; ESM is strict unconditionally.
Reading AI-generated code
Section titled “Reading AI-generated code”AI-generated snippets are frequently written and tested as quick CommonJS scripts (sloppy by default) and then assumed to behave identically once dropped into a class or an ES module. Watch for code that relies on an assignment “just working” without a preceding const/let/var — that is a latent bug that only a strict context (ESM, a class, or an explicit directive) will actually catch for you.
🐛 Spot the bug
function cache(key, value) {if (!store) store = {}; // no declaration — meant to be a shared cachestore[key] = value;return store;}
cache('a', 1);cache('b', 2);console.log(store); // "works" by accident todayShow the answer
store is never declared with const/let/var. In a sloppy CommonJS script this silently creates a global named store the first time cache runs, which is why it “works” — every call happens to share the same accidental global. Drop this same function into an ES module, a class method, or add 'use strict' to the file, and the first call throws ReferenceError: store is not defined instead. The fix is to declare the variable in an enclosing scope (module-level const store = {};) so the behavior does not depend on which mode the file happens to run in.