WHAT’S IN THIS CHAPTER?
➤➤ Using the <script> element
➤➤ Comparing inline and external scripts
➤➤ Examining how document modes affect JavaScript
➤➤ Preparing for JavaScript-disabled experiences
The introduction of JavaScript into web pages immediately ran into the web’s predominant language, HTML. As part of its original work on JavaScript, Netscape tried to figure out how to make JavaScript coexist in HTML pages without breaking those pages’ rendering in other browsers. Through trial, error, and controversy, several decisions were finally made and agreed upon to bring universal scripting support to the web. Much of the work done in these early days of the web has survived and become formalized in the HTML specification.
THE <SCRIPT> ELEMENT
The primary method of inserting JavaScript into an HTML page is via the <script> element.This element was created by Netscape and first implemented in Netscape Navigator 2. It was later added to the formal HTML specification. There are six attributes for the <script> element:
➤➤ async—Optional. Indicates that the script should begin downloading immediately but should not prevent other actions on the page such as downloading resources or waiting for other scripts to load. Valid only for external script files.
➤➤ charset—Optional. The character set of the code specified using the src attribute. This attribute is rarely used because most browsers don’t honor its value.
➤➤ crossorigin—Optional. Configures the CORS settings for the associated request; by default, CORS is not used at all. crossorigin="anonymous" will configure the request for the file to not have the credentials flag set. crossorigin="use-credentials" will set the credentials flag, meaning the outgoing request will include credentials.
➤➤ defer—Optional. Indicates that the execution of the script can safely be deferred until after the document’s content has been completely parsed and displayed. Valid only for external scripts. Internet Explorer 7 and earlier also allow for inline scripts.
➤➤ integrity—Optional. Allows for verification of Subresource Integrity (SRI) by checking the retrieved resource against a provided cryptographic signature. If the signature of the retrieved resource does not match that specified by this attribute, the page will error and the script will not execute. This is useful for ensuring that a Content Delivery Network (CDN) is not serving malicious payloads.
➤➤ language—Deprecated. Originally indicated the scripting language being used by the code block (such as "JavaScript", "JavaScript1.2", or "VBScript"). Most browsers ignore this attribute; it should not be used.
➤➤ src—Optional. Indicates an external file that contains code to be executed.
➤➤ type—Optional. Replaces language; indicates the content type (also called MIME type) of the scripting language being used by the code block. Traditionally, this value has always been "text/javascript", though both "text/javascript" and "text/ecmascript" are deprecated. JavaScript files are typically served with the "application/x-javascript" MIME type even though setting this in the type attribute may cause the script to be ignored. Other values that work in non–Internet Explorer browsers are "application/ javascript" and "application/ecmascript". If the value is module, the code is treated as an ES6 module and only then is eligible to use the import and export keywords.
There are two ways to use the <script> element: embed JavaScript code directly into the page or include JavaScript from an external file.
To include inline JavaScript code, place JavaScript code inside the <script> element directly, as follows:
<script>
function sayHi() {
console.log("Hi!");
}
</script>
The JavaScript code contained inside a <script> element is interpreted from top to bottom. In the case of this example, a function definition is interpreted and stored inside the interpreter environment. The rest of the page content is not loaded and/or displayed until after all of the code inside the <script> element has been evaluated.
When using inline JavaScript code, keep in mind that you cannot have the string "</script>" anywhere in your code. For example, the following code causes an error when loaded into a browser:
<script>
function sayScript() {
console.log("</script>");
}
</script>
Because of the way that inline scripts are parsed, the browser sees the string "</script>" as if it were the closing </script> tag. This problem can be avoided easily by escaping the "/" character, as in this example:
script>
function sayScript() {
console.log("<\/script>");
}
</script>
The changes to this code make it acceptable to browsers and won’t cause any errors. To include JavaScript from an external file, the src attribute is required. The value of src is a URL linked to a file containing JavaScript code, like this:
<script src="example.js"></script>
In this example, an external file named example.js is loaded into the page. The file itself need only contain the JavaScript code that would occur between the opening <script> and closing </script> tags. As with inline JavaScript code, processing of the page is halted while the external file is interpreted. (There is also some time taken to download the file.) In XHTML documents, you can omit the closing tag, as in this example:
<script src="example.js"/>
This syntax should not be used in HTML documents because it is invalid HTML and won’t be handled properly by some browsers, most notably Internet Explorer
NOTE By convention, external JavaScript files have a .js extension. This is not a requirement because browsers do not check the file extension of included JavaScript files. This leaves open the possibility of dynamically generating JavaScript code using a server-side scripting language, or for in-browser transpilation into JavaScript from a JavaScript extension language such as
TypeScript or React’s JSX. Keep in mind, though, that servers often use the file extension to determine the correct MIME type to apply to the response. If you don’t use a .js extension, double-check that your server is returning the correct MIME type.
It’s important to note that a <script> element using the src attribute should not include additional JavaScript code between the <script> and </script> tags. If both are provided, the script file is downloaded and executed while the inline code is ignored.
One of the most powerful and most controversial parts of the <script> element is its ability to include JavaScript files from outside domains. Much like an <img> element, the <script> element’s src attribute may be set to a full URL that exists outside the domain on which the HTML page exists, as in this example:
<script src="http://www.somewhere.com/afile.js"></script>
When the browser goes to resolve this resource, it will send a GET request to the path specified in the src attribute to retrieve the resource—presumably a JavaScript file. This initial request is not subject to the browser’s cross-origin restrictions, but any JavaScript returned and executed will be. Of course, this request is still subject to the HTTP/HTTPS protocol of the parent page.
Code from an external domain will be loaded and interpreted as if it were part of the page that is loading it. This capability allows you to serve up JavaScript from various domains, if necessary. Be careful, however, if you are referencing JavaScript files located on a server that you don’t control. A malicious programmer could, at any time, replace the file. When including JavaScript files from a different domain, make sure you are the domain owner or the domain is owned by a trusted source.
The <script> tag’s integrity attribute gives you a tool to defend against this; however, it has limited browser support.
Regardless of how the code is included, the <script> elements are interpreted in the order in which they appear in the page so long as the defer and async attributes are not present. The first <script> element’s code must be completely interpreted before thesecond <script> element begins interpretation, the second must be completed before the third, and so on.
Tag Placement
Traditionally, all <script> elements were placed within the <head> element on a page, as in this example:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script src="example1.js"></script>
<script src="example2.js"></script>
</head>
<body>
<!-- content here -->
</body>
</html>
The main purpose of this format was to keep external file references, both CSS files and JavaScript files, in the same area. However, including all JavaScript files in the <head> of a document means that all of the JavaScript code must be downloaded, parsed, and interpreted before the page begins rendering (rendering begins when the browser receives the opening <body> tag). For pages that require a lot of JavaScript code, this can cause a noticeable delay in page rendering, during which time the browser will be completely blank. For this reason, modern web applications typically include all JavaScript references in the <body> element, after the page content, as shown in this example:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
</head>
<body>
<!-- content here -->
<script src="example1.js"></script>
<script src="example2.js"></script>
</body>
</html>
Using this approach, the page is completely rendered in the browser before the JavaScript code is processed. The resulting user experience is perceived as faster because the amount of time spent on a blank browser window is reduced.
Deferred Scripts
HTML 4.01 defines an attribute named defer for the <script> element. The purpose of defer is to indicate that a script won’t be changing the structure of the page as it executes. As such, the script can be run safely after the entire page has been parsed. Setting the defer attribute on a <script> element signals to the browser that download should begin immediately but execution should be deferred:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script defer src="example1.js"></script>
<script defer src="example2.js"></script>
</head>
<body>
<!-- content here -->
</body>
</html>
Even though the <script> elements in this example are included in the document <head>, they will not be executed until after the browser has received the closing </html> tag. The HTML5 specification indicates that scripts will be executed in the order in which they appear, so the first deferred script executes before the second deferred script, and both will execute before the DOMContentLoaded event (see the chapter “Events” for more information). In reality, though, deferred scripts don’t always
execute in order or before the DOMContentLoaded event, so it’s best to include just one when possible.
As mentioned previously, the defer attribute is supported only for external script files. This was a clarification made in HTML5, so browsers that support the HTML5 implementation will ignore defer when set on an inline script. Internet Explorer 4–7 all exhibit the old behavior, while Internet Explorer 8 and above support the HTML5 behavior.
Support for the defer attribute was added beginning with Internet Explorer 4, Firefox 3.5, Safari 5, and Chrome 7. All other browsers simply ignore this attribute and treat the script as it normally would. For this reason, it’s still best to put deferred scripts at the bottom of the page.
NOTE For XHTML documents, specify the defer attribute as defer="defer".
Asynchronous Scripts
HTML5 introduces the async attribute for <script> elements. The async attribute is similar to defer in that it changes the way the script is processed. Also similar to defer, async applies only to external scripts and signals the browser to begin downloading the file immediately. Unlike defer, scripts marked as async are not guaranteed to execute in the order in which they are specified. For example:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script async src="example1.js"></script>
<script async src="example2.js"></script>
</head>
<body>
<!-- content here -->
</body>
</html>
In this code, the second script file might execute before the first, so it’s important that there are no dependencies between the two. The purpose of specifying an async script is to indicate that the page need not wait for the script to be downloaded and executed before continuing to load, and it also need not wait for another script to load and execute before it can do the same. Because of this, it’s recommended that asynchronous scripts not modify the DOM as they are loading.
Asynchronous scripts are guaranteed to execute before the page’s load event and may execute before or after DOMContentLoaded (see the “Events” chapter for details). Firefox 3.6, Safari 5, and Chrome 7 support asynchronous scripts. Using async scripts also confers to your page the implicit assumption that you do not intend to use document.write—but good web development practices dictate that you shouldn't be using it anyway.
NOTE For XHTML documents, specify the async attribute as async="async"
Dynamic Script Loading
You are not limited to using static <script> tags to retrieve resources. Because JavaScript is able to use the DOM API, you are more than welcome to add script elements, which will, in turn, load the resources they specify. This can be done by creating script elements and attaching them to the DOM:
let script = document.createElement('script');
script.src = 'gibberish.js';
document.head.appendChild(script);
Of course, this request will not be generated until the HTMLElement is attached to the DOM, and therefore not until this script itself runs. By default, scripts that are created in this fashion are async. This can be problematic, however, as all browsers support createElement but not all support async script requests. Therefore, to unify the dynamic script loading behavior, you can explicitly mark the tag as synchronous:
let script = document.createElement('script');
script.src = 'gibberish.js';
script.async = false;
document.head.appendChild(script);
Resources fetched in this fashion will be hidden from browser preloaders. This will severely injure their priority in the resource fetching queue. Depending on how your application works and how it is used, this can severely damage performance. To inform preloaders of the existence of these dynamically requested files, you can explicitly declare them in the document head:
<link rel="subresource" href="gibberish.js">
Changes in XHTML
Extensible HyperText Markup Language, or XHTML, is a reformulation of HTML as an application of XML. Unlike in HTML, where the type attribute is unneeded when using JavaScript, in XHTML, the <script> element requires that you specify the type attribute as text/javascript.
The rules for writing code in XHTML are stricter than those for HTML, which affects the <script> element when using embedded JavaScript code. Although valid in HTML, the following code block is invalid in XHTML:
<script type="text/javascript">
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
</script>
In HTML, the <script> element has special rules governing how its contents should be parsed; in XHTML, these special rules don’t apply. This means that the less-than symbol (<) in the statement a < b is interpreted as the beginning of a tag, which causes a syntax error because a less-than symbol must not be followed by a space.
There are two options for fixing the XHTML syntax error. The first is to replace all occurrences of the less-than symbol (<) with its HTML entity (<). The resulting code looks like this:
<script type="text/javascript">
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
</script>
This code will now run in an XHTML page; however, the code is slightly less readable. Fortunately, there is another approach.
The second option for turning this code into a valid XHTML version is to wrap the JavaScript code in a CDATA section. In XHTML (and XML), CDATA sections are used to indicate areas of the document that contain free-form text not intended to be parsed. This enables you to use any character, including the less-than symbol, without incurring a syntax error. The format is as follows:
<script type="text/javascript"><![CDATA[
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
]]></script>
In XHTML-compliant web browsers, this solves the problem. However, many browsers are still not XHTML-compliant and don’t support the CDATA section. To work around this, the CDATA markup must be offset by JavaScript comments:
<script type="text/javascript">
//<![CDATA[
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
//]]>
</script>
This format works in all modern browsers. Though a little bit of a hack, it validates as XHTML and degrades gracefully for pre-XHTML browsers.
NOTE XHTML mode is triggered when a page specifies its MIME type as "application/xhtml+xml". Not all browsers officially support XHTML served in this manner
Deprecated Syntax
Since Netscape 2 was released in 1995, all browsers have used JavaScript as their default programming language. The type attribute uses a MIME type string to identify the contents of <script>, but MIME types are not standardized across browsers. Even though browsers default to JavaScript, in some cases an invalid or unrecognized MIME type value for the type attribute will cause some browsers to skip execution of the associated code. Therefore, unless you are using XHTML or the <script> tag requests or wraps non-JavaScript, the best practice is to not specify a type attribute at all.
When the <script> element was originally introduced, it marked a departure from traditional HTML parsing. Special rules needed to be applied within this element, and that caused problems for browsers that didn’t support JavaScript (the most notable being Mosaic). Nonsupporting browsers would output the contents of the <script> element onto the page, effectively ruining the page’s appearance. Netscape worked with Mosaic to come up with a solution that would hide embedded JavaScript code from browsers that didn’t support it. The final solution was to enclose the script code in an HTML comment, like this:
<script><!--
function sayHi(){
console.log("Hi!");
}
//--></script>
Using this format, browsers like Mosaic would safely ignore the content inside of the <script> tag, and browsers that supported JavaScript had to look for this pattern to recognize that there was indeed JavaScript content to be parsed.
Although this format is still recognized and interpreted correctly by all web browsers, it is no longer necessary and should not be used. In XHTML mode, this also causes the script to be ignored because it is inside a valid XML comment.
INLINE CODE VERSUS EXTERNAL FILES
Although it’s possible to embed JavaScript in HTML files directly, it’s generally considered a best practice to include as much JavaScript as possible using external files. Keeping in mind that there are no hard and fast rules regarding this practice, the arguments for using external files are as follows:
➤➤ Maintainability—JavaScript code that is sprinkled throughout various HTML pages turns code maintenance into a problem. It is much easier to have a directory for all JavaScript files so that developers can edit JavaScript code independent of the markup in which it’s used.
➤➤ Caching—Browsers cache all externally linked JavaScript files according to specific settings, meaning that if two pages are using the same file, the file is downloaded only once. This ultimately means faster page-load times.
➤➤ Future-proof—By including JavaScript using external files, there’s no need to use the XHTML or comment hacks mentioned previously. The syntax to include external files is the same for both HTML and XHTML.
One notable consideration when configuring how external files are requested is their implication on request bandwidth. With SPDY/HTTP2, the per-request overhead is substantially reduced insofar as it may be advantageous to deliver scripts to the client as lightweight independent JavaScript components.
For example, your first page might have the following:
<script src="mainA.js"></script>
<script src="component1.js"></script>
<script src="component2.js"></script>
<script src="component3.js"></script>
...
A subsequent page loaded might have the following:
<script src="mainB.js"></script>
<script src="component3.js"></script>
<script src="component4.js"></script>
<script src="component5.js"></script>
...
On the initial request, if the browser supports SPDY/HTTP2, it will be able to efficiently retrieve a number of files from the same endpoint, and it will enter them into the browser cache on a per-file basis. From the perspective of the browser, retrieval of these individual resources over SPDY/HTTP2 should have approximately the same latency as delivering a monolithic JavaScript payload.
On the second page request, because you segmented your application into lightweight cacheable files, some of the components that the second page also depends upon are already in the browser cache.
Of course, this assumes the browser supports SPDY/HTTP2, which is only a valid assumption for modern browsers. Monolithic payloads may be more appropriate if your aim is to include support for older browsers.
DOCUMENT MODES
Internet Explorer 5.5 introduced the concept of document modes through the use of doctype switching. The first two document modes were quirks mode, which made Internet Explorer behave as if it were version 5 (with several nonstandard features) and standards mode, which made Internet Explorer behave in a more standards-compliant way. Though the primary difference between these two modes is related to the rendering of content with regard to CSS, there are also several side effects related to JavaScript. These side effects are discussed throughout the book.
Since Internet Explorer first introduced the concept of document modes, other browsers have followed suit. As this adoption happened, a third mode called almost standards mode arose. That mode has a lot of the features of standards mode but isn’t as strict. The main difference is in the treatment of spacing around images (most noticeable when images are used in tables).
Quirks mode is achieved in all browsers by omitting the doctype at the beginning of the document. This is considered poor practice because quirks mode is very different across all browsers, and no level of true browser consistency can be achieved without hacks.
Standards mode is turned on when one of the following doctypes is used:
<!-- HTML 4.01 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<!-- XHTML 1.0 Strict -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- HTML5 -->
<!DOCTYPE html>
Almost standards mode is triggered by transitional and frameset doctypes, as follows:
<!-- HTML 4.01 Transitional -->
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!-- HTML 4.01 Frameset -->
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd">
<!-- XHTML 1.0 Transitional -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- XHTML 1.0 Frameset -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Frameset//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
Because almost standards mode is so close to standards mode, the distinction is rarely made. People talking about “standards mode” may be talking about either, and detection for the document mode (discussed later in this book) also doesn’t make the distinction. Throughout this book, the term standards mode should be taken to mean any mode other than quirks.
THE <NOSCRIPT> ELEMENT
Of particular concern to early browsers was the graceful degradation of pages when the browser didn’t support JavaScript. To that end, the <noscript> element was created to provide alternate content for browsers without JavaScript. Although effectively 100% of browsers now support JavaScript, this element is still useful for browsers that explicitly disable JavaScript.
The <noscript> element can contain any HTML elements, aside from <script>, that can be included in the document <body>. Any content contained in a <noscript> element will be displayed under only the following two circumstances:
➤➤ The browser doesn’t support scripting.
➤➤ The browser’s scripting support is turned off
If either of these conditions is met, then the content inside the <noscript> element is rendered. In all other cases, the browser does not render the content of <noscript>.
Here is a simple example:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script ""defer="defer" src="example1.js"></script>
<script ""defer="defer" src="example2.js"></script>
</head>
<body>
<noscript>
<p>This page requires a JavaScript-enabled browser.</p>
</noscript>
</body>
</html>
In this example, a message is displayed to the user when the scripting is not available. For scriptingenabled browsers, this message will never be seen even though it is still a part of the page.
SUMMARY
JavaScript is inserted into HTML pages by using the <script> element. This element can be used to embed JavaScript into an HTML page, leaving it inline with the rest of the markup, or to include JavaScript that exists in an external file. The following are key points:
➤➤ To include external JavaScript files, the src attribute must be set to the URL of the file to include, which may be a file on the same server as the containing page or one that exists on a completely different domain.
➤➤ All <script> elements are interpreted in the order in which they occur on the page. The code contained within a <script> element must be completely interpreted before code in the next <script> element can begin so long as defer and async attributes are not used.
➤➤ For nondeferred scripts, the browser must complete interpretation of the code inside a <script> element before it can continue rendering the rest of the page. For this reason, <script> elements are usually included toward the end of the page, after the main content and just before the closing </body> tag.
➤➤ You can defer a script’s execution until after the document has rendered by using the defer attribute. Deferred scripts always execute in the order in which they are specified.
➤➤ You can indicate that a script need not wait for other scripts and also not block the document rendering by using the async attribute. Asynchronous scripts are not guaranteed to execute in the order in which they occur in the page.
By using the <noscript> element, you can specify that content is to be shown only if scripting support isn’t available on the browser. Any content contained in the <noscript> element will not be rendered if scripting is enabled on the browser.
本文深入探讨了在HTML页面中使用JavaScript的方法,包括内联代码与外部文件的对比,如何使用<script>元素,以及如何处理不同浏览器的文档模式。文章还介绍了<noscript>元素的作用,用于提供在JavaScript禁用情况下的替代内容。
1363

被折叠的 条评论
为什么被折叠?



