Chapter 10 – JavaScript in the DOM

最后更新于:2022-04-01 04:39:29

## 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 *[](http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#the-script-element)*[ element node](http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#the-script-element). The*`<script> `* element can contain JavaScript code or can be used to link to external JavaScript files using the *src*attribute. Both methods are explored in the code example below. live code: [http://jsfiddle.net/domenlightenment/g6T5F](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: *async*, *charset*, *defer*,*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](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 using*defer* 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](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 the*DOMContentLoaded* 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/](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 the*async* 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](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](http://pieisgood.org/test/script-link-events/) 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](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](http://pieisgood.org/test/script-link-events/) you also have use of *onerror*, *load*, 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> ~~~
';