ch15.md 395.0 KB
Newer Older
G
gdut-yy 已提交
1 2 3 4 5
# Chapter 15. JavaScript in Web Browsers
The JavaScript language was created in 1994 with the express purpose of enabling dynamic behavior in the documents displayed by web browsers. The language has evolved significantly since then, and at the same time, the scope and capabilities of the web platform have grown explosively. Today, JavaScript programmers can think of the web as a full-featured platform for application development. Web browsers specialize in the display of formatted text and images, but, like native operating systems, browsers also provide other services, including graphics, video, audio, networking, storage, and threading. JavaScript is the language that enables web applications to use the services provided by the web platform, and this chapter demonstrates how you can use the most important of these services.

The chapter begins with the web platform’s programming model, explaining how scripts are embedded within HTML pages (§15.1) and how JavaScript code is triggered asynchronously by events (§15.2). The sections that follow this introductory material document the core JavaScript APIs that enable your web applications to:

G
gdut-yy 已提交
6 7 8 9 10 11 12 13 14
- Control document content (§15.3) and style (§15.4)
- Determine the on-screen position of document elements (§15.5)
- Create reusable user interface components (§15.6)
- Draw graphics (§15.7 and §15.8)
- Play and generate sounds (§15.9)
- Manage browser navigation and history (§15.10)
- Exchange data over the network (§15.11)
- Store data on the user’s computer (§15.12)
- Perform concurrent computation with threads (§15.13)
G
gdut-yy 已提交
15 16 17 18 19 20 21 22 23 24 25 26 27

CLIENT-SIDE JAVASCRIPT
In this book, and on the web, you’ll see the term “client-side JavaScript.” The term is simply a synonym for JavaScript written to run in a web browser, and it stands in contrast to “server-side” code, which runs in web servers.

The two “sides” refer to the two ends of the network connection that separate the web server and the web browser, and software development for the web typically requires code to be written on both “sides.” Client-side and server-side are also often called “frontend” and “backend.”

Previous editions of this book attempted to comprehensively cover all JavaScript APIs defined by web browsers, and as a result, this book was too long a decade ago. The number and complexity of web APIs has continued to grow, and I no longer think it makes sense to attempt to cover them all in one book. As of the seventh edition, my goal is to cover the JavaScript language definitively and to provide an in-depth introduction to using the language with Node and with web browsers. This chapter cannot cover all the web APIs, but it introduces the most important ones in enough detail that you can start using them right away. And, having learned about the core APIs covered here, you should be able to pick up new APIs (like those summarized in §15.15) when and if you need them.

Node has a single implementation and a single authoritative source for documentation. Web APIs, by contrast, are defined by consensus among the major web browser vendors, and the authoritative documentation takes the form of a specification intended for the C++ programmers who implement the API, not for the JavaScript programmers who will use it. Fortunately, Mozilla’s “MDN web docs” project is a reliable and comprehensive source1 for web API documentation.

LEGACY APIS
In the 25 years since JavaScript was first released, browser vendors have been adding features and APIs for programmers to use. Many of those APIs are now obsolete. They include:

G
gdut-yy 已提交
28 29 30 31
- Proprietary APIs that were never standardized and/or never implemented by other browser vendors. Microsoft’s Internet Explorer defined a lot of these APIs. Some (like the innerHTML property) proved useful and were eventually standardized. Others (like the attachEvent() method) have been obsolete for years.
- Inefficient APIs (like the document.write() method) that have such a severe performance impact that their use is no longer considered acceptable.
- Outdated APIs that have long since been replaced by new APIs for achieving the same thing. An example is document.bgColor, which was defined to allow JavaScript to set the background color of a document. With the advent of CSS, document.bgColor became a quaint special case with no real purpose.
- Poorly designed APIs that have been replaced by better ones. In the early days of the web, standards committees defined the key Document Object Model API in a language-agnostic way so that the same API could be used in Java programs to work with XML documents on and in JavaScript programs to work with HTML documents. This resulted in an API that was not well suited to the JavaScript language and that had features that web programmers didn’t particularly care about. It took decades to recover from those early design mistakes, but today’s web browsers support a much-improved Document Object Model.
G
gdut-yy 已提交
32 33 34

Browser vendors may need to support these legacy APIs for the foreseeable future in order to ensure backward compatibility, but there is no longer any need for this book to document them or for you to learn about them. The web platform has matured and stabilized, and if you are a seasoned web developer who remembers the fourth or fifth edition of this book, then you may have as much outdated knowledge to forget as you have new material to learn.

G
gdut-yy 已提交
35
## 15.1 Web Programming Basics
G
gdut-yy 已提交
36 37
This section explains how JavaScript programs for the web are structured, how they are loaded into a web browser, how they obtain input, how they produce output, and how they run asynchronously by responding to events.

G
gdut-yy 已提交
38 39
### 15.1.1 JavaScript in HTML `<script>` Tags
Web browsers display HTML documents. If you want a web browser to execute JavaScript code, you must include (or reference) that code from an HTML document, and this is what the HTML `<script>` tag does.
G
gdut-yy 已提交
40

G
gdut-yy 已提交
41 42
JavaScript code can appear inline within an HTML file between `<script>` and `</script>` tags. Here, for example, is an HTML file that includes a script tag with JavaScript code that dynamically updates one element of the document to make it behave like a digital clock:
```html
G
gdut-yy 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
<!DOCTYPE html>                 <!-- This is an HTML5 file -->
<html>                          <!-- The root element -->
<head>                          <!-- Title, scripts & styles can go here -->
<title>Digital Clock</title>
<style>                         /* A CSS stylesheet for the clock */
#clock {                        /* Styles apply to element with id="clock" */
  font: bold 24px sans-serif;   /* Use a big bold font */
  background: #ddf;             /* on a light bluish-gray background. */
  padding: 15px;                /* Surround it with some space */
  border: solid black 2px;      /* and a solid black border */
  border-radius: 10px;          /* with rounded corners. */
}
</style>
</head>
<body>                    <!-- The body holds the content of the document. -->
<h1>Digital Clock</h1>    <!-- Display a title. -->
<span id="clock"></span>  <!-- We will insert the time into this element. -->
<script>
// Define a function to display the current time
function displayTime() {
    let clock = document.querySelector("#clock"); // Get element with id="clock"
    let now = new Date();                         // Get current time
    clock.textContent = now.toLocaleTimeString(); // Display time in the clock
}
displayTime()                    // Display the time right away
setInterval(displayTime, 1000);  // And then update it every second.
</script>
</body>
</html>
G
gdut-yy 已提交
72 73 74
```
Although JavaScript code can be embedded directly within a `<script>` tag, it is more common to instead use the src attribute of the `<script>` tag to specify the URL (an absolute URL or a URL relative to the URL of the HTML file being displayed) of a file containing JavaScript code. If we took the JavaScript code out of this HTML file and stored it in its own scripts/digital_clock.js file, then the `<script>` tag might reference that file of code like this:
```html
G
gdut-yy 已提交
75
<script src="scripts/digital_clock.js"></script>
G
gdut-yy 已提交
76 77
```
A JavaScript file contains pure JavaScript, without `<script>` tags or any other HTML. By convention, files of JavaScript code have names that end with .js.
G
gdut-yy 已提交
78

G
gdut-yy 已提交
79
A `<script>` tag with the a src attribute behaves exactly as if the contents of the specified JavaScript file appeared directly between the `<script>` and `</script>` tags. Note that the closing `</script>` tag is required in HTML documents even when the src attribute is specified: HTML does not support a `<script/>` tag.
G
gdut-yy 已提交
80 81 82

There are a number of advantages to using the src attribute:

G
gdut-yy 已提交
83 84 85 86
- It simplifies your HTML files by allowing you to remove large blocks of JavaScript code from them—that is, it helps keep content and behavior separate.
- When multiple web pages share the same JavaScript code, using the src attribute allows you to maintain only a single copy of that code, rather than having to edit each HTML file when the code changes.
- If a file of JavaScript code is shared by more than one page, it only needs to be downloaded once, by the first page that uses it—subsequent pages can retrieve it from the browser cache.
- Because the src attribute takes an arbitrary URL as its value, a JavaScript program or web page from one web server can employ code exported by other web servers. Much internet advertising relies on this fact.
G
gdut-yy 已提交
87 88

MODULES
G
gdut-yy 已提交
89
§10.3 documents JavaScript modules and covers their import and export directives. If you have written your JavaScript program using modules (and have not used a code-bundling tool to combine all your modules into a single nonmodular file of JavaScript), then you must load the top-level module of your program with a `<script>` tag that has a type="module" attribute. If you do this, then the module you specify will be loaded, and all of the modules it imports will be loaded, and (recursively) all of the modules they import will be loaded. See §10.3.5 for complete details.
G
gdut-yy 已提交
90 91

SPECIFYING SCRIPT TYPE
G
gdut-yy 已提交
92
In the early days of the web, it was thought that browsers might some day implement languages other than JavaScript, and programmers added attributes like language="javascript" and type="application/javascript" to their `<script>` tags. This is completely unnecessary. JavaScript is the default (and only) language of the web. The language attribute is deprecated, and there are only two reasons to use a type attribute on a `<script>` tag:
G
gdut-yy 已提交
93

G
gdut-yy 已提交
94 95
- To specify that the script is a module
- To embed data into a web page without displaying it (see §15.3.4)
G
gdut-yy 已提交
96 97 98 99

WHEN SCRIPTS RUN: ASYNC AND DEFERRED
When JavaScript was first added to web browsers, there was no API for traversing and manipulating the structure and content of an already rendered document. The only way that JavaScript code could affect the content of a document was to generate that content on the fly while the document was in the process of loading. It did this by using the document.write() method to inject HTML text into the document at the location of the script.

G
gdut-yy 已提交
100
The use of document.write() is no longer considered good style, but the fact that it is possible means that when the HTML parser encounters a `<script>` element, it must, by default, run the script just to be sure that it doesn’t output any HTML before it can resume parsing and rendering the document. This can dramatically slow down parsing and rendering of the web page.
G
gdut-yy 已提交
101

G
gdut-yy 已提交
102 103
Fortunately, this default synchronous or blocking script execution mode is not the only option. The `<script>` tag can have defer and async attributes, which cause scripts to be executed differently. These are boolean attributes—they don’t have a value; they just need to be present on the `<script>` tag. Note that these attributes are only meaningful when used in conjunction with the src attribute:
```html
G
gdut-yy 已提交
104 105
<script defer src="deferred.js"></script>
<script async src="async.js"></script>
G
gdut-yy 已提交
106 107
```
Both the defer and async attributes are ways of telling the browser that the linked script does not use document.write() to generate HTML output, and that the browser, therefore, can continue to parse and render the document while downloading the script. The defer attribute causes the browser to defer execution of the script until after the document has been fully loaded and parsed and is ready to be manipulated. The async attribute causes the browser to run the script as soon as possible but does not block document parsing while the script is being downloaded. If a `<script>` tag has both attributes, the async attribute takes precedence.
G
gdut-yy 已提交
108 109 110 111 112 113 114 115 116 117

Note that deferred scripts run in the order in which they appear in the document. Async scripts run as they load, which means that they may execute out of order.

Scripts with the type="module" attribute are, by default, executed after the document has loaded, as if they had a defer attribute. You can override this default with the async attribute, which will cause the code to be executed as soon as the module and all of its dependencies have loaded.

A simple alternative to the async and defer attributes—especially for code that is included directly in the HTML—is to simply put your scripts at the end of the HTML file. That way, the script can run knowing that the document content before it has been parsed and is ready to be manipulated.

LOADING SCRIPTS ON DEMAND
Sometimes, you may have JavaScript code that is not used when a document first loads and is only needed if the user takes some action like clicking on a button or opening a menu. If you are developing your code using modules, you can load a module on demand with import(), as described in §10.3.6.

G
gdut-yy 已提交
118 119
If you are not using modules, you can load a file of JavaScript on demand simply by adding a `<script>` tag to your document when you want the script to load:
```js
G
gdut-yy 已提交
120 121 122 123 124 125 126 127 128 129 130
// Asynchronously load and execute a script from a specified URL
// Returns a Promise that resolves when the script has loaded.
function importScript(url) {
    return new Promise((resolve, reject) => {
        let s = document.createElement("script"); // Create a <script> element
        s.onload = () => { resolve(); };          // Resolve promise when loaded
        s.onerror = (e) => { reject(e); };        // Reject on failure
        s.src = url;                              // Set the script URL
        document.head.append(s);                  // Add <script> to document
    });
}
G
gdut-yy 已提交
131 132
```
This importScript() function uses DOM APIs (§15.3) to create a new `<script>` tag and add it to the document `<head>`. And it uses event handlers (§15.2) to determine when the script has loaded successfully or when loading has failed.
G
gdut-yy 已提交
133

G
gdut-yy 已提交
134
### 15.1.2 The Document Object Model
G
gdut-yy 已提交
135 136 137
One of the most important objects in client-side JavaScript programming is the Document object—which represents the HTML document that is displayed in a browser window or tab. The API for working with HTML documents is known as the Document Object Model, or DOM, and it is covered in detail in §15.3. But the DOM is so central to client-side JavaScript programming that it deserves to be introduced here.

HTML documents contain HTML elements nested within one another, forming a tree. Consider the following simple HTML document:
G
gdut-yy 已提交
138
```html
G
gdut-yy 已提交
139 140 141 142 143 144 145 146 147
<html>
  <head>
    <title>Sample Document</title>
  </head>
  <body>
    <h1>An HTML Document</h1>
    <p>This is a <i>simple</i> document.
  </body>
</html>
G
gdut-yy 已提交
148 149
```
The top-level `<html>` tag contains `<head>` and `<body>` tags. The `<head>` tag contains a `<title>` tag. And the `<body>` tag contains `<h1>` and `<p>` tags. The `<title>` and `<h1>` tags contain strings of text, and the `<p>` tag contains two strings of text with an `<i>` tag between them.
G
gdut-yy 已提交
150 151 152

The DOM API mirrors the tree structure of an HTML document. For each HTML tag in the document, there is a corresponding JavaScript Element object, and for each run of text in the document, there is a corresponding Text object. The Element and Text classes, as well as the Document class itself, are all subclasses of the more general Node class, and Node objects are organized into a tree structure that JavaScript can query and traverse using the DOM API. The DOM representation of this document is the tree pictured in Figure 15-1.

G
gdut-yy 已提交
153 154
<Figures figure="15-1">The tree representation of an HTML document</Figures>

G
gdut-yy 已提交
155 156 157 158
If you are not already familiar with tree structures in computer programming, it is helpful to know that they borrow terminology from family trees. The node directly above a node is the parent of that node. The nodes one level directly below another node are the children of that node. Nodes at the same level, and with the same parent, are siblings. The set of nodes any number of levels below another node are the descendants of that node. And the parent, grandparent, and all other nodes above a node are the ancestors of that node.

The DOM API includes methods for creating new Element and Text nodes, and for inserting them into the document as children of other Element objects. There are also methods for moving elements within the document and for removing them entirely. While a server-side application might produce plain-text output by writing strings with console.log(), a client-side JavaScript application can produce formatted HTML output by building or manipulating the document tree document using the DOM API.

G
gdut-yy 已提交
159
There is a JavaScript class corresponding to each HTML tag type, and each occurrence of the tag in a document is represented by an instance of the class. The `<body>` tag, for example, is represented by an instance of HTMLBodyElement, and a `<table>` tag is represented by an instance of HTMLTableElement. The JavaScript element objects have properties that correspond to the HTML attributes of the tags. For example, instances of HTMLImageElement, which represent `<img>` tags, have a src property that corresponds to the src attribute of the tag. The initial value of the src property is the attribute value that appears in the HTML tag, and setting this property with JavaScript changes the value of the HTML attribute (and causes the browser to load and display a new image). Most of the JavaScript element classes just mirror the attributes of an HTML tag, but some define additional methods. The HTMLAudioElement and HTMLVideoElement classes, for example, define methods like play() and pause() for controlling playback of audio and video files.
G
gdut-yy 已提交
160

G
gdut-yy 已提交
161
### 15.1.3 The Global Object in Web Browsers
G
gdut-yy 已提交
162 163 164 165 166 167
There is one global object per browser window or tab (§3.7). All of the JavaScript code (except code running in worker threads; see §15.13) running in that window shares this single global object. This is true regardless of how many scripts or modules are in the document: all the scripts and modules of a document share a single global object; if one script defines a property on that object, that property is visible to all the other scripts as well.

The global object is where JavaScript’s standard library is defined—the parseInt() function, the Math object, the Set class, and so on. In web browsers, the global object also contains the main entry points of various web APIs. For example, the document property represents the currently displayed document, the fetch() method makes HTTP network requests, and the Audio() constructor allows JavaScript programs to play sounds.

In web browsers, the global object does double duty: in addition to defining built-in types and functions, it also represents the current web browser window and defines properties like history (§15.10.2), which represent the window’s browsing history, and innerWidth, which holds the window’s width in pixels. One of the properties of this global object is named window, and its value is the global object itself. This means that you can simply type window to refer to the global object in your client-side code. When using window-specific features, it is often a good idea to include a window. prefix: window.innerWidth is clearer than innerWidth, for example.

G
gdut-yy 已提交
168
### 15.1.4 Scripts Share a Namespace
G
gdut-yy 已提交
169 170 171 172 173 174 175 176
With modules, the constants, variables, functions, and classes defined at the top level (i.e., outside of any function or class definition) of the module are private to the module unless they are explicitly exported, in which case, they can be selectively imported by other modules. (Note that this property of modules is honored by code-bundling tools as well.)

With non-module scripts, however, the situation is completely different. If the top-level code in a script defines a constant, variable, function, or class, that declaration will be visible to all other scripts in the same document. If one script defines a function f() and another script defines a class c, then a third script can invoke the function and instantiate the class without having to take any action to import them. So if you are not using modules, the independent scripts in your document share a single namespace and behave as if they are all part of a single larger script. This can be convenient for small programs, but the need to avoid naming conflicts can become problematic for larger programs, especially when some of the scripts are third-party libraries.

There are some historical quirks with how this shared namespace works. var and function declarations at the top level create properties in the shared global object. If one script defines a top-level function f(), then another script in the same document can invoke that function as f() or as window.f(). On the other hand, the ES6 declarations const, let, and class, when used at the top level, do not create properties in the global object. They are still defined in a shared namespace, however: if one script defines a class C, other scripts will be able to create instances of that class with new C(), but not with new window.C().

To summarize: in modules, top-level declarations are scoped to the module and can be explicitly exported. In nonmodule scripts, however, top-level declarations are scoped to the containing document, and the declarations are shared by all scripts in the document. Older var and function declarations are shared via properties of the global object. Newer const, let, and class declarations are also shared and have the same document scope, but they do not exist as properties of any object that JavaScript code has access to.

G
gdut-yy 已提交
177
### 15.1.5 Execution of JavaScript Programs
G
gdut-yy 已提交
178 179
There is no formal definition of a program in client-side JavaScript, but we can say that a JavaScript program consists of all the JavaScript code in, or referenced from, a document. These separate bits of code share a single global Window object, which gives them access to the same underlying Document object representing the HTML document. Scripts that are not modules additionally share a top-level namespace.

G
gdut-yy 已提交
180
If a web page includes an embedded frame (using the `<iframe>` element), the JavaScript code in the embedded document has a different global object and Document object than the code in the embedding document, and it can be considered a separate JavaScript program. Remember, though, that there is no formal definition of what the boundaries of a JavaScript program are. If the container document and the contained document are both loaded from the same server, the code in one document can interact with the code in the other, and you can treat them as two interacting parts of a single program, if you wish. §15.13.6 explains how a JavaScript program can send and receive messages to and from JavaScript code running in an `<iframe>`.
G
gdut-yy 已提交
181

G
gdut-yy 已提交
182
You can think of JavaScript program execution as occurring in two phases. In the first phase, the document content is loaded, and the code from `<script>` elements (both inline scripts and external scripts) is run. Scripts generally run in the order in which they appear in the document, though this default order can be modified by the async and defer attributes we’ve described. The JavaScript code within any single script is run from top to bottom, subject, of course, to JavaScript’s conditionals, loops, and other control statements. Some scripts don’t really do anything during this first phase and instead just define functions and classes for use in the second phase. Other scripts might do significant work during the first phase and then do nothing in the second. Imagine a script at the very end of a document that finds all `<h1>` and `<h2>` tags in the document and modifies the document by generating and inserting a table of contents at the beginning of the document. This could be done entirely in the first phase. (See §15.3.6 for an example that does exactly this.)
G
gdut-yy 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

Once the document is loaded and all scripts have run, JavaScript execution enters its second phase. This phase is asynchronous and event-driven. If a script is going to participate in this second phase, then one of the things it must have done during the first phase is to register at least one event handler or other callback function that will be invoked asynchronously. During this event-driven second phase, the web browser invokes event handler functions and other callbacks in response to events that occur asynchronously. Event handlers are most commonly invoked in response to user input (mouse clicks, keystrokes, etc.) but may also be triggered by network activity, document and resource loading, elapsed time, or errors in JavaScript code. Events and event handlers are described in detail in §15.2.

Some of the first events to occur during the event-driven phase are the “DOMContentLoaded” and “load” events. “DOMContentLoaded” is triggered when the HTML document has been completely loaded and parsed. The “load” event is triggered when all of the document’s external resources—such as images—are also fully loaded. JavaScript programs often use one of these events as a trigger or starting signal. It is common to see programs whose scripts define functions but take no action other than registering an event handler function to be triggered by the “load” event at the beginning of the event-driven phase of execution. It is this “load” event handler that then manipulates the document and does whatever it is that the program is supposed to do. Note that it is common in JavaScript programming for an event handler function such as the “load” event handler described here to register other event handlers.

The loading phase of a JavaScript program is relatively short: ideally less than a second. Once the document is loaded, the event-driven phase lasts for as long as the document is displayed by the web browser. Because this phase is asynchronous and event-driven, there may be long periods of inactivity where no JavaScript is executed, punctuated by bursts of activity triggered by user or network events. We’ll cover these two phases in more detail next.

CLIENT-SIDE JAVASCRIPT THREADING MODEL
JavaScript is a single-threaded language, and single-threaded execution makes for much simpler programming: you can write code with the assurance that two event handlers will never run at the same time. You can manipulate document content knowing that no other thread is attempting to modify it at the same time, and you never need to worry about locks, deadlock, or race conditions when writing JavaScript code.

Single-threaded execution means that web browsers stop responding to user input while scripts and event handlers are executing. This places a burden on JavaScript programmers: it means that JavaScript scripts and event handlers must not run for too long. If a script performs a computationally intensive task, it will introduce a delay into document loading, and the user will not see the document content until the script completes. If an event handler performs a computationally intensive task, the browser may become nonresponsive, possibly causing the user to think that it has crashed.

The web platform defines a controlled form of concurrency called a “web worker.” A web worker is a background thread for performing computationally intensive tasks without freezing the user interface. The code that runs in a web worker thread does not have access to document content, does not share any state with the main thread or with other workers, and can only communicate with the main thread and other workers through asynchronous message events, so the concurrency is not detectable to the main thread, and web workers do not alter the basic single-threaded execution model of JavaScript programs. See §15.13 for full details on the web’s safe threading mechanism.

CLIENT-SIDE JAVASCRIPT TIMELINE
We’ve already seen that JavaScript programs begin in a script-execution phase and then transition to an event-handling phase. These two phases can be further broken down into the following steps:

G
gdut-yy 已提交
200 201 202 203 204 205 206 207
1. The web browser creates a Document object and begins parsing the web page, adding Element objects and Text nodes to the document as it parses HTML elements and their textual content. The document.readyState property has the value “loading” at this stage.
2. When the HTML parser encounters a `<script>` tag that does not have any of the async, defer, or type="module" attributes, it adds that script tag to the document and then executes the script. The script is executed synchronously, and the HTML parser pauses while the script downloads (if necessary) and runs. A script like this can use document.write() to insert text into the input stream, and that text will become part of the document when the parser resumes. A script like this often simply defines functions and registers event handlers for later use, but it can traverse and manipulate the document tree as it exists at that time. That is, non-module scripts that do not have an async or defer attribute can see their own `<script>` tag and document content that comes before it.
3. When the parser encounters a `<script>` element that has the async attribute set, it begins downloading the script text (and if the script is a module, it also recursively downloads all of the script’s dependencies) and continues parsing the document. The script will be executed as soon as possible after it has downloaded, but the parser does not stop and wait for it to download. Asynchronous scripts must not use the document.write() method. They can see their own `<script>` tag and all document content that comes before it, and may or may not have access to additional document content.
4. When the document is completely parsed, the document.readyState property changes to “interactive.”
5. Any scripts that had the defer attribute set (along with any module scripts that do not have an async attribute) are executed in the order in which they appeared in the document. Async scripts may also be executed at this time. Deferred scripts have access to the complete document and they must not use the document.write() method.
6. The browser fires a “DOMContentLoaded” event on the Document object. This marks the transition from synchronous script-execution phase to the asynchronous, event-driven phase of program execution. Note, however, that there may still be async scripts that have not yet executed at this point.
7. The document is completely parsed at this point, but the browser may still be waiting for additional content, such as images, to load. When all such content finishes loading, and when all async scripts have loaded and executed, the document.readyState property changes to “complete” and the web browser fires a “load” event on the Window object.
8. From this point on, event handlers are invoked asynchronously in response to user input events, network events, timer expirations, and so on.
G
gdut-yy 已提交
208

G
gdut-yy 已提交
209
### 15.1.6 Program Input and Output
G
gdut-yy 已提交
210 211
Like any program, client-side JavaScript programs process input data to produce output data. There are a variety of inputs available:

G
gdut-yy 已提交
212 213 214 215 216
- The content of the document itself, which JavaScript code can access with the DOM API (§15.3).
- User input, in the form of events, such as mouse clicks (or touch-screen taps) on HTML `<button>` elements, or text entered into HTML `<textarea>` elements, for example. §15.2 demonstrates how JavaScript programs can respond to user events like these.
- The URL of the document being displayed is available to client-side JavaScript as document.URL. If you pass this string to the URL() constructor (§11.9), you can easily access the path, query, and fragment sections of the URL.
- The content of the HTTP “Cookie” request header is available to client-side code as document.cookie. Cookies are usually used by server-side code for maintaining user sessions, but client-side code can also read (and write) them if necessary. See §15.12.2 for further details.
- The global navigator property provides access to information about the web browser, the OS it’s running on top of, and the capabilities of each. For example, navigator.userAgent is a string that identifies the web browser, navigator.language is the user’s preferred language, and navigator.hardwareConcurrency returns the number of logical CPUs available to the web browser. Similarly, the global screen property provides access to the user’s display size via the screen.width and screen.height properties. In a sense, these navigator and screen objects are to web browsers what environment variables are to Node programs.
G
gdut-yy 已提交
217 218 219

Client-side JavaScript typically produces output, when it needs to, by manipulating the HTML document with the DOM API (§15.3) or by using a higher-level framework such as React or Angular to manipulate the document. Client-side code can also use console.log() and related methods (§11.8) to produce output. But this output is only visible in the web developer console, so it is useful when debugging, but not for user-visible output.

G
gdut-yy 已提交
220
### 15.1.7 Program Errors
G
gdut-yy 已提交
221 222 223 224 225 226 227 228
Unlike applications (such as Node applications) that run directly on top of the OS, JavaScript programs in a web browser can’t really “crash.” If an exception occurs while your JavaScript program is running, and if you do not have a catch statement to handle it, an error message will be displayed in the developer console, but any event handlers that have been registered keep running and responding to events.

If you would like to define an error handler of last resort to be invoked when this kind of uncaught exception occurs, set the onerror property of the Window object to an error handler function. When an uncaught exception propagates all the way up the call stack and an error message is about to be displayed in the developer console, the window.onerror function will be invoked with three string arguments. The first argument to window.onerror is a message describing the error. The second argument is a string that contains the URL of the JavaScript code that caused the error. The third argument is the line number within the document where the error occurred. If the onerror handler returns true, it tells the browser that the handler has handled the error and that no further action is necessary—in other words, the browser should not display its own error message.

When a Promise is rejected and there is no .catch() function to handle it, that is a situation much like an unhandled exception: an unanticipated error or a logic error in your program. You can detect this by defining a window.onunhandledrejection function or by using window.addEventListener() to register a handler for “unhandledrejection” events. The event object passed to this handler will have a promise property whose value is the Promise object that rejected and a reason property whose value is what would have been passed to a .catch() function. As with the error handlers described earlier, if you call preventDefault() on the unhandled rejection event object, it will be considered handled and won’t cause an error message in the developer console.

It is not often necessary to define onerror or onunhandledrejection handlers, but it can be quite useful as a telemetry mechanism if you want to report client-side errors to the server (using the fetch() function to make an HTTP POST request, for example) so that you can get information about unexpected errors that happen in your users’ browsers.

G
gdut-yy 已提交
229
### 15.1.8 The Web Security Model
G
gdut-yy 已提交
230 231
The fact that web pages can execute arbitrary JavaScript code on your personal device has clear security implications, and browser vendors have worked hard to balance two competing goals:

G
gdut-yy 已提交
232 233
- Defining powerful client-side APIs to enable useful web applications
- Preventing malicious code from reading or altering your data, compromising your privacy, scamming you, or wasting your time
G
gdut-yy 已提交
234 235 236 237 238 239 240 241 242

The subsections that follow give a quick overview of the security restrictions and issues that you, as a JavaScript programmer, should to be aware of.

WHAT JAVASCRIPT CAN’T DO
Web browsers’ first line of defense against malicious code is that they simply do not support certain capabilities. For example, client-side JavaScript does not provide any way to write or delete arbitrary files or list arbitrary directories on the client computer. This means a JavaScript program cannot delete data or plant viruses.

Similarly, client-side JavaScript does not have general-purpose networking capabilities. A client-side JavaScript program can make HTTP requests (§15.11.1). And another standard, known as WebSockets (§15.11.3), defines a socket-like API for communicating with specialized servers. But neither of these APIs allows unmediated access to the wider network. General-purpose internet clients and servers cannot be written in client-side JavaScript.

THE SAME-ORIGIN POLICY
G
gdut-yy 已提交
243
The same-origin policy is a sweeping security restriction on what web content JavaScript code can interact with. It typically comes into play when a web page includes `<iframe>` elements. In this case, the same-origin policy governs the interactions of JavaScript code in one frame with the content of other frames. Specifically, a script can read only the properties of windows and documents that have the same origin as the document that contains the script.
G
gdut-yy 已提交
244 245 246

The origin of a document is defined as the protocol, host, and port of the URL from which the document was loaded. Documents loaded from different web servers have different origins. Documents loaded through different ports of the same host have different origins. And a document loaded with the http: protocol has a different origin than one loaded with the https: protocol, even if they come from the same web server. Browsers typically treat every file: URL as a separate origin, which means that if you’re working on a program that displays more than one document from the same server, you may not be able to test it locally using file: URLs and will have to run a static web server during development.

G
gdut-yy 已提交
247
It is important to understand that the origin of the script itself is not relevant to the same-origin policy: what matters is the origin of the document in which the script is embedded. Suppose, for example, that a script hosted by host A is included (using the src property of a `<script>` element) in a web page served by host B. The origin of that script is host B, and the script has full access to the content of the document that contains it. If the document contains an `<iframe>` that contains a second document from host B, then the script also has full access to the content of that second document. But if the top-level document contains another `<iframe>` that displays a document from host C (or even one from host A), then the same-origin policy comes into effect and prevents the script from accessing this nested document.
G
gdut-yy 已提交
248 249 250 251 252 253 254 255 256 257 258

The same-origin policy also applies to scripted HTTP requests (see §15.11.1). JavaScript code can make arbitrary HTTP requests to the web server from which the containing document was loaded, but it does not allow scripts to communicate with other web servers (unless those web servers opt in with CORS, as we describe next).

The same-origin policy poses problems for large websites that use multiple subdomains. For example, scripts with origin orders.example.com might need to read properties from documents on example.com. To support multidomain websites of this sort, scripts can alter their origin by setting document.domain to a domain suffix. So a script with origin https://orders.example.com can change its origin to https://example.com by setting document.domain to “example.com.” But that script cannot set document.domain to “orders.example”, “ample.com”, or “com”.

The second technique for relaxing the same-origin policy is Cross-Origin Resource Sharing, or CORS, which allows servers to decide which origins they are willing to serve. CORS extends HTTP with a new Origin: request header and a new Access-Control-Allow-Origin response header. It allows servers to use a header to explicitly list origins that may request a file or to use a wildcard and allow a file to be requested by any site. Browsers honor these CORS headers and do not relax same-origin restrictions unless they are present.

CROSS-SITE SCRIPTING
Cross-site scripting, or XSS, is a term for a category of security issues in which an attacker injects HTML tags or scripts into a target website. Client-side JavaScript programmers must be aware of, and defend against, cross-site scripting.

A web page is vulnerable to cross-site scripting if it dynamically generates document content and bases that content on user-submitted data without first “sanitizing” that data by removing any embedded HTML tags from it. As a trivial example, consider the following web page that uses JavaScript to greet the user by name:
G
gdut-yy 已提交
259
```html
G
gdut-yy 已提交
260 261 262 263
<script>
let name = new URL(document.URL).searchParams.get("name");
document.querySelector('h1').innerHTML = "Hello " + name;
</script>
G
gdut-yy 已提交
264 265 266
```
This two-line script extracts input from the “name” query parameter of the document URL. It then uses the DOM API to inject an HTML string into the first `<h1>` tag in the document. This page is intended to be invoked with a URL like this:
```
G
gdut-yy 已提交
267
http://www.example.com/greet.html?name=David
G
gdut-yy 已提交
268
```
G
gdut-yy 已提交
269
When used like this, it displays the text “Hello David.” But consider what happens when it is invoked with this query parameter:
G
gdut-yy 已提交
270
```
G
gdut-yy 已提交
271
name=%3Cimg%20src=%22x.png%22%20onload=%22alert(%27hacked%27)%22/%3E
G
gdut-yy 已提交
272
```
G
gdut-yy 已提交
273
When the URL-escaped parameters are decoded, this URL causes the following HTML to be injected into the document:
G
gdut-yy 已提交
274
```
G
gdut-yy 已提交
275
Hello <img src="x.png" onload="alert('hacked')"/>
G
gdut-yy 已提交
276
```
G
gdut-yy 已提交
277 278 279 280 281
After the image loads, the string of JavaScript in the onload attribute is executed. The global alert() function displays a modal dialogue box. A single dialogue box is relatively benign but demonstrates that arbitrary code execution is possible on this site because it displays unsanitized HTML.

Cross-site scripting attacks are so called because more than one site is involved. Site B includes a specially crafted link (like the one in the previous example) to site A. If site B can convince users to click the link, they will be taken to site A, but that site will now be running code from site B. That code might deface the page or cause it to malfunction. More dangerously, the malicious code could read cookies stored by site A (perhaps account numbers or other personally identifying information) and send that data back to site B. The injected code could even track the user’s keystrokes and send that data back to site B.

In general, the way to prevent XSS attacks is to remove HTML tags from any untrusted data before using it to create dynamic document content. You can fix the greet.html file shown earlier by replacing special HTML characters in the untrusted input string with their equivalent HTML entities:
G
gdut-yy 已提交
282
```js
G
gdut-yy 已提交
283 284 285 286 287 288 289
name = name
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#x27;")
    .replace(/\//g, "&#x2F;")
G
gdut-yy 已提交
290 291
```
Another approach to the problem of XSS is to structure your web applications so that untrusted content is always displayed in an `<iframe>` with the sandbox attribute set to disable scripting and other capabilities.
G
gdut-yy 已提交
292 293 294

Cross-site scripting is a pernicious vulnerability whose roots go deep into the architecture of the web. It is worth understanding this vulnerability in-depth, but further discussion is beyond the scope of this book. There are many online resources to help you defend against cross-site scripting.

G
gdut-yy 已提交
295
## 15.2 Events
G
gdut-yy 已提交
296 297 298 299 300 301 302 303
Client-side JavaScript programs use an asynchronous event-driven programming model. In this style of programming, the web browser generates an event whenever something interesting happens to the document or browser or to some element or object associated with it. For example, the web browser generates an event when it finishes loading a document, when the user moves the mouse over a hyperlink, or when the user strikes a key on the keyboard. If a JavaScript application cares about a particular type of event, it can register one or more functions to be invoked when events of that type occur. Note that this is not unique to web programming: all applications with graphical user interfaces are designed this way—they sit around waiting to be interacted with (i.e., they wait for events to occur), and then they respond.

In client-side JavaScript, events can occur on any element within an HTML document, and this fact makes the event model of web browsers significantly more complex than Node’s event model. We begin this section with some important definitions that help to explain that event model:

event type
This string specifies what kind of event occurred. The type “mousemove,” for example, means that the user moved the mouse. The type “keydown” means that the user pressed a key on the keyboard down. And the type “load” means that a document (or some other resource) has finished loading from the network. Because the type of an event is just a string, it’s sometimes called an event name, and indeed, we use this name to identify the kind of event we’re talking about.

event target
G
gdut-yy 已提交
304
This is the object on which the event occurred or with which the event is associated. When we speak of an event, we must specify both the type and the target. A load event on a Window, for example, or a click event on a `<button>` Element. Window, Document, and Element objects are the most common event targets in client-side JavaScript applications, but some events are triggered on other kinds of objects. For example, a Worker object (a kind of thread, covered §15.13) is a target for “message” events that occur when the worker thread sends a message to the main thread.
G
gdut-yy 已提交
305 306 307 308 309 310 311 312

event handler, or event listener
This function handles or responds to an event.2 Applications register their event handler functions with the web browser, specifying an event type and an event target. When an event of the specified type occurs on the specified target, the browser invokes the handler function. When event handlers are invoked for an object, we say that the browser has “fired,” “triggered,” or “dispatched” the event. There are a number of ways to register event handlers, and the details of handler registration and invocation are explained in §15.2.2 and §15.2.3.

event object
This object is associated with a particular event and contains details about that event. Event objects are passed as an argument to the event handler function. All event objects have a type property that specifies the event type and a target property that specifies the event target. Each event type defines a set of properties for its associated event object. The object associated with a mouse event includes the coordinates of the mouse pointer, for example, and the object associated with a keyboard event contains details about the key that was pressed and the modifier keys that were held down. Many event types define only a few standard properties—such as type and target—and do not carry much other useful information. For those events, it is the simple occurrence of the event, not the event details, that matter.

event propagation
G
gdut-yy 已提交
313
This is the process by which the browser decides which objects to trigger event handlers on. For events that are specific to a single object—such as the “load” event on the Window object or a “message” event on a Worker object—no propagation is required. But when certain kinds of events occur on elements within the HTML document, however, they propagate or “bubble” up the document tree. If the user moves the mouse over a hyperlink, the mousemove event is first fired on the `<a>` element that defines that link. Then it is fired on the containing elements: perhaps a `<p>` element, a `<section>` element, and the Document object itself. It is sometimes more convenient to register a single event handler on a Document or other container element than to register handlers on each individual element you’re interested in. An event handler can stop the propagation of an event so that it will not continue to bubble and will not trigger handlers on containing elements. Handlers do this by invoking a method of the event object. In another form of event propagation, known as event capturing, handlers specially registered on container elements have the opportunity to intercept (or “capture”) events before they are delivered to their actual target. Event bubbling and capturing are covered in detail in §15.2.4.
G
gdut-yy 已提交
314 315 316

Some events have default actions associated with them. When a click event occurs on a hyperlink, for example, the default action is for the browser to follow the link and load a new page. Event handlers can prevent this default action by invoking a method of the event object. This is sometimes called “canceling” the event and is covered in §15.2.5.

G
gdut-yy 已提交
317
### 15.2.1 Event Categories
G
gdut-yy 已提交
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
Client-side JavaScript supports such a large number of event types that there is no way this chapter can cover them all. It can be useful, though, to group events into some general categories, to illustrate the scope and wide variety of supported events:

Device-dependent input events
These events are directly tied to a specific input device, such as the mouse or keyboard. They include event types such as “mousedown,” “mousemove,” “mouseup,” “touchstart,” “touchmove,” “touchend,” “keydown,” and “keyup.”

Device-independent input events
These input events are not directly tied to a specific input device. The “click” event, for example, indicates that a link or button (or other document element) has been activated. This is often done via a mouse click, but it could also be done by keyboard or (on touch-sensitive devices) with a tap. The “input” event is a device-independent alternative to the “keydown” event and supports keyboard input as well as alternatives such as cut-and-paste and input methods used for ideographic scripts. The “pointerdown,” “pointermove,” and “pointerup” event types are device-independent alternatives to mouse and touch events. They work for mouse-type pointers, for touch screens, and for pen- or stylus-style input as well.

User interface events
UI events are higher-level events, often on HTML form elements that define a user interface for a web application. They include the “focus” event (when a text input field gains keyboard focus), the “change” event (when the user changes the value displayed by a form element), and the “submit” event (when the user clicks a Submit button in a form).

State-change events
Some events are not triggered directly by user activity, but by network or browser activity, and indicate some kind of life-cycle or state-related change. The “load” and “DOMContentLoaded” events—fired on the Window and Document objects, respectively, at the end of document loading—are probably the most commonly used of these events (see “Client-side JavaScript timeline”). Browsers fire “online” and “offline” events on the Window object when network connectivity changes. The browser’s history management mechanism (§15.10.4) fires the “popstate” event in response to the browser’s Back button.

API-specific events
G
gdut-yy 已提交
333
A number of web APIs defined by HTML and related specifications include their own event types. The HTML `<video>` and `<audio>` elements define a long list of associated event types such as “waiting,” “playing,” “seeking,” “volumechange,” and so on, and you can use them to customize media playback. Generally speaking, web platform APIs that are asynchronous and were developed before Promises were added to JavaScript are event-based and define API-specific events. The IndexedDB API, for example (§15.12.3), fires “success” and “error” events when database requests succeed or fail. And although the new fetch() API (§15.11.1) for making HTTP requests is Promise-based, the XMLHttpRequest API that it replaces defines a number of API-specific event types.
G
gdut-yy 已提交
334

G
gdut-yy 已提交
335
### 15.2.2 Registering Event Handlers
G
gdut-yy 已提交
336 337 338 339
There are two basic ways to register event handlers. The first, from the early days of the web, is to set a property on the object or document element that is the event target. The second (newer and more general) technique is to pass the handler to the addEventListener() method of the object or element.

SETTING EVENT HANDLER PROPERTIES
The simplest way to register an event handler is by setting a property of the event target to the desired event handler function. By convention, event handler properties have names that consist of the word “on” followed by the event name: onclick, onchange, onload, onmouseover, and so on. Note that these property names are case sensitive and are written in all lowercase,3 even when the event type (such as “mousedown”) consists of multiple words. The following code includes two event handler registrations of this kind:
G
gdut-yy 已提交
340
```js
G
gdut-yy 已提交
341 342 343 344 345 346 347 348 349 350 351 352 353
// Set the onload property of the Window object to a function.
// The function is the event handler: it is invoked when the document loads.
window.onload = function() {
    // Look up a <form> element
    let form = document.querySelector("form#shipping");
    // Register an event handler function on the form that will be invoked
    // before the form is submitted. Assume isFormValid() is defined elsewhere.
    form.onsubmit = function(event) { // When the user submits the form
        if (!isFormValid(this)) {     // check whether form inputs are valid
            event.preventDefault();   // and if not, prevent form submission.
        }
    };
};
G
gdut-yy 已提交
354
```
G
gdut-yy 已提交
355 356 357
The shortcoming of event handler properties is that they are designed around the assumption that event targets will have at most one handler for each type of event. It is often better to register event handlers using addEventListener() because that technique does not overwrite any previously registered handlers.

SETTING EVENT HANDLER ATTRIBUTES
G
gdut-yy 已提交
358
The event handler properties of document elements can also be defined directly in the HTML file as attributes on the corresponding HTML tag. (Handlers that would be registered on the Window element with JavaScript can be defined with attributes on the `<body>` tag in HTML.) This technique is generally frowned upon in modern web development, but it is possible, and it’s documented here because you may still see it in existing code.
G
gdut-yy 已提交
359 360

When defining an event handler as an HTML attribute, the attribute value should be a string of JavaScript code. That code should be the body of the event handler function, not a complete function declaration. That is, your HTML event handler code should not be surrounded by curly braces and prefixed with the function keyword. For example:
G
gdut-yy 已提交
361
```html
G
gdut-yy 已提交
362
<button onclick="console.log('Thank you');">Please Click</button>
G
gdut-yy 已提交
363
```
G
gdut-yy 已提交
364 365 366
If an HTML event handler attribute contains multiple JavaScript statements, you must remember to separate those statements with semicolons or break the attribute value across multiple lines.

When you specify a string of JavaScript code as the value of an HTML event handler attribute, the browser converts your string into a function that works something like this one:
G
gdut-yy 已提交
367
```js
G
gdut-yy 已提交
368 369 370 371 372 373 374 375 376
function(event) {
    with(document) {
        with(this.form || {}) {
            with(this) {
                /* your code here */
            }
        }
    }
}
G
gdut-yy 已提交
377 378
```
The event argument means that your handler code can refer to the current event object as event. The with statements mean that the code of your handler can refer to the properties of the target object, the containing `<form>` (if any), and the containing Document object directly, as if they were variables in scope. The with statement is forbidden in strict mode (§5.6.3), but JavaScript code in HTML attributes is never strict. Event handlers defined in this way are executed in an environment in which unexpected variables are defined. This can be a source of confusing bugs and is a good reason to avoid writing event handlers in HTML.
G
gdut-yy 已提交
379 380 381 382

ADDEVENTLISTENER()
Any object that can be an event target—this includes the Window and Document objects and all document Elements—defines a method named addEventListener() that you can use to register an event handler for that target. addEventListener() takes three arguments. The first is the event type for which the handler is being registered. The event type (or name) is a string that does not include the “on” prefix used when setting event handler properties. The second argument to addEventListener() is the function that should be invoked when the specified type of event occurs. The third argument is optional and is explained below.

G
gdut-yy 已提交
383 384
The following code registers two handlers for the “click” event on a `<button>` element. Note the differences between the two techniques used:
```html
G
gdut-yy 已提交
385 386 387 388 389 390
<button id="mybutton">Click me</button>
<script>
let b = document.querySelector("#mybutton");
b.onclick = function() { console.log("Thanks for clicking me!"); };
b.addEventListener("click", () => { console.log("Thanks again!"); });
</script>
G
gdut-yy 已提交
391
```
G
gdut-yy 已提交
392 393 394
Calling addEventListener() with “click” as its first argument does not affect the value of the onclick property. In this code, a button click will log two messages to the developer console. And if we called addEventListener() first and then set onclick, we would still log two messages, just in the opposite order. More importantly, you can call addEventListener() multiple times to register more than one handler function for the same event type on the same object. When an event occurs on an object, all of the handlers registered for that type of event are invoked in the order in which they were registered. Invoking addEventListener() more than once on the same object with the same arguments has no effect—the handler function remains registered only once, and the repeated invocation does not alter the order in which handlers are invoked.

addEventListener() is paired with a removeEventListener() method that expects the same two arguments (plus an optional third) but removes an event handler function from an object rather than adding it. It is often useful to temporarily register an event handler and then remove it soon afterward. For example, when you get a “mousedown” event, you might register temporary event handlers for “mousemove” and “mouseup” events so that you can see if the user drags the mouse. You’d then deregister these handlers when the “mouseup” event arrives. In such a situation, your event handler removal code might look like this:
G
gdut-yy 已提交
395
```js
G
gdut-yy 已提交
396 397
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
G
gdut-yy 已提交
398
```
G
gdut-yy 已提交
399 400 401
The optional third argument to addEventListener() is a boolean value or object. If you pass true, then your handler function is registered as a capturing event handler and is invoked at a different phase of event dispatch. We’ll cover event capturing in §15.2.4. If you pass a third argument of true when you register an event listener, then you must also pass true as the third argument to removeEventListener() if you want to remove the handler.

Registering a capturing event handler is only one of the three options that addEventListener() supports, and instead of passing a single boolean value, you can also pass an object that explicitly specifies the options you want:
G
gdut-yy 已提交
402
```js
G
gdut-yy 已提交
403 404 405 406 407
document.addEventListener("click", handleClick, {
    capture: true,
    once: true,
    passive: true
});
G
gdut-yy 已提交
408
```
G
gdut-yy 已提交
409 410 411 412 413 414 415 416
If the Options object has a capture property set to true, then the event handler will be registered as a capturing handler. If that property is false or is omitted, then the handler will be non-capturing.

If the Options object has a once property set to true, then the event listener will be automatically removed after it is triggered once. If this property is false or is omitted, then the handler is never automatically removed.

If the Options object has a passive property set to true, it indicates that the event handler will never call preventDefault() to cancel the default action (see §15.2.5). This is particularly important for touch events on mobile devices—if event handlers for “touchmove” events can prevent the browser’s default scrolling action, then the browser cannot implement smooth scrolling. This passive property provides a way to register a potentially disruptive event handler of this sort but lets the web browser know that it can safely begin its default behavior—such as scrolling—while the event handler is running. Smooth scrolling is so important for a good user experience that Firefox and Chrome make “touchmove” and “mousewheel” events passive by default. So if you actually want to register a handler that calls preventDefault() for one of these events, you should explicitly set the passive property to false.

You can also pass an Options object to removeEventListener(), but the capture property is the only one that is relevant. There is no need to specify once or passive when removing a listener, and these properties are ignored.

G
gdut-yy 已提交
417
### 15.2.3 Event Handler Invocation
G
gdut-yy 已提交
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
Once you’ve registered an event handler, the web browser will invoke it automatically when an event of the specified type occurs on the specified object. This section describes event handler invocation in detail, explaining event handler arguments, the invocation context (the this value), and the meaning of the return value of an event handler.

EVENT HANDLER ARGUMENT
Event handlers are invoked with an Event object as their single argument. The properties of the Event object provide details about the event:

type
The type of the event that occurred.

target
The object on which the event occurred.

currentTarget
For events that propagate, this property is the object on which the current event handler was registered.

timeStamp
A timestamp (in milliseconds) that represents when the event occurred but that does not represent an absolute time. You can determine the elapsed time between two events by subtracting the timestamp of the first event from the timestamp of the second.

isTrusted
This property will be true if the event was dispatched by the web browser itself and false if the event was dispatched by JavaScript code.

Specific kinds of events have additional properties. Mouse and pointer events, for example, have clientX and clientY properties that specify the window coordinates at which the event occurred.

EVENT HANDLER CONTEXT
When you register an event handler by setting a property, it looks as if you are defining a new method on the target object:
G
gdut-yy 已提交
442
```js
G
gdut-yy 已提交
443
target.onclick = function() { /* handler code */ };
G
gdut-yy 已提交
444
```
G
gdut-yy 已提交
445 446 447 448 449 450 451 452 453 454 455 456
It isn’t surprising, therefore, that event handlers are invoked as methods of the object on which they are defined. That is, within the body of an event handler, the this keyword refers to the object on which the event handler was registered.

Handlers are invoked with the target as their this value, even when registered using addEventListener(). This does not work for handlers defined as arrow functions, however: arrow functions always have the same this value as the scope in which they are defined.

HANDLER RETURN VALUE
In modern JavaScript, event handlers should not return anything. You may see event handlers that return values in older code, and the return value is typically a signal to the browser that it should not perform the default action associated with the event. If the onclick handler of a Submit button in a form returns false, for example, then the web browser will not submit the form (usually because the event handler determined that the user’s input fails client-side validation).

The standard and preferred way to prevent the browser from performing a default action is to call the preventDefault() method (§15.2.5) on the Event object.

INVOCATION ORDER
An event target may have more than one event handler registered for a particular type of event. When an event of that type occurs, the browser invokes all of the handlers in the order in which they were registered. Interestingly, this is true even if you mix event handlers registered with addEventListener() with an event handler registered on an object property like onclick.

G
gdut-yy 已提交
457
### 15.2.4 Event Propagation
G
gdut-yy 已提交
458 459
When the target of an event is the Window object or some other standalone object, the browser responds to an event simply by invoking the appropriate handlers on that one object. When the event target is a Document or document Element, however, the situation is more complicated.

G
gdut-yy 已提交
460
After the event handlers registered on the target element are invoked, most events “bubble” up the DOM tree. The event handlers of the target’s parent are invoked. Then the handlers registered on the target’s grandparent are invoked. This continues up to the Document object, and then beyond to the Window object. Event bubbling provides an alternative to registering handlers on lots of individual document elements: instead, you can register a single handler on a common ancestor element and handle events there. You might register a “change” handler on a `<form>` element, for example, instead of registering a “change” handler for every element in the form.
G
gdut-yy 已提交
461 462 463 464 465 466 467

Most events that occur on document elements bubble. Notable exceptions are the “focus,” “blur,” and “scroll” events. The “load” event on document elements bubbles, but it stops bubbling at the Document object and does not propagate on to the Window object. (The “load” event handlers of the Window object are triggered only when the entire document has loaded.)

Event bubbling is the third “phase” of event propagation. The invocation of the event handlers of the target object itself is the second phase. The first phase, which occurs even before the target handlers are invoked, is called the “capturing” phase. Recall that addEventListener() takes an optional third argument. If that argument is true, or {capture:true}, then the event handler is registered as a capturing event handler for invocation during this first phase of event propagation. The capturing phase of event propagation is like the bubbling phase in reverse. The capturing handlers of the Window object are invoked first, then the capturing handlers of the Document object, then of the body object, and so on down the DOM tree until the capturing event handlers of the parent of the event target are invoked. Capturing event handlers registered on the event target itself are not invoked.

Event capturing provides an opportunity to peek at events before they are delivered to their target. A capturing event handler can be used for debugging, or it can be used along with the event cancellation technique described in the next section to filter events so that the target event handlers are never actually invoked. One common use for event capturing is handling mouse drags, where mouse motion events need to be handled by the object being dragged, not the document elements over which it is dragged.

G
gdut-yy 已提交
468
### 15.2.5 Event Cancellation
G
gdut-yy 已提交
469 470 471 472
Browsers respond to many user events, even if your code does not: when the user clicks the mouse on a hyperlink, the browser follows the link. If an HTML text input element has the keyboard focus and the user types a key, the browser will enter the user’s input. If the user moves their finger across a touch-screen device, the browser scrolls. If you register an event handler for events like these, you can prevent the browser from performing its default action by invoking the preventDefault() method of the event object. (Unless you registered the handler with the passive option, which makes preventDefault() ineffective.)

Canceling the default action associated with an event is only one kind of event cancellation. We can also cancel the propagation of events by calling the stopPropagation() method of the event object. If there are other handlers defined on the same object, the rest of those handlers will still be invoked, but no event handlers on any other object will be invoked after stopPropagation() is called. stopPropagation() works during the capturing phase, at the event target itself, and during the bubbling phase. stopImmediatePropagation() works like stopPropagation(), but it also prevents the invocation of any subsequent event handlers registered on the same object.

G
gdut-yy 已提交
473
### 15.2.6 Dispatching Custom Events
G
gdut-yy 已提交
474 475 476
Client-side JavaScript’s event API is a relatively powerful one, and you can use it to define and dispatch your own events. Suppose, for example, that your program periodically needs to perform a long calculation or make a network request and that, while this operation is pending, other operations are not possible. You want to let the user know about this by displaying “spinners” to indicate that the application is busy. But the module that is busy should not need to know where the spinners should be displayed. Instead, that module might just dispatch an event to announce that it is busy and then dispatch another event when it is no longer busy. Then, the UI module can register event handlers for those events and take whatever UI actions are appropriate to notify the user.

If a JavaScript object has an addEventListener() method, then it is an “event target,” and this means it also has a dispatchEvent() method. You can create your own event object with the CustomEvent() constructor and pass it to dispatchEvent(). The first argument to CustomEvent() is a string that specifies the type of your event, and the second argument is an object that specifies the properties of the event object. Set the detail property of this object to a string, object, or other value that represents the content of your event. If you plan to dispatch your event on a document element and want it to bubble up the document tree, add bubbles:true to the second argument:
G
gdut-yy 已提交
477
```js
G
gdut-yy 已提交
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
// Dispatch a custom event so the UI knows we are busy
document.dispatchEvent(new CustomEvent("busy", { detail: true }));

// Perform a network operation
fetch(url)
  .then(handleNetworkResponse)
  .catch(handleNetworkError)
  .finally(() => {
      // After the network request has succeeded or failed, dispatch
      // another event to let the UI know that we are no longer busy.
      document.dispatchEvent(new CustomEvent("busy", { detail: false }));
  });

// Elsewhere, in your program you can register a handler for "busy" events
// and use it to show or hide the spinner to let the user know.
document.addEventListener("busy", (e) => {
    if (e.detail) {
        showSpinner();
    } else {
        hideSpinner();
    }
});
G
gdut-yy 已提交
500
```
G
gdut-yy 已提交
501
## 15.3 Scripting Documents
G
gdut-yy 已提交
502 503 504 505 506 507
Client-side JavaScript exists to turn static HTML documents into interactive web applications. So scripting the content of web pages is really the central purpose of JavaScript.

Every Window object has a document property that refers to a Document object. The Document object represents the content of the window, and it is the subject of this section. The Document object does not stand alone, however. It is the central object in the DOM for representing and manipulating document content.

The DOM was introduced in §15.1.2. This section explains the API in detail. It covers:

G
gdut-yy 已提交
508 509 510 511 512
- How to query or select individual elements from a document.
- How to traverse a document, and how to find the ancestors, siblings, and descendants of any document element.
- How to query and set the attributes of document elements.
- How to query, set, and modify the content of a document.
- How to modify the structure of a document by creating, inserting, and deleting nodes.
G
gdut-yy 已提交
513

G
gdut-yy 已提交
514
### 15.3.1 Selecting Document Elements
G
gdut-yy 已提交
515
Client-side JavaScript programs often need to manipulate one or more elements within the document. The global document property refers to the Document object, and the Document object has head and body properties that refer to the Element objects for the `<head>` and `<body>` tags, respectively. But a program that wants to manipulate an element embedded more deeply in the document must somehow obtain or select the Element objects that refer to those document elements.
G
gdut-yy 已提交
516 517 518 519 520

SELECTING ELEMENTS WITH CSS SELECTORS
CSS stylesheets have a very powerful syntax, known as selectors, for describing elements or sets of elements within a document. The DOM methods querySelector() and querySelectorAll() allow us to find the element or elements within a document that match a specified CSS selector. Before we cover the methods, we’ll start with a quick tutorial on CSS selector syntax.

CSS selectors can describe elements by tag name, the value of their id attribute, or the words in their class attribute:
G
gdut-yy 已提交
521
```js
G
gdut-yy 已提交
522 523 524
div                     // Any <div> element
#nav                    // The element with id="nav"
.warning                // Any element with "warning" in its class attribute
G
gdut-yy 已提交
525
```
G
gdut-yy 已提交
526
The # character is used to match based on the id attribute, and the . character is used to match based on the class attribute. Elements can also be selected based on more general attribute values:
G
gdut-yy 已提交
527
```js
G
gdut-yy 已提交
528 529
p[lang="fr"]            // A paragraph written in French: <p lang="fr">
*[name="x"]             // Any element with a name="x" attribute
G
gdut-yy 已提交
530
```
G
gdut-yy 已提交
531
Note that these examples combine a tag name selector (or the * tag name wildcard) with an attribute selector. More complex combinations are also possible:
G
gdut-yy 已提交
532
```js
G
gdut-yy 已提交
533 534
span.fatal.error        // Any <span> with "fatal" and "error" in its class
span[lang="fr"].warning // Any <span> in French with class "warning"
G
gdut-yy 已提交
535
```
G
gdut-yy 已提交
536
Selectors can also specify document structure:
G
gdut-yy 已提交
537
```js
G
gdut-yy 已提交
538 539 540 541 542
#log span               // Any <span> descendant of the element with id="log"
#log>span               // Any <span> child of the element with id="log"
body>h1:first-child     // The first <h1> child of the <body>
img + p.caption         // A <p> with class "caption" immediately after an <img>
h2 ~ p                  // Any <p> that follows an <h2> and is a sibling of it
G
gdut-yy 已提交
543
```
G
gdut-yy 已提交
544
If two selectors are separated by a comma, it means that we’ve selected elements that match either one of the selectors:
G
gdut-yy 已提交
545
```js
G
gdut-yy 已提交
546
button, input[type="button"] // All <button> and <input type="button"> elements
G
gdut-yy 已提交
547
```
G
gdut-yy 已提交
548
As you can see, CSS selectors allow us to refer to elements within a document by type, ID, class, attributes, and position within the document. The querySelector() method takes a CSS selector string as its argument and returns the first matching element in the document that it finds, or returns null if none match:
G
gdut-yy 已提交
549
```js
G
gdut-yy 已提交
550 551
// Find the document element for the HTML tag with attribute id="spinner"
let spinner = document.querySelector("#spinner");
G
gdut-yy 已提交
552
```
G
gdut-yy 已提交
553
querySelectorAll() is similar, but it returns all matching elements in the document rather than just returning the first:
G
gdut-yy 已提交
554
```js
G
gdut-yy 已提交
555 556
// Find all Element objects for <h1>, <h2>, and <h3> tags
let titles = document.querySelectorAll("h1, h2, h3");
G
gdut-yy 已提交
557
```
G
gdut-yy 已提交
558 559 560 561 562 563 564 565
The return value of querySelectorAll() is not an array of Element objects. Instead, it is an array-like object known as a NodeList. NodeList objects have a length property and can be indexed like arrays, so you can loop over them with a traditional for loop. NodeLists are also iterable, so you can use them with for/of loops as well. If you want to convert a NodeList into a true array, simply pass it to Array.from().

The NodeList returned by querySelectorAll() will have a length property set to 0 if there are not any elements in the document that match the specified selector.

querySelector() and querySelectorAll() are implemented by the Element class as well as by the Document class. When invoked on an element, these methods will only return elements that are descendants of that element.

Note that CSS defines ::first-line and ::first-letter pseudoelements. In CSS, these match portions of text nodes rather than actual elements. They will not match if used with querySelectorAll() or querySelector(). Also, many browsers will refuse to return matches for the :link and :visited pseudoclasses, as this could expose information about the user’s browsing history.

G
gdut-yy 已提交
566 567
Another CSS-based element selection method is closest(). This method is defined by the Element class and takes a selector as its only argument. If the selector matches the element it is invoked on, it returns that element. Otherwise, it returns the closest ancestor element that the selector matches, or returns null if none matched. In a sense, closest() is the opposite of querySelector(): closest() starts at an element and looks for a match above it in the tree, while querySelector() starts with an element and looks for a match below it in the tree. closest() can be useful when you have registered an event handler at a high level in the document tree. If you are handling a “click” event, for example, you might want to know whether it is a click a hyperlink. The event object will tell you what the target was, but that target might be the text inside a link rather than the hyperlink’s `<a>` tag itself. Your event handler could look for the nearest containing hyperlink like this:
```js
G
gdut-yy 已提交
568 569
// Find the closest enclosing <a> tag that has an href attribute.
let hyperlink = event.target.closest("a[href]");
G
gdut-yy 已提交
570
```
G
gdut-yy 已提交
571
Here is another way you might use closest():
G
gdut-yy 已提交
572
```js
G
gdut-yy 已提交
573 574 575 576
// Return true if the element e is inside of an HTML list element
function insideList(e) {
    return e.closest("ul,ol,dl") !== null;
}
G
gdut-yy 已提交
577
```
G
gdut-yy 已提交
578
The related method matches() does not return ancestors or descendants: it simply tests whether an element is matched by a CSS selector and returns true if so and false otherwise:
G
gdut-yy 已提交
579
```js
G
gdut-yy 已提交
580 581 582 583
// Return true if e is an HTML heading element
function isHeading(e) {
    return e.matches("h1,h2,h3,h4,h5,h6");
}
G
gdut-yy 已提交
584
```
G
gdut-yy 已提交
585 586
OTHER ELEMENT SELECTION METHODS
In addition to querySelector() and querySelectorAll(), the DOM also defines a number of older element selection methods that are more or less obsolete now. You may still see some of these methods (especially getElementById()) in use, however:
G
gdut-yy 已提交
587
```js
G
gdut-yy 已提交
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
// Look up an element by id. The argument is just the id, without
// the CSS selector prefix #. Similar to document.querySelector("#sect1")
let sect1 = document.getElementById("sect1");

// Look up all elements (such as form checkboxes) that have a name="color"
// attribute. Similar to document.querySelectorAll('*[name="color"]');
let colors = document.getElementsByName("color");

// Look up all <h1> elements in the document.
// Similar to document.querySelectorAll("h1")
let headings = document.getElementsByTagName("h1");

// getElementsByTagName() is also defined on elements.
// Get all <h2> elements within the sect1 element.
let subheads = sect1.getElementsByTagName("h2");

// Look up all elements that have class "tooltip."
// Similar to document.querySelectorAll(".tooltip")
let tooltips = document.getElementsByClassName("tooltip");

// Look up all descendants of sect1 that have class "sidebar"
// Similar to sect1.querySelectorAll(".sidebar")
let sidebars = sect1.getElementsByClassName("sidebar");
G
gdut-yy 已提交
611
```
G
gdut-yy 已提交
612 613 614
Like querySelectorAll(), the methods in this code return a NodeList (except for getElementById(), which returns a single Element object). Unlike querySelectorAll(), however, the NodeLists returned by these older selection methods are “live,” which means that the length and content of the list can change if the document content or structure changes.

PRESELECTED ELEMENTS
G
gdut-yy 已提交
615 616
For historical reasons, the Document class defines shortcut properties to access certain kinds of nodes. The images, forms, and links properties, for example, provide easy access to the `<img>`, `<form>`, and `<a>` elements (but only `<a>` tags that have an href attribute) of a document. These properties refer to HTMLCollection objects, which are much like NodeList objects, but they can additionally be indexed by element ID or name. With the document.forms property, for example, you can access the `<form id="address">` tag as:
```js
G
gdut-yy 已提交
617
document.forms.address;
G
gdut-yy 已提交
618
```
G
gdut-yy 已提交
619 620
An even more outdated API for selecting elements is the document.all property, which is like an HTMLCollection for all elements in the document. document.all is deprecated, and you should no longer use it.

G
gdut-yy 已提交
621
### 15.3.2 Document Structure and Traversal
G
gdut-yy 已提交
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
Once you have selected an Element from a Document, you sometimes need to find structurally related portions (parent, siblings, children) of the document. When we are primarily interested in the Elements of a document instead of the text within them (and the whitespace between them, which is also text), there is a traversal API that allows us to treat a document as a tree of Element objects, ignoring Text nodes that are also part of the document. This traversal API does not involve any methods; it is simply a set of properties on Element objects that allow us to refer to the parent, children, and siblings of a given element:

parentNode
This property of an element refers to the parent of the element, which will be another Element or a Document object.

children
This NodeList contains the Element children of an element, but excludes non-Element children like Text nodes (and Comment nodes).

childElementCount
The number of Element children. Returns the same value as children.length.

firstElementChild, lastElementChild
These properties refer to the first and last Element children of an Element. They are null if the Element has no Element children.

nextElementSibling, previousElementSibling
These properties refer to the sibling Elements immediately before or immediately after an Element, or null if there is no such sibling.

Using these Element properties, the second child Element of the first child Element of the Document can be referred to with either of these expressions:
G
gdut-yy 已提交
640
```js
G
gdut-yy 已提交
641 642
document.children[0].children[1]
document.firstElementChild.firstElementChild.nextElementSibling
G
gdut-yy 已提交
643 644
```
(In a standard HTML document, both of those expressions refer to the `<body>` tag of the document.)
G
gdut-yy 已提交
645 646

Here are two functions that demonstrate how you can use these properties to recursively do a depth-first traversal of a document invoking a specified function for every element in the document:
G
gdut-yy 已提交
647
```js
G
gdut-yy 已提交
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
// Recursively traverse the Document or Element e, invoking the function
// f on e and on each of its descendants
function traverse(e, f) {
    f(e);                             // Invoke f() on e
    for(let child of e.children) {    // Iterate over the children
        traverse(child, f);           // And recurse on each one
    }
}

function traverse2(e, f) {
    f(e);                             // Invoke f() on e
    let child = e.firstElementChild;  // Iterate the children linked-list style
    while(child !== null) {
        traverse2(child, f);          // And recurse
        child = child.nextElementSibling;
    }
}
G
gdut-yy 已提交
665
```
G
gdut-yy 已提交
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
DOCUMENTS AS TREES OF NODES
If you want to traverse a document or some portion of a document and do not want to ignore the Text nodes, you can use a different set of properties defined on all Node objects. This will allow you to see Elements, Text nodes, and even Comment nodes (which represent HTML comments in the document).

All Node objects define the following properties:

parentNode
The node that is the parent of this one, or null for nodes like the Document object that have no parent.

childNodes
A read-only NodeList that that contains all children (not just Element children) of the node.

firstChild, lastChild
The first and last child nodes of a node, or null if the node has no children.

nextSibling, previousSibling
The next and previous sibling nodes of a node. These properties connect nodes in a doubly linked list.

nodeType
A number that specifies what kind of node this is. Document nodes have value 9. Element nodes have value 1. Text nodes have value 3. Comment nodes have value 8.

nodeValue
The textual content of a Text or Comment node.

nodeName
The HTML tag name of an Element, converted to uppercase.

Using these Node properties, the second child node of the first child of the Document can be referred to with expressions like these:
G
gdut-yy 已提交
693
```js
G
gdut-yy 已提交
694 695
document.childNodes[0].childNodes[1]
document.firstChild.firstChild.nextSibling
G
gdut-yy 已提交
696
```
G
gdut-yy 已提交
697
Suppose the document in question is the following:
G
gdut-yy 已提交
698
```html
G
gdut-yy 已提交
699
<html><head><title>Test</title></head><body>Hello World!</body></html>
G
gdut-yy 已提交
700 701
```
Then the second child of the first child is the `<body>` element. It has a nodeType of 1 and a nodeName of “BODY”.
G
gdut-yy 已提交
702

G
gdut-yy 已提交
703
Note, however, that this API is extremely sensitive to variations in the document text. If the document is modified by inserting a single newline between the `<html>` and the `<head>` tag, for example, the Text node that represents that newline becomes the first child of the first child, and the second child is the `<head>` element instead of the `<body>` element.
G
gdut-yy 已提交
704 705

To demonstrate this Node-based traversal API, here is a function that returns all of the text within an element or document:
G
gdut-yy 已提交
706
```js
G
gdut-yy 已提交
707 708 709 710 711 712 713 714 715 716 717 718 719 720
// Return the plain-text content of element e, recursing into child elements.
// This method works like the textContent property
function textContent(e) {
    let s = "";                        // Accumulate the text here
    for(let child = e.firstChild; child !== null; child = child.nextSibling) {
        let type = child.nodeType;
        if (type === 3) {              // If it is a Text node
            s += child.nodeValue;      // add the text content to our string.
        } else if (type === 1) {       // And if it is an Element node
            s += textContent(child);   // then recurse.
        }
    }
    return s;
}
G
gdut-yy 已提交
721
```
G
gdut-yy 已提交
722 723
This function is a demonstration only—in practice, you would simply write e.textContent to obtain the textual content of the element e.

G
gdut-yy 已提交
724
### 15.3.3 Attributes
G
gdut-yy 已提交
725
HTML elements consist of a tag name and a set of name/value pairs known as attributes. The `<a>` element that defines a hyperlink, for example, uses the value of its href attribute as the destination of the link.
G
gdut-yy 已提交
726 727 728 729

The Element class defines general getAttribute(), setAttribute(), hasAttribute(), and removeAttribute() methods for querying, setting, testing, and removing the attributes of an element. But the attribute values of HTML elements (for all standard attributes of standard HTML elements) are available as properties of the HTMLElement objects that represent those elements, and it is usually much easier to work with them as JavaScript properties than it is to call getAttribute() and related methods.

HTML ATTRIBUTES AS ELEMENT PROPERTIES
G
gdut-yy 已提交
730 731
The Element objects that represent the elements of an HTML document usually define read/write properties that mirror the HTML attributes of the elements. Element defines properties for the universal HTML attributes such as id, title, lang, and dir and event handler properties like onclick. Element-specific subtypes define attributes specific to those elements. To query the URL of an image, for example, you can use the src property of the HTMLElement that represents the `<img>` element:
```js
G
gdut-yy 已提交
732 733 734
let image = document.querySelector("#main_image");
let url = image.src;       // The src attribute is the URL of the image
image.id === "main_image"  // => true; we looked up the image by id
G
gdut-yy 已提交
735 736 737
```
Similarly, you might set the form-submission attributes of a `<form>` element with code like this:
```js
G
gdut-yy 已提交
738 739 740
let f = document.querySelector("form");      // First <form> in the document
f.action = "https://www.example.com/submit"; // Set the URL to submit it to.
f.method = "POST";                           // Set the HTTP request type.
G
gdut-yy 已提交
741 742
```
For some elements, such as the `<input>` element, some HTML attribute names map to differently named properties. The HTML value attribute of an `<input>`, for example, is mirrored by the JavaScript defaultValue property. The JavaScript value property of the `<input>` element contains the user’s current input, but changes to the value property do not affect the defaultValue property nor the value attribute.
G
gdut-yy 已提交
743 744 745

HTML attributes are not case sensitive, but JavaScript property names are. To convert an attribute name to the JavaScript property, write it in lowercase. If the attribute is more than one word long, however, put the first letter of each word after the first in uppercase: defaultChecked and tabIndex, for example. Event handler properties like onclick are an exception, however, and are written in lowercase.

G
gdut-yy 已提交
746
Some HTML attribute names are reserved words in JavaScript. For these, the general rule is to prefix the property name with “html”. The HTML for attribute (of the `<label>` element), for example, becomes the JavaScript htmlFor property. “class” is a reserved word in JavaScript, and the very important HTML class attribute is an exception to the rule: it becomes className in JavaScript code.
G
gdut-yy 已提交
747

G
gdut-yy 已提交
748
The properties that represent HTML attributes usually have string values. But when the attribute is a boolean or numeric value (the defaultChecked and maxLength attributes of an `<input>` element, for example), the properties are booleans or numbers instead of strings. Event handler attributes always have functions (or null) as their values.
G
gdut-yy 已提交
749 750 751 752 753 754 755

Note that this property-based API for getting and setting attribute values does not define any way to remove an attribute from an element. In particular, the delete operator cannot be used for this purpose. If you need to delete an attribute, use the removeAttribute() method.

THE CLASS ATTRIBUTE
The class attribute of an HTML element is a particularly important one. Its value is a space-separated list of CSS classes that apply to the element and affect how it is styled with CSS. Because class is a reserved word in JavaScript, the value of this attribute is available through the className property on Element objects. The className property can set and return the value of the class attribute as a string. But the class attribute is poorly named: its value is a list of CSS classes, not a single class, and it is common in client-side JavaScript programming to want to add and remove individual class names from this list rather than work with the list as a single string.

For this reason, Element objects define a classList property that allows you to treat the class attribute as a list. The value of the classList property is an iterable Array-like object. Although the name of the property is classList, it behaves more like a set of classes, and defines add(), remove(), contains(), and toggle() methods:
G
gdut-yy 已提交
756
```js
G
gdut-yy 已提交
757 758 759 760 761 762
// When we want to let the user know that we are busy, we display
// a spinner. To do this we have to remove the "hidden" class and add the
// "animated" class (assuming the stylesheets are configured correctly).
let spinner = document.querySelector("#spinner");
spinner.classList.remove("hidden");
spinner.classList.add("animated");
G
gdut-yy 已提交
763
```
G
gdut-yy 已提交
764 765 766 767 768 769
DATASET ATTRIBUTES
It is sometimes useful to attach additional information to HTML elements, typically when JavaScript code will be selecting those elements and manipulating them in some way. In HTML, any attribute whose name is lowercase and begins with the prefix “data-” is considered valid, and you can use them for any purpose. These “dataset attributes” will not affect the presentation of the elements on which they appear, and they define a standard way to attach additional data without compromising document validity.

In the DOM, Element objects have a dataset property that refers to an object that has properties that correspond to the data- attributes with their prefix removed. Thus, dataset.x would hold the value of the data-x attribute. Hyphenated attributes map to camelCase property names: the attribute data-section-number becomes the property dataset.sectionNumber.

Suppose an HTML document contains this text:
G
gdut-yy 已提交
770
```html
G
gdut-yy 已提交
771
<h2 id="title" data-section-number="16.1">Attributes</h2>
G
gdut-yy 已提交
772
```
G
gdut-yy 已提交
773
Then you could write JavaScript like this to access that section number:
G
gdut-yy 已提交
774
```js
G
gdut-yy 已提交
775
let number = document.querySelector("#title").dataset.sectionNumber;
G
gdut-yy 已提交
776
```
G
gdut-yy 已提交
777
### 15.3.4 Element Content
G
gdut-yy 已提交
778
Look again at the document tree pictured in Figure 15-1, and ask yourself what the “content” of the `<p>` element is. There are two ways we might answer this question:
G
gdut-yy 已提交
779

G
gdut-yy 已提交
780 781
- The content is the HTML string “This is a `<i>`simple`</i>` document”.
- The content is the plain-text string “This is a simple document”.
G
gdut-yy 已提交
782 783 784 785 786

Both of these are valid answers, and each answer is useful in its own way. The sections that follow explain how to work with the HTML representation and the plain-text representation of an element’s content.

ELEMENT CONTENT AS HTML
Reading the innerHTML property of an Element returns the content of that element as a string of markup. Setting this property on an element invokes the web browser’s parser and replaces the element’s current content with a parsed representation of the new string. You can test this out by opening the developer console and typing:
G
gdut-yy 已提交
787
```js
G
gdut-yy 已提交
788
document.body.innerHTML = "<h1>Oops</h1>";
G
gdut-yy 已提交
789
```
G
gdut-yy 已提交
790 791 792 793 794 795 796 797 798
You will see that the entire web page disappears and is replaced with the single heading, “Oops”. Web browsers are very good at parsing HTML, and setting innerHTML is usually fairly efficient. Note, however, that appending text to the innerHTML property with the += operator is not efficient because it requires both a serialization step to convert element content to a string and then a parsing step to convert the new string back into element content.

WARNING
When using these HTML APIs, it is very important that you never insert user input into the document. If you do this, you allow malicious users to inject their own scripts into your application. See “Cross-site scripting” for details.

The outerHTML property of an Element is like innerHTML except that its value includes the element itself. When you query outerHTML, the value includes the opening and closing tags of the element. And when you set outerHTML on an element, the new content replaces the element itself.

A related Element method is insertAdjacentHTML(), which allows you to insert a string of arbitrary HTML markup “adjacent” to the specified element. The markup is passed as the second argument to this method, and the precise meaning of “adjacent” depends on the value of the first argument. This first argument should be a string with one of the values “beforebegin,” “afterbegin,” “beforeend,” or “afterend.” These values correspond to insertion points that are illustrated in Figure 15-2.

G
gdut-yy 已提交
799 800 801
<Figures figure="15-2">Insertion points for insertAdjacentHTML()</Figures>

#### ELEMENT CONTENT AS PLAIN TEXT
G
gdut-yy 已提交
802
Sometimes you want to query the content of an element as plain text or to insert plain text into a document (without having to escape the angle brackets and ampersands used in HTML markup). The standard way to do this is with the textContent property:
G
gdut-yy 已提交
803
```js
G
gdut-yy 已提交
804 805 806
let para = document.querySelector("p"); // First <p> in the document
let text = para.textContent;            // Get the text of the paragraph
para.textContent = "Hello World!";      // Alter the text of the paragraph
G
gdut-yy 已提交
807
```
G
gdut-yy 已提交
808 809 810 811
The textContent property is defined by the Node class, so it works for Text nodes as well as Element nodes. For Element nodes, it finds and returns all text in all descendants of the element.

The Element class defines an innerText property that is similar to textContent. innerText has some unusual and complex behaviors, such as attempting to preserve table formatting. It is not well specified nor implemented compatibly between browsers, however, and should no longer be used.

G
gdut-yy 已提交
812 813
TEXT IN `<SCRIPT>` ELEMENTS
Inline `<script>` elements (i.e., those that do not have a src attribute) have a text property that you can use to retrieve their text. The content of a `<script>` element is never displayed by the browser, and the HTML parser ignores angle brackets and ampersands within a script. This makes a `<script>` element an ideal place to embed arbitrary textual data for use by your application. Simply set the type attribute of the element to some value (such as “text/x-custom-data”) that makes it clear that the script is not executable JavaScript code. If you do this, the JavaScript interpreter will ignore the script, but the element will exist in the document tree, and its text property will return the data to you.
G
gdut-yy 已提交
814

G
gdut-yy 已提交
815
### 15.3.5 Creating, Inserting, and Deleting Nodes
G
gdut-yy 已提交
816 817 818
We’ve seen how to query and alter document content using strings of HTML and of plain text. And we’ve also seen that we can traverse a Document to examine the individual Element and Text nodes that it is made of. It is also possible to alter a document at the level of individual nodes. The Document class defines methods for creating Element objects, and Element and Text objects have methods for inserting, deleting, and replacing nodes in the tree.

Create a new element with the createElement() method of the Document class and append strings of text or other elements to it with its append() and prepend() methods:
G
gdut-yy 已提交
819
```js
G
gdut-yy 已提交
820 821 822 823 824 825
let paragraph = document.createElement("p"); // Create an empty <p> element
let emphasis = document.createElement("em"); // Create an empty <em> element
emphasis.append("World");                    // Add text to the <em> element
paragraph.append("Hello ", emphasis, "!");   // Add text and <em> to <p>
paragraph.prepend("¡");                      // Add more text at start of <p>
paragraph.innerHTML                          // => "¡Hello <em>World</em>!"
G
gdut-yy 已提交
826
```
G
gdut-yy 已提交
827 828 829
append() and prepend() take any number of arguments, which can be Node objects or strings. String arguments are automatically converted to Text nodes. (You can create Text nodes explicitly with document.createTextNode(), but there is rarely any reason to do so.) append() adds the arguments to the element at the end of the child list. prepend() adds the arguments at the start of the child list.

If you want to insert an Element or Text node into the middle of the containing element’s child list, then neither append() or prepend() will work for you. In this case, you should obtain a reference to a sibling node and call before() to insert the new content before that sibling or after() to insert it after that sibling. For example:
G
gdut-yy 已提交
830
```js
G
gdut-yy 已提交
831 832 833 834 835
// Find the heading element with class="greetings"
let greetings = document.querySelector("h2.greetings");

// Now insert the new paragraph and a horizontal rule after that heading
greetings.after(paragraph, document.createElement("hr"));
G
gdut-yy 已提交
836
```
G
gdut-yy 已提交
837 838 839
Like append() and prepend(), after() and before() take any number of string and element arguments and insert them all into the document after converting strings to Text nodes. append() and prepend() are only defined on Element objects, but after() and before() work on both Element and Text nodes: you can use them to insert content relative to a Text node.

Note that elements can only be inserted at one spot in the document. If an element is already in the document and you insert it somewhere else, it will be moved to the new location, not copied:
G
gdut-yy 已提交
840
```js
G
gdut-yy 已提交
841 842 843
// We inserted the paragraph after this element, but now we
// move it so it appears before the element instead
greetings.before(paragraph);
G
gdut-yy 已提交
844
```
G
gdut-yy 已提交
845
If you do want to make a copy of an element, use the cloneNode() method, passing true to copy all of its content:
G
gdut-yy 已提交
846
```js
G
gdut-yy 已提交
847 848
// Make a copy of the paragraph and insert it after the greetings element
greetings.after(paragraph.cloneNode(true));
G
gdut-yy 已提交
849
```
G
gdut-yy 已提交
850
You can remove an Element or Text node from the document by calling its remove() method, or you can replace it by calling replaceWith() instead. remove() takes no arguments, and replaceWith() takes any number of strings and elements just like before() and after() do:
G
gdut-yy 已提交
851
```js
G
gdut-yy 已提交
852 853 854 855 856 857 858
// Remove the greetings element from the document and replace it with
// the paragraph element (moving the paragraph from its current location
// if it is already inserted into the document).
greetings.replaceWith(paragraph);

// And now remove the paragraph.
paragraph.remove();
G
gdut-yy 已提交
859
```
G
gdut-yy 已提交
860 861
The DOM API also defines an older generation of methods for inserting and removing content. appendChild(), insertBefore(), replaceChild(), and removeChild() are harder to use than the methods shown here and should never be needed.

G
gdut-yy 已提交
862
### 15.3.6 Example: Generating a Table of Contents
G
gdut-yy 已提交
863 864 865
Example 15-1 shows how to dynamically create a table of contents for a document. It demonstrates many of the document scripting techniques described in the previous sections. The example is well commented, and you should have no trouble following the code.

Example 15-1. Generating a table of contents with the DOM API
G
gdut-yy 已提交
866
```js
G
gdut-yy 已提交
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
/**
 * TOC.js: create a table of contents for a document.
 *
 * This script runs when the DOMContentLoaded event is fired and
 * automatically generates a table of contents for the document.
 * It does not define any global symbols so it should not conflict
 * with other scripts.
 *
 * When this script runs, it first looks for a document element with
 * an id of "TOC". If there is no such element it creates one at the
 * start of the document. Next, the function finds all <h2> through
 * <h6> tags, treats them as section titles, and creates a table of
 * contents within the TOC element. The function adds section numbers
 * to each section heading and wraps the headings in named anchors so
 * that the TOC can link to them. The generated anchors have names
 * that begin with "TOC", so you should avoid this prefix in your own
 * HTML.
 *
 * The entries in the generated TOC can be styled with CSS. All
 * entries have a class "TOCEntry". Entries also have a class that
 * corresponds to the level of the section heading. <h1> tags generate
 * entries of class "TOCLevel1", <h2> tags generate entries of class
 * "TOCLevel2", and so on. Section numbers inserted into headings have
 * class "TOCSectNum".
 *
 * You might use this script with a stylesheet like this:
 *
 *   #TOC { border: solid black 1px; margin: 10px; padding: 10px; }
 *   .TOCEntry { margin: 5px 0px; }
 *   .TOCEntry a { text-decoration: none; }
 *   .TOCLevel1 { font-size: 16pt; font-weight: bold; }
 *   .TOCLevel2 { font-size: 14pt; margin-left: .25in; }
 *   .TOCLevel3 { font-size: 12pt; margin-left: .5in; }
 *   .TOCSectNum:after { content: ": "; }
 *
 * To hide the section numbers, use this:
 *
 *   .TOCSectNum { display: none }
 **/
document.addEventListener("DOMContentLoaded", () => {
    // Find the TOC container element.
    // If there isn't one, create one at the start of the document.
    let toc = document.querySelector("#TOC");
    if (!toc) {
        toc = document.createElement("div");
        toc.id = "TOC";
        document.body.prepend(toc);
    }

    // Find all section heading elements. We're assuming here that the
    // document title uses <h1> and that sections within the document are
    // marked with <h2> through <h6>.
    let headings = document.querySelectorAll("h2,h3,h4,h5,h6");

    // Initialize an array that keeps track of section numbers.
    let sectionNumbers = [0,0,0,0,0];

    // Now loop through the section header elements we found.
    for(let heading of headings) {
        // Skip the heading if it is inside the TOC container.
        if (heading.parentNode === toc) {
            continue;
        }

        // Figure out what level heading it is.
        // Subtract 1 because <h2> is a level-1 heading.
        let level = parseInt(heading.tagName.charAt(1)) - 1;

        // Increment the section number for this heading level
        // and reset all lower heading level numbers to zero.
        sectionNumbers[level-1]++;
        for(let i = level; i < sectionNumbers.length; i++) {
            sectionNumbers[i] = 0;
        }

        // Now combine section numbers for all heading levels
        // to produce a section number like 2.3.1.
        let sectionNumber = sectionNumbers.slice(0, level).join(".");

        // Add the section number to the section header title.
        // We place the number in a <span> to make it styleable.
        let span = document.createElement("span");
        span.className = "TOCSectNum";
        span.textContent = sectionNumber;
        heading.prepend(span);

        // Wrap the heading in a named anchor so we can link to it.
        let anchor = document.createElement("a");
        let fragmentName = `TOC${sectionNumber}`;
        anchor.name = fragmentName;
        heading.before(anchor);    // Insert anchor before heading
        anchor.append(heading);    // and move heading inside anchor

        // Now create a link to this section.
        let link = document.createElement("a");
        link.href = `#${fragmentName}`;     // Link destination

        // Copy the heading text into the link. This is a safe use of
        // innerHTML because we are not inserting any untrusted strings.
        link.innerHTML = heading.innerHTML;

        // Place the link in a div that is styleable based on the level.
        let entry = document.createElement("div");
        entry.classList.add("TOCEntry", `TOCLevel${level}`);
        entry.append(link);

        // And add the div to the TOC container.
        toc.append(entry);
    }
});
G
gdut-yy 已提交
977
```
G
gdut-yy 已提交
978
## 15.4 Scripting CSS
G
gdut-yy 已提交
979 980 981 982
We’ve seen that JavaScript can control the logical structure and content of HTML documents. It can also control the visual appearance and layout of those documents by scripting CSS. The following subsections explain a few different techniques that JavaScript code can use to work with CSS.

This is a book about JavaScript, not about CSS, and this section assumes that you already have a working knowledge of how CSS is used to style HTML content. But it’s worth mentioning some of the CSS styles that are commonly scripted from JavaScript:

G
gdut-yy 已提交
983 984 985 986
- Setting the display style to “none” hides an element. You can later show the element by setting display to some other value.
- You can dynamically position elements by setting the position style to “absolute,” “relative,” or “fixed” and then setting the top and left styles to the desired coordinates. This is important when using JavaScript to display dynamic content like modal dialogues and tooltips.
- You can shift, scale, and rotate elements with the transform style.
- You can animate changes to other CSS styles with the transition style. These animations are handled automatically by the web browser and do not require JavaScript, but you can use JavaScript to initiate the animations.
G
gdut-yy 已提交
987

G
gdut-yy 已提交
988
### 15.4.1 CSS Classes
G
gdut-yy 已提交
989 990 991
The simplest way to use JavaScript to affect the styling of document content is to add and remove CSS class names from the class attribute of HTML tags. This is easy to do with the classList property of Element objects, as explained in “The class attribute”.

Suppose, for example, that your document’s stylesheet includes a definition for a “hidden” class:
G
gdut-yy 已提交
992
```css
G
gdut-yy 已提交
993 994 995
.hidden {
  display:none;
}
G
gdut-yy 已提交
996
```
G
gdut-yy 已提交
997
With this style defined, you can hide (and then show) an element with code like this:
G
gdut-yy 已提交
998
```js
G
gdut-yy 已提交
999 1000 1001 1002 1003 1004
// Assume that this "tooltip" element has class="hidden" in the HTML file.
// We can make it visible like this:
document.querySelector("#tooltip").classList.remove("hidden");

// And we can hide it again like this:
document.querySelector("#tooltip").classList.add("hidden");
G
gdut-yy 已提交
1005
```
G
gdut-yy 已提交
1006
### 15.4.2 Inline Styles
G
gdut-yy 已提交
1007 1008 1009
To continue with the preceding tooltip example, suppose that the document is structured with only a single tooltip element, and we want to dynamically position it before displaying it. In general, we can’t create a different stylesheet class for each possible position of the tooltip, so the classList property won’t help us with positioning.

In this case, we need to script the style attribute of the tooltip element to set inline styles that are specific to that one element. The DOM defines a style property on all Element objects that correspond to the style attribute. Unlike most such properties, however, the style property is not a string. Instead, it is a CSSStyleDeclaration object: a parsed representation of the CSS styles that appear in textual form in the style attribute. To display and set the position of our hypothetical tooltip with JavaScript, we might use code like this:
G
gdut-yy 已提交
1010
```js
G
gdut-yy 已提交
1011 1012 1013 1014 1015 1016
function displayAt(tooltip, x, y) {
    tooltip.style.display = "block";
    tooltip.style.position = "absolute";
    tooltip.style.left = `${x}px`;
    tooltip.style.top = `${y}px`;
}
G
gdut-yy 已提交
1017
```
G
gdut-yy 已提交
1018 1019 1020 1021
NAMING CONVENTIONS: CSS PROPERTIES IN JAVASCRIPT
Many CSS style properties, such as font-size, contain hyphens in their names. In JavaScript, a hyphen is interpreted as a minus sign and is not allowed in property names or other identifiers. Therefore, the names of the properties of the CSSStyleDeclaration object are slightly different from the names of actual CSS properties. If a CSS property name contains one or more hyphens, the CSSStyleDeclaration property name is formed by removing the hyphens and capitalizing the letter immediately following each hyphen. The CSS property border-left-width is accessed through the JavaScript borderLeftWidth property, for example, and the CSS font-family property is written as fontFamily in JavaScript.

When working with the style properties of the CSSStyleDeclaration object, remember that all values must be specified as strings. In a stylesheet or style attribute, you can write:
G
gdut-yy 已提交
1022
```css
G
gdut-yy 已提交
1023
display: block; font-family: sans-serif; background-color: #ffffff;
G
gdut-yy 已提交
1024
```
G
gdut-yy 已提交
1025
To accomplish the same thing for an element e with JavaScript, you have to quote all of the values:
G
gdut-yy 已提交
1026
```js
G
gdut-yy 已提交
1027 1028 1029
e.style.display = "block";
e.style.fontFamily = "sans-serif";
e.style.backgroundColor = "#ffffff";
G
gdut-yy 已提交
1030
```
G
gdut-yy 已提交
1031 1032 1033
Note that the semicolons go outside the strings. These are just normal JavaScript semicolons; the semicolons you use in CSS stylesheets are not required as part of the string values you set with JavaScript.

Furthermore, remember that many CSS properties require units such as “px” for pixels or “pt” for points. Thus, it is not correct to set the marginLeft property like this:
G
gdut-yy 已提交
1034
```js
G
gdut-yy 已提交
1035 1036
e.style.marginLeft = 300;    // Incorrect: this is a number, not a string
e.style.marginLeft = "300";  // Incorrect: the units are missing
G
gdut-yy 已提交
1037
```
G
gdut-yy 已提交
1038
Units are required when setting style properties in JavaScript, just as they are when setting style properties in stylesheets. The correct way to set the value of the marginLeft property of an element e to 300 pixels is:
G
gdut-yy 已提交
1039
```js
G
gdut-yy 已提交
1040
e.style.marginLeft = "300px";
G
gdut-yy 已提交
1041
```
G
gdut-yy 已提交
1042
If you want to set a CSS property to a computed value, be sure to append the units at the end of the computation:
G
gdut-yy 已提交
1043
```js
G
gdut-yy 已提交
1044
e.style.left = `${x0 + left_border + left_padding}px`;
G
gdut-yy 已提交
1045
```
G
gdut-yy 已提交
1046
Recall that some CSS properties, such as margin, are shortcuts for other properties, such as margin-top, margin-right, margin-bottom, and margin-left. The CSSStyleDeclaration object has properties that correspond to these shortcut properties. For example, you might set the margin property like this:
G
gdut-yy 已提交
1047
```js
G
gdut-yy 已提交
1048
e.style.margin = `${top}px ${right}px ${bottom}px ${left}px`;
G
gdut-yy 已提交
1049
```
G
gdut-yy 已提交
1050
Sometimes, you may find it easier to set or query the inline style of an element as a single string value rather than as a CSSStyleDeclaration object. To do that, you can use the Element getAttribute() and setAttribute() methods, or you can use the cssText property of the CSSStyleDeclaration object:
G
gdut-yy 已提交
1051
```js
G
gdut-yy 已提交
1052 1053 1054 1055 1056
// Copy the inline styles of element e to element f:
f.setAttribute("style", e.getAttribute("style"));

// Or do it like this:
f.style.cssText = e.style.cssText;
G
gdut-yy 已提交
1057
```
G
gdut-yy 已提交
1058 1059
When querying the style property of an element, keep in mind that it represents only the inline styles of an element and that most styles for most elements are specified in stylesheets rather than inline. Furthermore, the values you obtain when querying the style property will use whatever units and whatever shortcut property format is actually used on the HTML attribute, and your code may have to do some sophisticated parsing to interpret them. In general, if you want to query the styles of an element, you probably want the computed style, which is discussed next.

G
gdut-yy 已提交
1060
### 15.4.3 Computed Styles
G
gdut-yy 已提交
1061 1062 1063
The computed style for an element is the set of property values that the browser derives (or computes) from the element’s inline style plus all applicable style rules in all stylesheets: it is the set of properties actually used to display the element. Like inline styles, computed styles are represented with a CSSStyleDeclaration object. Unlike inline styles, however, computed styles are read-only. You can’t set these styles, but the computed CSSStyleDeclaration object for an element lets you determine what style property values the browser used when rendering that element.

Obtain the computed style for an element with the getComputedStyle() method of the Window object. The first argument to this method is the element whose computed style is desired. The optional second argument is used to specify a CSS pseudoelement, such as “::before” or “::after”:
G
gdut-yy 已提交
1064
```js
G
gdut-yy 已提交
1065 1066 1067
let title = document.querySelector("#section1title");
let styles = window.getComputedStyle(title);
let beforeStyles = window.getComputedStyle(title, "::before");
G
gdut-yy 已提交
1068
```
G
gdut-yy 已提交
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
The return value of getComputedStyle() is a CSSStyleDeclaration object that represents all the styles that apply to the specified element (or pseudoelement). There are a number of important differences between a CSSStyleDeclaration object that represents inline styles and one that represents computed styles:

Computed style properties are read-only.

Computed style properties are absolute: relative units like percentages and points are converted to absolute values. Any property that specifies a size (such as a margin size or a font size) will have a value measured in pixels. This value will be a string with a “px” suffix, so you’ll still need to parse it, but you won’t have to worry about parsing or converting other units. Properties whose values are colors will be returned in “rgb()” or “rgba()” format.

Shortcut properties are not computed—only the fundamental properties that they are based on are. Don’t query the margin property, for example, but use marginLeft, marginTop, and so on. Similarly, don’t query border or even borderWidth. Instead, use borderLeftWidth, borderTopWidth, and so on.

The cssText property of the computed style is undefined.

A CSSStyleDeclaration object returned by getComputedStyle() generally contains much more information about an element than the CSSStyleDeclaration obtained from the inline style property of that element. But computed styles can be tricky, and querying them does not always provide the information you might expect. Consider the font-family attribute: it accepts a comma-separated list of desired font families for cross-platform portability. When you query the fontFamily property of a computed style, you’re simply getting the value of the most specific font-family style that applies to the element. This may return a value such as “arial,helvetica,sans-serif,” which does not tell you which typeface is actually in use. Similarly, if an element is not absolutely positioned, attempting to query its position and size through the top and left properties of its computed style often returns the value auto. This is a perfectly legal CSS value, but it is probably not what you were looking for.

Although CSS can be used to precisely specify the position and size of document elements, querying the computed style of an element is not the preferred way to determine the element’s size and position. See §15.5.2 for a simpler, portable alternative.

G
gdut-yy 已提交
1083
### 15.4.4 Scripting Stylesheets
G
gdut-yy 已提交
1084
In addition to scripting class attributes and inline styles, JavaScript can also manipulate stylesheets themselves. Stylesheets are associated with an HTML document with a `<style>` tag or with a `<link rel="stylesheet">` tag. Both of these are regular HTML tags, so you can give them both id attributes and then look them up with document.querySelector().
G
gdut-yy 已提交
1085

G
gdut-yy 已提交
1086 1087
The Element objects for both `<style>` and `<link>` tags have a disabled property that you can use to disable the entire stylesheet. You might use it with code like this:
```js
G
gdut-yy 已提交
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
// This function switches between the "light" and "dark" themes
function toggleTheme() {
    let lightTheme = document.querySelector("#light-theme");
    let darkTheme = document.querySelector("#dark-theme");
    if (darkTheme.disabled) {          // Currently light, switch to dark
        lightTheme.disabled = true;
        darkTheme.disabled = false;
    } else {                           // Currently dark, switch to light
        lightTheme.disabled = false;
        darkTheme.disabled = true;
    }
}
G
gdut-yy 已提交
1100
```
G
gdut-yy 已提交
1101
Another simple way to script stylesheets is to insert new ones into the document using DOM manipulation techniques we’ve already seen. For example:
G
gdut-yy 已提交
1102
```js
G
gdut-yy 已提交
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
function setTheme(name) {
    // Create a new <link rel="stylesheet"> element to load the named stylesheet
    let link = document.createElement("link");
    link.id = "theme";
    link.rel = "stylesheet";
    link.href = `themes/${name}.css`;

    // Look for an existing link with id "theme"
    let currentTheme = document.querySelector("#theme");
    if (currentTheme) {
        // If there is an existing theme, replace it with the new one.
        currentTheme.replaceWith(link);
    } else {
        // Otherwise, just insert the link to the theme stylesheet.
        document.head.append(link);
    }
}
G
gdut-yy 已提交
1120 1121 1122
```
Less subtly, you can also just insert a string of HTML containing a `<style>` tag into your document. This is a fun trick, for example:
```js
G
gdut-yy 已提交
1123 1124 1125 1126
document.head.insertAdjacentHTML(
    "beforeend",
    "<style>body{transform:rotate(180deg)}</style>"
);
G
gdut-yy 已提交
1127
```
G
gdut-yy 已提交
1128 1129
Browsers define an API that allows JavaScript to look inside stylesheets to query, modify, insert, and delete style rules in that stylesheet. This API is so specialized that it is not documented here. You can read about it on MDN by searching for “CSSStyleSheet” and “CSS Object Model.”

G
gdut-yy 已提交
1130
### 15.4.5 CSS Animations and Events
G
gdut-yy 已提交
1131
Suppose you have the following two CSS classes defined in a stylesheet:
G
gdut-yy 已提交
1132
```css
G
gdut-yy 已提交
1133 1134
.transparent { opacity: 0; }
.fadeable { transition: opacity .5s ease-in }
G
gdut-yy 已提交
1135
```
G
gdut-yy 已提交
1136 1137 1138
If you apply the first style to an element, it will be fully transparent and therefore invisible. But if you apply the second style that tells the browser that when the opacity of the element changes, that change should be animated over a period of 0.5 seconds, “ease-in” specifies that the opacity change animation should start off slow and then accelerate.

Now suppose that your HTML document contains an element with the “fadeable” class:
G
gdut-yy 已提交
1139
```js
G
gdut-yy 已提交
1140
<div id="subscribe" class="fadeable notification">...</div>
G
gdut-yy 已提交
1141
```
G
gdut-yy 已提交
1142
In JavaScript, you can add the “transparent” class:
G
gdut-yy 已提交
1143
```js
G
gdut-yy 已提交
1144
document.querySelector("#subscribe").classList.add("transparent");
G
gdut-yy 已提交
1145
```
G
gdut-yy 已提交
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
This element is configured to animate opacity changes. Adding the “transparent” class changes the opacity and triggers an animate: the browser “fades out” the element so that it becomes fully transparent over the period of half a second.

This works in reverse as well: if you remove the “transparent” class of a “fadeable” element, that is also an opacity change, and the element fades back in and becomes visible again.

JavaScript does not have to do any work to make these animations happen: they are a pure CSS effect. But JavaScript can be used to trigger them.

JavaScript can also be used to monitor the progress of a CSS transition because the web browser fires events at the start and end of a transition. The “transitionrun” event is dispatched when the transition is first triggered. This may happen before any visual changes begin, when the transition-delay style has been specified. Once the visual changes begin a “transitionstart” event is dispatched, and when the animation is complete, a “transitionend” event is dispatched. The target of all these events is the element being animated, of course. The event object passed to handlers for these events is a TransitionEvent object. It has a propertyName property that specifies the CSS property being animated and an elapsedTime property that for “transitionend” events specifies how many seconds have passed since the “transitionstart” event.

In addition to transitions, CSS also supports a more complex form of animation known simply as “CSS Animations.” These use CSS properties such as animation-name and animation-duration and a special @keyframes rule to define animation details. Details of how CSS animations work are beyond the scope of this book, but once again, if you define all of the animation properties on a CSS class, then you can use JavaScript to trigger the animation simply by adding the class to the element that is to be animated.

And like CSS transitions, CSS animations also trigger events that your JavaScript code can listen form. “animationstart” is dispatched when the animation starts, and “animationend” is dispatched when it is complete. If the animation repeats more than once, then an “animationiteration” event is dispatched after each repetition except the last. The event target is the animated element, and the event object passed to handler functions is an AnimationEvent object. These events include an animationName property that specifies the animation-name property that defines the animation and an elapsedTime property that specifies how many seconds have passed since the animation started.

G
gdut-yy 已提交
1158
## 15.5 Document Geometry and Scrolling
G
gdut-yy 已提交
1159 1160 1161 1162
In this chapter so far, we have thought about documents as abstract trees of elements and text nodes. But when a browser renders a document within a window, it creates a visual representation of the document in which each element has a position and a size. Often, web applications can treat documents as trees of elements and never have to think about how those elements are rendered on screen. Sometimes, however, it is necessary to determine the precise geometry of an element. If, for example, you want to use CSS to dynamically position an element (such as a tooltip) next to some ordinary browser-positioned element, you need to be able to determine the location of that element.

The following subsections explain how you can go back and forth between the abstract, tree-based model of a document and the geometrical, coordinate-based view of the document as it is laid out in a browser window.

G
gdut-yy 已提交
1163
### 15.5.1 Document Coordinates and Viewport Coordinates
G
gdut-yy 已提交
1164
The position of a document element is measured in CSS pixels, with the x coordinate increasing to the right and the y coordinate increasing as we go down. There are two different points we can use as the coordinate system origin, however: the x and y coordinates of an element can be relative to the top-left corner of the document or relative to the top-left corner of the viewport in which the document is displayed. In top-level windows and tabs, the “viewport” is the portion of the browser that actually displays document content: it excludes browser “chrome” such as menus, toolbars, and tabs. For documents displayed in `<iframe>` tags, it is the iframe element in the DOM that defines the viewport for the nested document. In either case, when we talk about the position of an element, we must be clear whether we are using document coordinates or viewport coordinates. (Note that viewport coordinates are sometimes called “window coordinates.”)
G
gdut-yy 已提交
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178

If the document is smaller than the viewport, or if it has not been scrolled, the upper-left corner of the document is in the upper-left corner of the viewport and the document and viewport coordinate systems are the same. In general, however, to convert between the two coordinate systems, we must add or subtract the scroll offsets. If an element has a y coordinate of 200 pixels in document coordinates, for example, and if the user has scrolled down by 75 pixels, then that element has a y coordinate of 125 pixels in viewport coordinates. Similarly, if an element has an x coordinate of 400 in viewport coordinates after the user has scrolled the viewport 200 pixels horizontally, then the element’s x coordinate in document coordinates is 600.

If we use the mental model of printed paper documents, it is logical to assume that every element in a document must have a unique position in document coordinates, regardless of how much the user has scrolled the document. That is an appealing property of paper documents, and it applies for simple web documents, but in general, document coordinates don’t really work on the web. The problem is that the CSS overflow property allows elements within a document to contain more content than it can display. Elements can have their own scrollbars and serve as viewports for the content they contain. The fact that the web allows scrolling elements within a scrolling document means that it is simply not possible to describe the position of an element within the document using a single (x,y) point.

Because document coordinates don’t really work, client-side JavaScript tends to use viewport coordinates. The getBoundingClientRect() and elementFromPoint() methods described next use viewport coordinates, for example, and the clientX and clientY properties of mouse and pointer event objects also use this coordinate system.

When you explicitly position an element using CSS position:fixed, the top and left properties are interpreted in viewport coordinates. If you use position:relative, the element is positioned relative to where it would have been if it didn’t have the position property set. If you use position:absolute, then top and left are relative to the document or to the nearest containing positioned element. This means, for example, that an absolutely positioned element inside a relatively positioned element is positioned relative to the container element, not relative to the overall document. It is sometimes very useful to create a relatively positioned container with top and left set to 0 (so the container is laid out normally) in order to establish a new coordinate system origin for the absolutely positioned elements it contains. We might refer to this new coordinate system as “container coordinates” to distinguish it from document coordinates and viewport coordinates.

CSS PIXELS
If, like me, you are old enough to remember computer monitors with resolutions of 1024 × 768 and touch-screen phones with resolutions of 320 × 480, then you may still think that the word “pixel” refers to a single “picture element” in hardware. Today’s 4K monitors and “retina” displays have such high resolution that software pixels have been decoupled from hardware pixels. A CSS pixel—and therefore a client-side JavaScript pixel—may in fact consist of multiple device pixels. The devicePixelRatio property of the Window object specifies how many device pixels are used for each software pixel. A “dpr” of 2, for example, means that each software pixel is actually a 2 × 2 grid of hardware pixels. The devicePixelRatio value depends on the physical resolution of your hardware, on settings in your operating system, and on the zoom level in your browser.

devicePixelRatio does not have to be an integer. If you are using a CSS font size of “12px” and the device pixel ratio is 2.5, then the actual font size, in device pixels, is 30. Because the pixel values we use in CSS no longer correspond directly to individual pixels on the screen, pixel coordinates no longer need to be integers. If the devicePixelRatio is 3, then a coordinate of 3.33 makes perfect sense. And if the ratio is actually 2, then a coordinate of 3.33 will just be rounded up to 3.5.

G
gdut-yy 已提交
1179
### 15.5.2 Querying the Geometry of an Element
G
gdut-yy 已提交
1180 1181
You can determine the size (including CSS border and padding, but not the margin) and position (in viewport coordinates) of an element by calling its getBoundingClientRect() method. It takes no arguments and returns an object with properties left, right, top, bottom, width, and height. The left and top properties give the x and y coordinates of the upper-left corner of the element, and the right and bottom properties give the coordinates of the lower-right corner. The differences between these values are the width and height properties.

G
gdut-yy 已提交
1182
Block elements, such as images, paragraphs, and `<div>` elements are always rectangular when laid out by the browser. Inline elements, such as `<span>`, `<code>`, and `<b>` elements, however, may span multiple lines and may therefore consist of multiple rectangles. Imagine, for example, some text within `<em>` and `</em>` tags that happens to be displayed so that it wraps across two lines. Its rectangles consist of the end of the first line and beginning of the second line. If you call getBoundingClientRect() on this element, the bounding rectangle would include the entire width of both lines. If you want to query the individual rectangles of inline elements, call the getClientRects() method to obtain a read-only, array-like object whose elements are rectangle objects like those returned by getBoundingClientRect().
G
gdut-yy 已提交
1183

G
gdut-yy 已提交
1184
### 15.5.3 Determining the Element at a Point
G
gdut-yy 已提交
1185 1186
The getBoundingClientRect() method allows us to determine the current position of an element in a viewport. Sometimes we want to go in the other direction and determine which element is at a given location in the viewport. You can determine this with the elementFromPoint() method of the Document object. Call this method with the x and y coordinates of a point (using viewport coordinates, not document coordinates: the clientX and clientY coordinates of a mouse event work, for example). elementFromPoint() returns an Element object that is at the specified position. The hit detection algorithm for selecting the element is not precisely specified, but the intent of this method is that it returns the innermost (most deeply nested) and uppermost (highest CSS z-index attribute) element at that point.

G
gdut-yy 已提交
1187
### 15.5.4 Scrolling
G
gdut-yy 已提交
1188
The scrollTo() method of the Window object takes the x and y coordinates of a point (in document coordinates) and sets these as the scrollbar offsets. That is, it scrolls the window so that the specified point is in the upper-left corner of the viewport. If you specify a point that is too close to the bottom or too close to the right edge of the document, the browser will move it as close as possible to the upper-left corner but won’t be able to get it all the way there. The following code scrolls the browser so that the bottom-most page of the document is visible:
G
gdut-yy 已提交
1189
```js
G
gdut-yy 已提交
1190 1191 1192 1193 1194
// Get the heights of the document and viewport.
let documentHeight = document.documentElement.offsetHeight;
let viewportHeight = window.innerHeight;
// And scroll so the last "page" shows in the viewport
window.scrollTo(0, documentHeight - viewportHeight);
G
gdut-yy 已提交
1195
```