1. Interaction
  2. JavaScript
  3. Built-ins

Interaction · JavaScript 08

Built-ins

A JavaScript program starts with a large library already loaded. This page covers the parts you will use every week, the behavior in them that catches people, and a few older features you need to recognize when you read other people's code. It gives the behavior and the judgment. MDN has the complete tables, and each section links to them.

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.

  1. Built-in objects, such as String, Date and Array. The ECMAScript specification defines them, so they exist wherever JavaScript runs.
  2. Browser and API objects, such as window and navigator. Web standards define them, and the browser supplies them.
  3. Document objects, such as document and document.forms. They are built from the page's HTML, so they differ from one page to the next.
  4. User-defined objects, the ones your own code creates.
The four groups of objects, split between the language and the hostTwo columns. Under The language are built-in objects, such as String, Date and Array, defined by ECMAScript, and user-defined objects, written by you. Under The host, here a browser, are browser and API objects, such as window, navigator and console, defined by web standards, and document objects, such as document and document.forms, built from this page's HTML.The languageThe host: a browser1. Built-in objectsString Date Array Mathdefined by ECMAScript, in every host4. User-defined objects{ name: 'Ada' } new Cart()written by you, in the language2. Browser and API objectswindow navigator consoledefined by web standards3. Document objectsdocument document.formsbuilt from this page's HTML
Groups 1 and 4 belong to the language and move with your code to another host. Groups 2 and 3 come from the browser.
// 1. Built-in: part of the language let today = new Date(); // 2. Browser and API: supplied by the host let language = navigator.language; // 3. Document: this page, parsed into objects let formCount = document.forms.length; // 4. User-defined: yours let user = { name: 'Ada', joined: today };

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.

let stamp = new Date(0); console.log(stamp.getUTCFullYear()); // logs: 1970 console.log(Math.max(3, 9)); // logs: 9 new Math(); // throws: TypeError

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.

let word = 'hello'; word[0] = 'J'; // ignored here, a TypeError in strict mode console.log(word); // logs: hello console.log(word.toUpperCase()); // logs: HELLO console.log(word); // logs: hello word = word.toUpperCase(); console.log(word); // logs: HELLO

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.

let line = ' Ada Lovelace, ada@example.com '; let clean = line.trim(); console.log(clean.includes('@')); // logs: true console.log(clean.startsWith('Ada')); // logs: true console.log(clean.indexOf(',')); // logs: 12 console.log(clean.indexOf('#')); // logs: -1 console.log(clean.slice(0, 3)); // logs: Ada console.log(clean.slice(-3)); // logs: com console.log(clean.at(-1)); // logs: m console.log(clean.split(', ').join(' | ')); // logs: Ada Lovelace | ada@example.com console.log('7'.padStart(3, '0')); // logs: 007 console.log('a-b-c'.replace('-', '+')); // logs: a+b-c console.log('a-b-c'.replaceAll('-', '+')); // logs: a+b+c

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.

let face = '\u{1F600}'; // one emoji console.log(face.length); // logs: 2 console.log([...face].length); // logs: 1 let accented = 'é'; // e plus a combining acute accent console.log(accented.length); // logs: 2 console.log([...accented].length); // logs: 2 let parts = new Intl.Segmenter('en').segment(accented); console.log([...parts].length); // logs: 1

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.

let pattern = /dog/; console.log(pattern.test('hot dog')); // logs: true console.log(pattern.test('doggy')); // logs: true console.log(pattern.test('cat')); // logs: false let same = new RegExp('dog'); console.log(same.test('hot dog')); // logs: true

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.

let digits = new RegExp('\\d+'); // the same pattern as /\d+/ console.log(digits.test('room 12')); // logs: true let term = 'dog'; let finder = new RegExp(term, 'i'); // the second argument holds the flags console.log(finder.test('Hot DOG')); // logs: true

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.

// To test a pattern, use the test() method let pattern = new RegExp('dog'); console.log(pattern.toString() + ' test dog = ' + pattern.test('dog')); // logs: /dog/ test dog = true // The exec() method tests a pattern and gets more information let myPattern = /https:.*/; let result = myPattern.exec('Visit https://www.pint.com'); console.log(result[0]); // logs: https://www.pint.com console.log(result.index); // logs: 6 // Also used in a number of String methods console.log('JavaScript regular expressions are powerful!'.search(/pow.*/i)); // logs: 35 let s = 'Hello. Regexps are fun.'; s = s.replace(/\./, '!'); // replace first . with a ! console.log(s); // logs: Hello! Regexps are fun.

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

The parts of a regular expression literalThe literal slash, open group, backslash d, three in braces, close group, hyphen, a second group with four digits, slash, g is split into boxes. The slashes are delimiters. The first group matches three digits, the hyphen matches itself, the second group matches four digits, and the flag g asks for every match. Run on the text Call 555-0199, the match at index 0 is 555-0199, group 1 is 555, and group 2 is 0199./(\d{3})-(\d{4})/gdelimitergroup 1: three digitsa literal hyphengroup 2: four digitsdelimiterflag:every matchexec on the text:Call 555-0199the whole match, index 0'555-0199'group 1, index 1'555'group 2, index 2'0199'
Parentheses capture. Each group's text gets its own slot in the result array, after the whole match.

This table is enough to read most patterns. MDN's regular expression cheat sheet has the rest.

SyntaxMeaning
.Any character except a line ending, unless the s flag is set
\d \w \sA 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|bEither side
( ) (?<year> )A capture group, and one with a name
\.A backslash makes a special character literal
i g m sFlags: ignore case, every match, multiline anchors, . matches line endings
u v y dFlags: 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.

let word = /dog/g; console.log(word.test('dog')); // logs: true console.log(word.lastIndex); // logs: 3 console.log(word.test('dog')); // logs: false console.log(word.test('dog')); // logs: true

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.

let said = 'She said "yes" and "no"'; console.log(said.match(/".*"/)[0]); // logs: "yes" and "no" console.log(said.match(/".*?"/)[0]); // logs: "yes"

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.

let myArray = []; // Typical array access by index myArray[3] = 'Hello'; // Get the array length console.log(myArray.length); // logs: 4 // Arrays can be accessed via key as well myArray['3'] = 'Goodbye'; console.log(myArray[3]); // logs: Goodbye // Arrays can store arbitrary content myArray[3] = function () { return 'called'; }; // Thus, this is legit console.log(myArray[3]()); // logs: called // 'object', which explains why you can use a number as a string console.log(typeof myArray); // logs: object console.log(Array.isArray(myArray)); // logs: true

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.

An array passing through filter, map and reduceThe array 3, 8, 12, 5 goes through filter keeping values above 4, giving 8, 12, 5. That goes through map doubling each value, giving 16, 24, 10. That goes through reduce adding them up from 0, giving 50. A note says each step returns a new value and the original array is unchanged.[3, 8, 12, 5].filter((n) => n > 4)[8, 12, 5].map((n) => n * 2)[16, 24, 10].reduce((sum, n) => sum + n, 0)50Each step returns a new value.The original array is unchanged.
Each method returns a new value, so the next call in the chain works on that result.

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.

let doubled = [1, 2].map((n) => { n * 2; }); console.log(doubled[0]); // logs: undefined console.log(['1', '2', '3'].map(parseInt).join()); // logs: 1,NaN,NaN console.log(['1', '2', '3'].map(Number).join()); // logs: 1,2,3

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.

let scores = [10, 9, 1]; console.log(scores.sort().join()); // logs: 1,10,9 console.log(scores.sort((a, b) => a - b).join()); // logs: 1,9,10 let original = [3, 1, 2]; let copy = original.toSorted((a, b) => a - b); console.log(copy.join()); // logs: 1,2,3 console.log(original.join()); // logs: 3,1,2

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.

let ages = [12, 19, 34]; console.log(ages.find((age) => age >= 18)); // logs: 19 console.log(ages.some((age) => age < 13)); // logs: true console.log(ages.every((age) => age < 13)); // logs: false console.log([NaN].includes(NaN)); // logs: true console.log([NaN].indexOf(NaN)); // logs: -1 console.log([[1, 2], [3]].flat().join()); // logs: 1,2,3 let groups = Object.groupBy(ages, (age) => (age >= 18 ? 'adult' : 'minor')); console.log(JSON.stringify(groups)); // logs: {"minor":[12],"adult":[19,34]}

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.

let first = { id: 1 }; let second = { id: 2 }; let plain = {}; plain[first] = 'one'; plain[second] = 'two'; console.log(Object.keys(plain).join()); // logs: [object Object] console.log(plain[first]); // logs: two console.log(plain.constructor === Object); // logs: true let map = new Map(); map.set(first, 'one'); map.set(second, 'two'); console.log(map.size); // logs: 2 console.log(map.get(first)); // logs: one console.log(map.get('constructor')); // logs: undefined for (let [key, value] of map) { console.log(key.id, value); // logs: 1 one // logs: 2 two }

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.

let tags = new Set(['js', 'css', 'js']); console.log(tags.size); // logs: 2 console.log(tags.has('css')); // logs: true console.log([...tags].join()); // logs: js,css console.log([...new Set([3, 3, 1])].join()); // logs: 3,1 console.log(new Set([{}, {}]).size); // logs: 2 let front = new Set(['html', 'css', 'js']); let back = new Set(['js', 'sql']); console.log([...front.intersection(back)].join()); // logs: js console.log([...front.union(back)].join()); // logs: html,css,js,sql

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().

let start = new Date(Date.UTC(2026, 0, 31)); // 0 is January console.log(start.toISOString()); // logs: 2026-01-31T00:00:00.000Z start.setUTCMonth(1); // February 31 does not exist console.log(start.toISOString()); // logs: 2026-03-03T00:00:00.000Z let same = new Date(start.getTime()); console.log(same === start); // logs: false console.log(same.getTime() === start.getTime()); // logs: true let format = new Intl.DateTimeFormat('en-US', { dateStyle: 'long', timeZone: 'UTC' }); console.log(format.format(start)); // logs: March 3, 2026

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.

// Special dialog form for Alerts alert('Hey there JavaScript coder!'); // Special dialog form for Confirms if (confirm('Do you like cheese?')) { alert('Cheese lover'); } else { alert('Cheese hater'); } // Special dialog form for Prompts let theirName = prompt('What\'s your name?', '');

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.

let ask = document.querySelector('#ask'); ask.addEventListener('close', () => { console.log('They chose', ask.returnValue); }); ask.showModal(); console.log('This line runs while the dialog is still open');

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>.

<p>Before</p> <script> document.write('<p>Written <em>as the page loads</em></p>'); </script> <p>After</p>

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.

document.write during parsing and after loadingOn the left, during parsing, the parser reads a paragraph, then a script that calls write, then another paragraph. The written text joins the stream. On the right, after the document has loaded, a call to document.write first runs document.open, which clears the page, so only the written text remains.While the parser is reading<p>Before</p>document.write('hi');<p>After</p>The text joins the streamAfter the document has loadeddocument.write('hi');runs document.open() firsthiThe old page is gone
The same call adds to a page as it loads and wipes it out afterward.

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.

let note = document.createElement('p'); note.textContent = 'Added at any time'; document.body.append(note);

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.

let aString = "console.log('hi')"; eval(aString); // logs: hi

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.

function add(a, b) { return a + b; } console.log(add.toString()); // logs: function add(a, b) { return a + b; } console.log([1, 2].toString()); // logs: 1,2 console.log({}.toString()); // logs: [object Object] console.log(/dog/.toString()); // logs: /dog/ let saved = add.toString(); let revived = eval('(' + saved + ')'); console.log(revived(2, 3)); // logs: 5

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.

let formula = new URLSearchParams(location.search).get('formula'); eval(formula);

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.

setTimeout("save()", 0); // avoid setTimeout(save, 0); // pass the function let value = eval('settings.' + name); // avoid let safer = settings[name]; // bracket access let data = eval('(' + text + ')'); // avoid: old JSON code let parsed = JSON.parse(text); // data stays data

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.

eval('function helper() { return 1; }'); console.log(typeof helper); // logs: function 'use strict'; eval('function helper() { return 1; }'); console.log(typeof helper); // logs: undefined

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.

let count = 1; cuont = 2; console.log(count, cuont); // logs: 1 2 'use strict'; let count = 1; cuont = 2; // throws: ReferenceError

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

ConceptKey points
Four groupsBuilt-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.
OOPYes-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".
StringsImmutable, so methods return new strings. length counts UTF-16 code units. indexOf reports failure as -1.
Regular expressionsLiteral 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 methodsfilter, map and reduce return new values and can chain. sort changes the array and compares strings by default. toSorted copies.
JSONstringify 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 SetMap 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.
DateA 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.
TemporalStage 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.
Dialogsalert, confirm and prompt stop the page and cannot be styled. prompt can return null. Use <dialog> for interface.
document.writeWrites into the stream while parsing and clears the page after loading. Deprecated. Read it, and use DOM methods.
evalRuns 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 modeNarrows eval without banning it, turns silent failures into errors, and is automatic in modules and classes.