Chapter 10 - JavaScript in the

10.1 Inserting & executing JavaScript overview

JavaScript can be inserted in to an HTML document in a modern way by including external JavaScript files or writing page level inline JavaScript, which is basically the contents of an external JavaScript file literally embed in the HTML page as a text node. Don"t confuse element inline JavaScript contained in attribute event handlers (i.e.<div onclick="alert("yo")"></div>) with page inline JavaScript (i.e. <script>alert("hi")</script>).

Both methods of inserting JavaScript into an HTML document require the use of a  element node. The<script>  element can contain JavaScript code or can be used to link to external JavaScript files using the srcattribute. Both methods are explored in the code example below.

live code: http://jsfiddle.net/domenlightenment/g6T5F

<!DOCTYPE html>
<html lang="en">
<body>

<!-- external, cross domain JavaScript include -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script>

<!-- page inline JavaScript -->
<script>
console.log("hi");
</script>

</body>
</html>

Notes

Its possible to insert and execute JavaScript in the DOM by placing JavaScript in an element attribute event handler (i.e. <div onclick="alert("yo")"></div>) and using the javascript: protocal (e.g. <a href="javascript:alert("yo")"></a>) but this is no longer considered a modern practice.

Trying to include an external JavaScript file and writing page inline JavaScript using the same <script> element will result in the page inline JavaScript being ignored and the exterenal JavaScript file being downloaded and exectued

Self-closing scripts tags (i.e. <script src="" /> ) should be avoid unless you are rocking some old school XHTML

The <script> element does not have any required attributes but offers the follow optional attribures: asynccharsetdefer,src, and type

Page inline JavaScript produces a text node. Which permits the usage of innerHTML and textContent to retrieve the contents of a line <style>. However, appending a new text node made up of JavaScript code to the DOM after the browser has already parsed the DOM will not execute the new JavaScript code. It simply replaces the text.

If JavaScript code contains the string "</script>" you will have to escape the closing "/" with "</script>" so that the parser does not think this is the real closing </script> element

10.2 JavaScript is parsed synchronously by default

By default when the DOM is being parsed and it encounters a <script> element it will stop parsing the document, block any further rendering & downloading, and exectue the JavaScript. Because this behavior is blocking and does not permit parallel parsing of the DOM or exection of JavaScriopt its consider to be synchronous. If the JavaScript is external to the html document the blocking is exacerbated because the JavaScript must first be downloaed before it can be parsed. In the code example below I comment what is occuring during browser rendering when the browser encoutners several <script> elements in the DOM.

live code: http://jsfiddle.net/domenlightenment/rF3Lh

<!DOCTYPE html>
<html lang="en">
<body>

<!-- stop document parsing, block document parsing, load js, exectue js, then resume document parsing... -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script>

<!-- stop document parsing, block document parsing, exectue js, then resume document parsing... -->
<script>console.log("hi");</script>

</body>
</html>

You should make note of the differences between an inline script"s and external scripts as it pertains to the loading phase.

Notes

The default blocking nature of a <script> element can have a significant effect on the perfomrance & percived performance of the visual rendering of a HTML web page. If you have a couple of script elements at the start of an html page nothing else is happening (e.g. DOM parsing & resource loading) until each one is downloaed and executed sequentially.

10.3 Defering the downloading & exectuion of external JavaScript usingdefer

The <script> element has an attribute called defer that will defer the blocking, downloading, and executing of an external JavaScript file until the browser has parsed the closing <html> node. Using this attribute simply defers what normally occurs when a web browser encoutners a <script> node. In the code below I defer each external JavaScript file until the final <html> is encountered.

live code: http://jsfiddle.net/domenlightenment/HDegp

<!DOCTYPE html>
<html lang="en">
<body>

<!-- defer, don"t block just ignore this until the <html> element node is parsed -->
<script defer src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script>

<!-- defer, don"t block just ignore this until the <html> element node is parsed -->
<script defer src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>

<!-- defer, don"t block just ignore this until the <html> element node is parsed -->
<script defer src="http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.0.6/jquery.mousewheel.min.js"></script>

<script>
//We know that jQuery is not avaliable because this occurs before the closing <html> element
console.log(window["jQuery"] === undefined); //logs true

//Only after everything is loaded can we safley conclude that jQuery was loaded and parsed
document.body.onload = function(){console.log(jQuery().jquery)}; //logs function
</script> 

</body>
</html>

Notes

According to the specification defered scripts are suppose to be exectued in document order and before theDOMContentLoaded event. However, adherence to this specification among modern browsers is inconsistent.

defer is a boolan attribute it does not have a value

Some browers support defered inline scripts but this is not common among modern browsers

By using defer the assummption is that document.write() is not being used in the JavaScript that will be defered

10.4 Asynchronously downloading & executing external JavaScript files using async

The <script> element has an attribute called async that will override the sequential blocking nature of <script>elements when the DOM is being constructed by a web browser. By using this attribute, we are telling the browser not to block the construction (i.e. DOM parsing, including downloading other assets e.g. images, style sheets, etc...) of the html page and forgo the the sequential loading as well.

What happens by using the async attribute is the files are loaded in parallel and parsed in order of download once they are fully downloaded. In the code below I comment what is happening when the HTML document is being parsed and render by the web browser.

live code: http://jsfiddle.net/domenlightenment/

<!DOCTYPE html>
<html lang="en">
<body>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script async src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script async src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script async src="http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.0.6/jquery.mousewheel.min.js"></script>

<script>
// we have no idea if jQuery has been loaded yet likley not yet...
console.log(window["jQuery"] === undefined);//logs true

//Only after everything is loaded can we safley conclude that jQuery was loaded and parsed
document.body.onload = function(){console.log(jQuery().jquery)};
</script> 

</body>
</html>

Notes

IE 10 has support for async, but IE 9 does not

A major drawback to using the async attribute is JavaScript files potentially get parsed out of the order they are included in the DOM. This raises a dependency management issue.

async is a boolan attribute it does not have a value

By using async the assummption is that document.write() is not being used in the JavaScript that will be defered

The async attribute will trump the defer if both are used on a <script> element

10.5 Forcing asynchronous downloading & parsing of external JavaScript using dynamic <script> 

A known hack for forcing a web browser into asynchronous JavaScript downloading and parsing without using theasync attribure is to programatically create <script> elements that include external JavaScript files and insert them in the DOM. In the code below I programatically create the <script> element node and then append it to the<body> element which forces the browser to treat the <script> element asynchronously.

live code: http://jsfiddle.net/domenlightenment/du94d

<!DOCTYPE html>
<html lang="en">
<body>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script>
var underscoreScript = document.createElement("script"); 
underscoreScript.src = "http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"; 
document.body.appendChild(underscoreScript);
</script>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script>
var jqueryScript = document.createElement("script");
jqueryScript.src = "http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"; 
document.body.appendChild(jqueryScript);
</script>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script>
var mouseWheelScript = document.createElement("script");
mouseWheelScript.src = "http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.0.6/jquery.mousewheel.min.js"; 
document.body.appendChild(mouseWheelScript);
</script>

<script>
//Only after everything is loaded can we safley conclude that jQuery was loaded and parsed
document.body.onload = function(){console.log(jQuery().jquery)};
</script>

</body>
</html>

Notes

A major drawback to using dynamic <script> elements is JavaScript files potentially get parsed out of the order they are included in the DOM. This raises a dependency management issue.

10.6 Using the onload call back for asynchronous <script>"s so we know when its loaded

The <script> element supports a load event handler (ie. onload) that will execute once an external JavaScript file has been loaded and executed. In the code below I leverage the onload event to create a callback programatically notifying us when the JavaScript file has been downloaded and exectued.

live code: http://jsfiddle.net/domenlightenment/XzAFx

<!DOCTYPE html>
<html lang="en">
<body>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script>
var underscoreScript = document.createElement("script"); 
underscoreScript.src = "http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js";
underscoreScript.onload = function(){console.log("underscsore is loaded and exectuted");};
document.body.appendChild(underscoreScript);
</script>

<!-- Don"t block, just start downloading and then parse the file when it"s done downloading -->
<script async src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js" onload="console.log("jQuery is loaded and exectuted");"></script>

</body>
</html>

Notes

The onload event is only the tip of the iceberg avaliable where onload is supported you also have use of onerrorload, and,error.

10.7 Be mindful of <script> "s placement in HTML for DOM manipulation

Given a <script> elements synchronous nature, placing one in the <head> element of an HTML document presents a timing problem if the JavaScript execution is dependant upon any of the DOM that proceeds the<script>. In a nut shell, if JavaScript is executed at the begining of a document that manipulates the DOM, that proceeds it, you are going to get a JavaScript error. Proven by the following code example:

live code: N/A

<!DOCTYPE html>
<html lang="en">
<head>
<!-- stop parsing, block parsing, exectue js then resume... -->
<script>
//we can"t script the body element yet, its null, not even been parsed by the browser, its not in the DOM yet 
console.log(document.body.innerHTML); //logs Uncaught TypeError: Cannot read property "innerHTML" of null 
</script>
</head>
<body>
<strong>Hi</strong>
</body>
</html>

Many developers, myself being one of them, for this reason will attempt to place all <script> elements before the closing <body> element. By doing this you can rest assured the DOM in front of the <script>"s has been parsed and is ready for scripting. As well, this strategy will remove a dependancy on DOM ready events that can liter a code base.

10.8 Getting a list of <script>"s in the DOM

The document.scripts property avaliable from the document object provides a list (i.e. an HTMLCollection) of all of the scripts currently in the DOM. In the code below I leverage this property to gain access to each of the<script> elements src attributes.

live code: N/A

<!DOCTYPE html>
<html lang="en">
<body>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.0.6/jquery.mousewheel.min.js"></script>

<script>​
Array.prototype.slice.call(document.scripts).forEach(function(elm){	console.log(elm); });//will log each script element in the document
</script> 

</body>
</html>
文章导航