The sections follow a rough order of use: strings, patterns over strings, arrays, the JSON format that carries them between programs, and the two collections and two date APIs that fix older problems. The last sections cover input and output as they used to be done, then eval. The rule from earlier pages still applies. The Prof's law of weird stuff says to focus on what is odd compared with the languages you know, so that is what these sections concentrate on.
1. Four groups of objects
Objects in client-side JavaScript fall into four groups. Knowing which group an object belongs to tells you who defined it, where its documentation lives and whether it will exist in another environment.
- Built-in objects, such as
String,DateandArray. The ECMAScript specification defines them, so they exist wherever JavaScript runs. - Browser and API objects, such as
windowandnavigator. Web standards define them, and the browser supplies them. - Document objects, such as
documentanddocument.forms. They are built from the page's HTML, so they differ from one page to the next. - User-defined objects, the ones your own code creates.
The line between groups 1 and 2 is easy to miss because everything arrives on the same global object. console, setTimeout, fetch and structuredClone feel like part of the language, and none of them is in the ECMAScript specification. The language you can't avoid draws this boundary between language and host. When a script fails in Node with document is not defined, you have crossed it. MDN keeps the two lists apart as well: Standard built-in objects and Web APIs. This page stays almost entirely in group 1. Groups 2 and 3 are the subject of the DOM and Events series, with two old exceptions in section 8.
Built-ins are ordinary objects. Their methods sit on prototypes, as the previous page showed, which is why every array can call map. They are written in a few styles, though. Date is a class-style object: you make an instance and call its methods. Math is a plain container of functions, and trying to make an instance of it throws.
Should we always use OOP?
Since the language is full of objects, the question comes up. The answer is yes-ish, but make sure it makes sense. At the very small scale, without reuse, the value of objects is suspect: a class wrapped around one function adds a name and nothing else. At scale there are true trade-offs. Every property read on a deep inheritance chain walks up that chain, so a lookup high on the chain costs more, and when performance is the goal the depth is one of the things to look at. And some problems are awkward in this form. The array methods in section 4 pass functions around instead of defining objects, and they read better for it.
This course's position: speed and awkwardness are the signals to watch, because each time they appear they point to the same mistake, "the one true way to write code" fallacy. The built-ins do not follow one way, and your code does not have to either.
2. Strings
Types and coercion covers what a string is, template strings, and how a primitive can have methods at all. The behavior to carry from there is that strings are immutable. Every string method returns a new string and leaves the original alone, and an assignment to one character does nothing.
The most common beginner bug with strings is the middle pair of lines: calling a method and ignoring its result. These are the methods you will use most. The String reference has the full list.
Three details in that list come up again. indexOf reports failure as -1, so test for -1 or use includes, because 0 is a successful result at the start of the string. Negative arguments to slice and at count from the end, where clean[-1] would look for a property named -1 and give undefined. And replace with a string argument changes only the first match, which surprises people who have used the same name in another language.
What length counts
A string's length counts UTF-16 code units, which is not the same as the characters a person sees. Any character outside the first 65,536 code points takes two units, and that includes most emoji. Spreading a string walks it by code point, which is closer. Letters built from a base character and a combining mark are still two code points. Intl.Segmenter counts what a reader would count. A character limit on a form field is one place where the difference shows.
3. Regular expressions
JavaScript implements regular expressions through the RegExp object. It was introduced in JavaScript 1.2 and JScript 3.0, and the 1999 third edition of ECMAScript standardized it. The syntax is very similar to Perl's, so what you know from Perl, Python or grep -E mostly carries over. String methods often use regexes, which is where you will meet them most.
A literal pattern goes between two slashes. Any string containing dog matches /dog/, including hot dog and doggy, because a pattern matches anywhere in the string unless you anchor it. You can also create the regex with the RegExp() constructor from a string.
Use a literal when you know the pattern as you write the code. Use the constructor when the pattern is built while the program runs. Its argument is a string, and backslash is also the escape character in strings, so every backslash has to be doubled. If part of the pattern comes from a person, use RegExp.escape() on that part, so that a typed . or * stays literal. It became Baseline in May 2025, so older browsers lack it.
The methods
To test a pattern, use the test() method. The exec() method runs the same match and gets more information, such as offset values. Regexes are also used in a number of String methods.
Pick the method by what you want back. test gives a boolean, search gives an offset or -1, exec, and match without the g flag, give an array with the match details or null (with g, match gives only the full matches), matchAll gives every match with its details, replace gives a new string, and split accepts a pattern as its separator. exec and match return null when nothing matches, so reading result[0] without checking is a common source of TypeError.
Reading a pattern
This table is enough to read most patterns. MDN's regular expression cheat sheet has the rest.
| Syntax | Meaning |
|---|---|
. | Any character except a line ending, unless the s flag is set |
\d \w \s | A digit, a word character, whitespace. Capitals mean the opposite. |
[abc] [^abc] | One character from the set, or one not in it |
* + ? {2,4} | Repeat the previous item: zero or more, one or more, zero or one, a set range |
^ $ | Start and end of the string, or of each line with the m flag |
a|b | Either side |
( ) (?<year> ) | A capture group, and one with a name |
\. | A backslash makes a special character literal |
i g m s | Flags: ignore case, every match, multiline anchors, . matches line endings |
u v y d | Flags: Unicode, Unicode with more set features, sticky, record match indices |
The playground uses the pattern from the diagram on a longer string. match without g stops at the first number and reports its groups and offset. replace with g changes every number. matchAll requires g and walks each match with its groups.
Three traps
A regex with the g flag has state. test and exec start from its lastIndex property and update it after each match, so the same regex on the same string can answer differently each time. This bites when a g regex is stored in a variable and used for checking. Leave off g when you only want a yes or no.
Repeats are greedy. * and + take as much as they can while the rest of the pattern can still match, so a pattern for one quoted phrase can swallow everything from the first quotation mark to the last. A ? after the repeat makes it take as little as it can.
Some patterns are slow. JavaScript's engines backtrack, and a pattern with nested repeats such as (a+)+$ can take exponential time on a string that almost matches. Script shares a thread with the page, so the tab stops responding while it runs. OWASP describes the attack as regular expression denial of service. Be careful running a complicated pattern against text you did not write.
Regexes also appear in markup. An <input>'s pattern attribute is a regex, and Forms covers its rules, including how it anchors. A regex suits a short, flat format such as a postal code. It is a poor tool for nested formats. Use the browser's own parsers for HTML and URLs, and type="email" rather than a homemade email pattern.
4. Array methods
Objects and arrays covers arrays as a data structure. Here is the short version, since the methods build on it. Typical access is by index, and indexes start at 0, as usual. Assigning past the end grows the array and leaves empty slots, because length is one more than the highest index. Arrays can store arbitrary content, including functions, and a stored function can be called in place. And arrays can be accessed by key as well, because an array is an object: typeof says so, and it explains why you can use a number as a string. The index is a property name, and the number is converted to a string to find it.
Since typeof cannot tell an array from any other object, use Array.isArray to ask.
map, filter and reduce
Most loops over an array do one of a few jobs: keep some items, transform each item, or combine them into one value. Three methods name those jobs. filter calls your function on each item and keeps the ones it returns a truthy value for. map calls it on each item and builds a new array of the results, always the same length as the original. reduce carries an accumulator through the array and returns its final value. None of them changes the array it was called on, so they can be chained.
The playground does the same with objects, which is the usual case. It keeps the items in stock, takes their names, then adds up their prices. The last line shows that cart still has all three items. Functions and closures covers the arrow function syntax used for the callbacks.
That last suggestion leads to one rule. Without a starting value, reduce throws a TypeError on an empty array, so give it the starting value.
Two more traps come from how callbacks work. An arrow function with braces needs an explicit return, and map will happily collect a list of undefined without one. And map passes three arguments, the item, the index and the array. A function with an optional second parameter will receive the index in it, which gives the classic parseInt result below.
Which methods change the array
This is the question to ask of every array method, because the answer is inconsistent and a wrong guess causes action at a distance through a shared reference. push, pop, shift, unshift, splice, sort and reverse change the array in place. map, filter, slice and concat return a new one. sort has a second trap of its own: by default it converts items to strings and compares those, so numbers come out in dictionary order.
The comparison function returns a negative number, zero or a positive number to say which item goes first. toSorted, toReversed, toSpliced and with are the versions that return a copy. They became Baseline in July 2023 and, as of January 2026, count as widely available.
The rest of the kit
A few more cover most remaining jobs, and the Array reference lists all of them. find returns the first matching item, and findIndex its position. some and every answer yes-or-no questions and stop as soon as they know. includes is the readable test for membership, and unlike indexOf it can find NaN. flat and flatMap remove a level of nesting. Array.from turns anything iterable or array-like, such as an HTMLCollection, into a true array that has all these methods. Object.groupBy sorts items into named lists.
These methods have not replaced the loops on Statements, variables and flow. A chain builds a new array at each step and cannot break, while a for/of can stop at the first answer. A long reduce is often harder to read than the loop it replaced. Use the form a reader will follow most easily, and return to the fallacy from section 1 if you catch yourself treating either as the only correct style.
5. JSON
JSON is a text format for data, based on a subset of JavaScript's literal syntax. It is what a program sends across a network or saves to a file, and the built-in JSON object converts in both directions. JSON.stringify turns a value into text, and JSON.parse turns text into a new value. A JSON document looks like an object literal, with stricter rules. Keys and strings need double quotes, trailing commas and comments are not allowed, and the only values are strings, numbers, booleans, null, arrays and objects.
Most things outside that list are changed or dropped on the way out, without any error. A BigInt is the exception and throws a TypeError. A property holding undefined or a function disappears. NaN and Infinity become null. A Date becomes a string, because it has a toJSON method, and it stays a string when parsed. A Map or Set becomes an empty object, so its contents are lost. The playground shows each of these, then the error from parsing text that looks like JavaScript but is not JSON.
JSON.parse throws on bad input, and text from a network or from storage can always be bad, so that call often belongs inside a try block, as Errors and async shows. A circular reference makes JSON.stringify throw a TypeError as well. The third argument to stringify sets the indentation, which is useful for logs. Both functions also accept a function as the second argument, to transform values as they pass through, such as turning date strings back into Date objects. Client-Side Communication puts all of this to work on actual responses.
A round trip through JSON was the traditional way to deep-copy an object, and it has all the losses above. The browser's structuredClone() copies dates, maps, sets and circular references intact, though it throws on functions. Remember it comes from group 2, not from the language.
6. Map and Set
For most of its life JavaScript used plain objects as dictionaries, and that has two problems. Keys can only be strings or symbols, so anything else is converted to a string. An object used as a key becomes [object Object], so all such objects collide. A plain object also inherits names like constructor and toString, so a lookup can find something you never put there. Map fixes both. It accepts any value as a key, it has only the keys you added, it reports its size, and it iterates in insertion order.
A plain object is still the right choice for a record whose fields you know, such as a user with a name and a role, and it is the form JSON understands. Reach for Map when the keys are data: they arrive while the program runs, they are not strings, or they are added and removed often.
Set holds each value once, so it is the short way to remove duplicates and a clear way to ask whether a value has been seen. Both collections compare objects by identity, and NaN matches NaN. Two objects with the same contents are two different keys, as reference comparison would predict. Since spread and for/of work on both, converting to an array is one step.
Those last two lines use the set methods, Baseline since June 2024. union, intersection, difference and a few others are listed in the Set reference. WeakMap and WeakSet are variants that hold their object keys weakly, so that an entry does not keep its key from being garbage collected. They suit data attached to a DOM element or another object that may go away, and Objects and arrays discusses what leaks and why.
7. Dates and the state of Temporal
Date
A Date is one number, the milliseconds since the start of 1970 in UTC, with methods that display it in the local time zone or in UTC. Its oddities have a known source. Brendan Eich, as quoted in Jason Williams's history of Temporal, called it "a straight port by Ken Smith" of Java's date code, made in 1995 when the instruction was to make the language look like Java. Java later replaced that design. JavaScript has kept it, for the usual reason that old pages have to keep working.
Here is what came along with it. Months count from 0, so January is 0 and December is 11, while days of the month count from 1. A Date is mutable, so a function that receives one can change the caller's copy. Out-of-range values roll over without any error, which means that adding a month to January 31 lands in March. The older getYear() returns the year minus 1900, and getFullYear() replaced it. Since a Date is an object, === compares identity, so compare the numbers from getTime().
Parsing has the worst trap. new Date('2026-11-05'), a date with no time, is read as midnight UTC. new Date('2026-11-05T00:00'), a date and a time with no zone, is read as midnight local time. In San Diego those are different days. Formats other than this ISO form depend on the browser, so a string like '11/05/2026' may parse differently or not at all. Pass ISO strings with an explicit zone, or pass numbers. For display, use Intl.DateTimeFormat rather than building strings by hand, as in the last lines above, since it handles language and time zone for you. The sample uses the UTC methods so that its output is the same everywhere. Change one to the local version and yours may differ.
Temporal, as of November 2026
Temporal is the replacement. It is a global namespace, like Math, holding separate types for separate ideas: Temporal.Instant for an exact moment, Temporal.ZonedDateTime for a moment in a named time zone, Temporal.PlainDate for a date with no time or zone, and Temporal.Duration for a length of time. Its objects are immutable, so add returns a new object. Months count from 1. Parsing is strict and does not guess.
The state of support, checked on 5 November 2026:
- The standard. TC39 advanced Temporal to Stage 4 in March 2026. The committee's list of finished proposals expects it in the 2027 edition of ECMAScript.
- Browsers. It shipped in Firefox 139 in May 2025 and in Chrome 144 and Edge 144 in January 2026. Safari has it only in Technology Preview, and MDN's compatibility data lists no version of Safari on iOS.
- Baseline. Web Platform Status and MDN both report it as limited availability, not Baseline, because of Safari.
- Other hosts. MDN's data lists Node 26 and Deno 2.7.
This list will go out of date, so check those two status pages before you depend on it. A page that has to work on iPhones today has two choices: keep using Date and Intl, or load a polyfill such as @js-temporal/polyfill and accept its weight. How much JavaScript? covers how to weigh that choice and how to read Baseline. Date will stay in the language in any case, so you will keep reading it.
The playground checks for Temporal before using it, the feature detection a page would do. Where it exists, the same step that overflowed to March above stops at the last day of February, the original object does not change, and the month reads as a normal number. Where it does not exist, the playground says so.
8. Input and output, the old way
Two older features are in group 2 and group 3, and they belong on this page because many older tutorials and scripts use them. You will read them more often than you write them.
Special dialog forms
The browser has three special dialog forms: alerts, confirms and prompts.
alert returns nothing. confirm returns true or false. prompt returns a string, an empty string if the person pressed OK without typing, or null if they cancelled. Handle the null. Handle the type as well: a typed 5 arrives as the string '5', and the + trap from Types and coercion is waiting.
What sets these apart is that they stop the program. The call does not return until the person responds, and nothing else on the page runs in the meantime, because script shares one thread with the page. The event loop module explains why. That makes them easy to code, with the answer on the next line and no callback. It also makes them poor interface. You cannot style them. Their wording and buttons belong to the browser. MDN notes that a browser may decline to show one or to wait, for example when the person has switched tabs.
For a message, write to the page. For a decision, use the <dialog> element, which you can style and fill with an actual form. The difference for your code is that showModal() returns at once. The page is blocked for the person, but script keeps running, and the answer arrives later through an event.
For debugging, console.log replaced alert long ago. It does not stop the page, and it shows objects in a form you can inspect, where alert shows you [object Object].
document.write
document.write() and document.writeln() write to the HTML document. Writing should be done before or as the document loads, since the text goes into the stream the parser is reading, at the point right after the script. Since you are writing into HTML, you may write out tags, and you have to consider HTML's whitespace rules. writeln adds a line ending, which collapses to a single space on the page unless you are inside something like <pre>.
In traditional JavaScript the document was static after that. With the DOM, everything is rewritable, and you do it with DOM methods rather than document.write. Calling document.write after the page has loaded no longer has a stream to add to, so it first opens a new one, and that clears the page.
Do not use it in new code. MDN marks it deprecated and quotes the HTML specification's own warning, that the method has "very idiosyncratic behavior" and that its exact behavior can depend on network latency. Depending on when it is called, it may add text, clear the page, be ignored or throw. It throws in an XML document, so a page served as true XHTML cannot use it at all. The chance that a script might call it is one reason classic scripts block the parser, as Browser module 3 describes. It also parses a string as HTML, with the injection hazard DOM step 5 describes for innerHTML.
DOM step 6 covers creating elements in full.
9. Eval() is evil?
eval gives you direct access to the JavaScript interpreter. You hand it a string, and it parses and runs that string as code, in the current scope.
Consider what that means alongside toString() on various objects and functions. Calling toString on a function gives you its source text, so a function can be turned into a string, stored or sent somewhere, and turned back. That is serialization of code. The revived copy keeps only its text. Any variables it reached through a closure are gone, so this works only for functions that use nothing but their parameters and globals.
The title has a question mark because the construct is not the problem. It's the use. The danger is a string that came from somewhere you do not control. Code run through eval has all of your page's privileges, so if any part of that string can come from a URL, a form field or a server response, someone else can write code that runs as your page. That is cross-site scripting. MDN's page on eval lists the other costs as well. It is slower than the alternatives, it forces expensive variable lookups, and minifiers give up on any scope it can see.
Be aware that other constructs do the same thing. setTimeout("code", 0) and setInterval with a string are eval by another name, and so is new Function('a', 'return a'), though the last runs in global scope and cannot touch your local variables. Most uses of all of them have a plain replacement, shown below. A few tools have a legitimate need to run text as code, such as a developer's console, because running your text is their job.
That last pair explains much of eval's history. JSON looks like JavaScript, so older code parsed it with eval, and any response could run code in the page. JSON.parse accepts only data.
"use strict"
A script or function that begins with the string "use strict"; runs in strict mode, a stricter version of the language with several old mistakes removed. Look into it, since it keeps you away from many dangerous constructs. It is often assumed to ban eval, and it does not. Strict mode narrows what eval can do: code in the string gets its own variable scope, so it can no longer add a name to yours.
To forbid eval, use the two tools built for that. A linter rule such as ESLint's no-eval catches it in your own code as you write. A Content-Security-Policy header with a script-src and without unsafe-eval makes the browser refuse eval, string timers and Function at run time, including in any library you load. The film Everything You Embed shows that header doing its job.
The day-to-day benefit of strict mode is that mistakes turn into errors. The largest one is assigning to a name that was never declared. In ordinary scripts that creates a global variable, so a typo in a name runs quietly. In strict mode it throws.
The same applies to other silent failures. Assigning to a read-only or frozen property throws a TypeError where it would have been ignored, as with the frozen array on Statements, variables and flow, and so does setting a property on a primitive. this in a plain function call is undefined rather than the global object, as Objects and arrays explains. The with statement, duplicate parameter names and old octal literals are syntax errors.
The directive must be the first statement of its script or function to count, and if it is anywhere else it is just a string that does nothing. Often you will not need to write it. Code in an ES module and everything in a class body are strict automatically. In a classic script, write it at the top. The samples in these pages run as classic scripts without it unless they say otherwise.
10. Summary
| Concept | Key points |
|---|---|
| Four groups | Built-in objects from the language, browser and API objects and document objects from the host, and your own. Knowing the group tells you where a name comes from and where it will exist. |
| OOP | Yes-ish, if it makes sense. Suspect at the very small scale, with costs from deep inheritance at the large. Beware "the one true way to write code". |
| Strings | Immutable, so methods return new strings. length counts UTF-16 code units. indexOf reports failure as -1. |
| Regular expressions | Literal or RegExp(). test, exec, and the string methods search, match, matchAll, replace and split. A g regex has state, repeats are greedy, and some patterns are very slow. |
| Array methods | filter, map and reduce return new values and can chain. sort changes the array and compares strings by default. toSorted copies. |
| JSON | stringify and parse. Functions and undefined are dropped, NaN becomes null, dates become strings, and Map and Set come out empty. parse throws on bad text. |
| Map and Set | Map takes any key and inherits none. Set holds each value once. Both compare objects by identity. Use a plain object for a record with known fields. |
| Date | A port of an old Java class. Months from 0, mutable, rolls over without error, and parsing depends on the string's form. Use ISO strings and Intl. |
| Temporal | Stage 4 since March 2026. In Firefox, Chrome and Edge, but in Safari only in Technology Preview, so not Baseline as of November 2026. Check before relying on it. |
| Dialogs | alert, confirm and prompt stop the page and cannot be styled. prompt can return null. Use <dialog> for interface. |
| document.write | Writes into the stream while parsing and clears the page after loading. Deprecated. Read it, and use DOM methods. |
| eval | Runs a string as code with your page's privileges, as do string timers and Function. The danger is the source of the string. JSON.parse and bracket access replace most uses. CSP can forbid it. |
| Strict mode | Narrows eval without banning it, turns silent failures into errors, and is automatic in modules and classes. |
