Skip to content

JavaScript

JavaScript is a dynamically typed, single-threaded, garbage-collected programming language with first-class functions and prototype-based objects. It began as a browser scripting language in 1995 and is now the only language that runs natively in every web browser, plus a first-class server language via Node.js, Deno, and Bun.

Three things get called JavaScript, and keeping them apart removes most confusion:

Term What it is
ECMAScript The written language specification, maintained by TC39 and published by Ecma International as ECMA-262. Defines syntax, types, operators, and the built-in objects (Array, Promise, Math, …).
JavaScript The common name for implementations of ECMAScript. The language itself.
The host environment Everything the spec does not define: document, fetch, localStorage in a browser; fs, process, require in Node. These are APIs the host adds.

This distinction matters constantly. Array.prototype.map works everywhere because it is ECMAScript. document.querySelector only works in a browser because it is a host API defined by the WHATWG DOM standard, not by ECMAScript.

Since 2015 the spec ships once a year, named by year: ES2015 (also called ES6), ES2016, ES2017, and so on through ES2024, ES2025, and beyond. Each release is small — a handful of features that have already passed through TC39’s four-stage proposal process and shipped in real engines.

You will still see the old numbering: ES5 (2009) is the baseline that pre-modern browsers supported, and ES6 = ES2015 was the huge release that introduced let/const, arrow functions, classes, modules, promises, Map/Set, template literals, and destructuring. Most of what people mean by “modern JavaScript” arrived in ES2015.

An engine is the program that actually parses and executes JavaScript. The major ones:

  • V8 — Chrome, Edge, Node.js, Deno
  • SpiderMonkey — Firefox
  • JavaScriptCore — Safari, Bun

All modern engines are JIT (just-in-time) compilers: they parse your source to bytecode, interpret it, watch which functions run hot, then compile those to optimized machine code. You do not compile JavaScript ahead of time.

Every browser embeds an engine. The browser gives your code the DOM (an object model of the page), plus networking, storage, timers, and hundreds of other Web APIs. This is covered in the DOM and the browser.

index.html
<!doctype html>
<html>
<body>
<h1 id="title">Hello</h1>
<script>
document.getElementById('title').textContent = 'Hello from JavaScript';
</script>
</body>
</html>

Node.js embeds V8 and adds APIs for filesystem access, networking, processes, and modules. It is how JavaScript became a general-purpose server language.

Terminal window
node --version
# => v22.11.0 (or whatever you have)

Deno and Bun are newer runtimes with the same core language. Deno emphasizes security-by-default (explicit permission flags) and built-in TypeScript. Bun emphasizes speed and bundles a package manager, test runner, and bundler. All three run standard ECMAScript; they differ in host APIs and tooling.

The same engines are embedded in desktop apps (Electron), mobile apps (React Native), edge/serverless platforms (Cloudflare Workers, Vercel), and databases. If you learn the language, the host API is the only new part.

You have three good options. Use whichever is closest to hand.

1. Browser console. Open any page, press F12 (or Cmd+Option+I on macOS), go to the Console tab, and type. It evaluates immediately and prints the result. Best for quick experiments and anything DOM-related.

2. A file with Node.

Terminal window
# create a file
cat > demo.js <<'EOF'
const users = ['ada', 'grace', 'alan'];
console.log(users.map((n) => n.toUpperCase()));
EOF
node demo.js
[ 'ADA', 'GRACE', 'ALAN' ]

3. The Node REPL. Run node with no arguments for an interactive prompt. It prints the value of every expression, so you do not need console.log.

Terminal window
node
Welcome to Node.js v22.11.0.
Type ".help" for more information.
> 2 ** 10
1024
> [1, 2, 3].reduce((a, b) => a + b)
6
> .exit

You can also run a one-liner without a file:

Terminal window
node -e "console.log(new Date().toISOString())"

Five properties shape almost everything about how JavaScript behaves. Understanding them up front makes the rest of this section click.

Variables do not have types; values do. A variable can hold a number now and a string later. Types are checked at runtime, not before.

let x = 42;
x = 'now a string'; // perfectly legal
x = { legal: true }; // still legal

The upside is speed of writing. The downside is that type errors surface at runtime, sometimes far from the mistake. That is precisely the problem TypeScript solves.

When you mix types, JavaScript converts rather than complaining. Sometimes that is convenient; sometimes it is the source of the language’s most famous oddities.

'5' + 3; // => '53' (+ prefers string concatenation)
'5' - 3; // => 2 (- has no string meaning, so both become numbers)
[] + {}; // => '[object Object]'

The rules are learnable and mostly avoidable — see operators and control flow.

Objects inherit directly from other objects through a hidden link called the prototype. There are no classes underneath — the class keyword added in ES2015 is syntax over the prototype system. See objects and prototypes.

A function is an object. You can store it in a variable, pass it as an argument, return it from another function, and attach properties to it. Closures — functions that capture the variables around them — follow directly from this and are used everywhere. See functions and scope.

const twice = (fn) => (x) => fn(fn(x));
const inc = (n) => n + 1;
twice(inc)(5); // => 7

Your JavaScript runs on one thread. It never runs two pieces of your code at the same time. Long operations (network requests, file reads, timers) are handed to the host, which calls you back later through the event loop. That is why asynchronous code is not optional in JavaScript — it is the only way to avoid freezing everything. See async JavaScript.

Hold these four ideas and most JavaScript behavior becomes predictable:

  1. Values are either primitives or objects. Primitives (number, string, boolean, null, undefined, symbol, bigint) are immutable and copied by value. Everything else is an object, handled by reference. Nearly every “why did my array change?” bug is this.

  2. Scope is lexical. What a name refers to is decided by where the code is written, not where it is called from. The one exception is this, which is decided by how a function is called — which is why this confuses people.

  3. Objects delegate. Looking up obj.foo checks obj, then its prototype, then that prototype’s prototype, until null. Methods you did not define come from up that chain.

  4. The event loop runs one task at a time to completion. Nothing interrupts a running function. Callbacks, promises, and await continuations all wait in queues for the current work to finish.

Read in order the first time; each page assumes the ones before it.

# Page What it covers
2 Syntax and types The 8 types, typeof, numbers and BigInt, strings, null vs undefined, symbols, let/const/var, hoisting and the TDZ, ASI, strict mode
3 Operators and control flow Operators, == vs === and coercion, truthy/falsy, ??/?./logical assignment, if/switch/loops, for..of vs for..in
4 Functions and scope Declarations, expressions, arrows, parameters, closures, lexical scope, the four this binding rules, call/apply/bind, recursion, IIFEs
5 Objects and prototypes Object literals, references, copying, descriptors, getters/setters, Object.freeze, the prototype chain, class, extends, private fields
6 Arrays and iteration Array methods (mutating vs not), map/filter/reduce, sort gotchas, destructuring, spread/rest, iterators, Map/Set, JSON
7 Async JavaScript Call stack, event loop, macrotasks vs microtasks, callbacks, promises, async/await, Promise.all and friends, fetch, timers
8 Modules and tooling ES modules, dynamic import(), top-level await, CommonJS and interop, <script type="module">, bundlers and transpilers
9 The DOM and the browser The DOM tree, selecting and building nodes, attributes vs properties, events and delegation, forms, fetch, storage, timers
10 Modern features and gotchas Floating-point and coercion traps, a bug catalog, modern conveniences, Intl, error-handling patterns
  • Results are shown as // => comments. Multi-line output uses a text block.
  • Examples are runnable as written unless the surrounding context is obvious.
  • Where behavior depends on the environment (browser vs Node), that is called out.
  • ECMAScript is the spec; JavaScript is the language; the DOM, fetch, and fs are host APIs, not part of the language.
  • The spec ships yearly (ES2015 onward); ES2015 introduced most of what “modern JavaScript” means.
  • Run code in the browser console, with node file.js, or in the node REPL.
  • The language is dynamically typed, weakly typed, prototype-based, function-first, and single-threaded with an event loop.
  • Primitives copy by value; objects are handled by reference. Scope is lexical; this is not.