say I have code like this
<html><body>
<bunch of html tags...>
<script>
function myF()={};
setTimeout(myF, 100);
</script>
<lots of more html goes here.....></body></html>
As I understand it, the script will be evaluated as the html is parsed. But, in this case we have a setTimeout followed by lots of html parsing. When will the timeout get to make its call? Does it need to wait until all the html parsing is done before myF is finally called, or will myF be called when the timeout event occurs, even if there is more html parsing to accomplish?
No, setTimeout() does not necessarily wait for DOMContentLoaded
If it did, we wouldn't have need for the DOMContentLoaded event, but if that's not enough to convince you, below is conclusive evidence:
<script>
window.addEventListener('load', function() {
alert("Window Loaded");
});
document.addEventListener('DOMContentLoaded', function() {
alert("DOM Content Loaded");
});
setTimeout(function() {
alert(typeof jQuery == 'function' ? 'jQuery Loaded' : 'jQuery Not Loaded');
}, 15);
</script>
<p>This content will be loaded before jQuery is fetched.</p>
<script>
document.write('<script src="https://code.jquery.com/jquery-3.2.1.min.js?' + Math.random() + '" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></' + 'script>');
</script>
<script>
alert('Loaded after <script src="jquery.min.js">');
</script>
<p>This content will not be loaded until after jQuery is fetched.</p>
If it had to wait for DOMContentLoaded, you'd see
Loaded after <script src="jquery.min.js">
DOM Content Loaded
Window Loaded
jQuery Loaded
However (at least for me), a good portion of the time, the output is
jQuery Not Loaded
Loaded after <script src="jquery.min.js">
DOM Content Loaded
Window Loaded
Even though the parsing of HTML is single-threaded, it is blocked when <script> without async and <link> must pause to fetch the resource from the URL and execute the script or stylesheet respectively, which means there's a race-condition between the DOMContentLoaded event and setTimeout(function() { ... }, 15).
Don't rely on that. setTimeout doesn't create an 'interrupt' so to speak, all it does it add the function to a queue that is checked when the browser decides to check it. This could be at the end of the thread loop, or it could be during HTML parsing.
Further reading on JavaScript timers: https://johnresig.com/blog/how-javascript-timers-work/
The better (standard) way to wait until the HTML is finished parsing is like this:
<html><body>
<bunch of html tags...>
<script>
function myF()={};
document.addEventListener("DOMContentLoaded", myF);
</script>
<lots of more html goes here.....></body></html>
Or, using jQuery, like this:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<bunch of html tags...>
<script>
function myF()={};
$(myF);
</script>
<lots of more html goes here.....></body></html>
Related
I've looked all around stack overflow for answers to this same problem. Usually it's simple syntax errors like omitting a closing tag or not writing the src attribute properly.
I could've sworn, though, that my html and js is correct, but I still can't get the jQuery working.
The normal javascript (function test()) works perfectly as it's linked with an html button using onclick. The $('#click') jQuery simply will not work, though.
<head>
<script type='text/javascript' src='jquery-3.2.1.min.js'></script>
<script type='text/javascript' src='test.js'></script>
</head>
<body>
<button type='button' style='margin: 1em;' id='click'>CLICK</button>
<button type='button' style='margin: 1em;' id='testButton' onclick='test()'>TEST LINKING</button>
</body>
Both js files and the html are in the same folder.
And here's the js.test:
$('#click').click(function () {
alert('jQuery Works!');
}); //NOT WORKING!
function test() {
alert('Okay!');
} //WORKING!
The problem isn't linking jQuery, it's about scope and DOM loading.
When you create a function globally it becomes a global reference that you can access whenever you want afterward.
When you call a function, or change the value of a variable, it needs a scope to know when to be called. It's generally the global scope but, when you use jQuery functions, you cannot know if your libraries are already loaded and therefore if you'll be able to access the functions they provide.
Therefore, wrap your EH in a document.ready function that is executed by jQuery itelf when the DOM is fully loaded. You can do it this way :
$(document).ready(function(){
$('#click').click(function () {
alert('jQuery Works!');
});
});
Or in a shorter way :
$(function(){
$('#click').click(function () {
alert('jQuery Works!');
});
});
Ahh, the problem here is that when you load the JS and jQuery file, browser starts going through you code, finds that it should now bind a click handler for #click, but when it actually goes to find #click on the page it doesn't get it. Because it has not been added to the dom yet !
You have three ways to deal with this :
Use the defer attribute of the script tag. It is used to define script that will not run until after the page has loaded
<script type='text/javascript' src='test.js' defer></script>
Just load your JS for the event handler after the divs like :
<head>
<script type='text/javascript' src='jquery-3.2.1.min.js'></script>
</head>
<body>
<button type='button' style='margin: 1em;' id='click'>CLICK</button>
<button type='button' style='margin: 1em;' id='testButton' onclick='test()'>TEST LINKING</button>
<script type='text/javascript' src='test.js'></script>
</body>
This is why you normally see the line
Place CSS includes in the head and JS include just before the close of the body tag
That's because without CSS your page would look ugly untill it has been loaded, and placing JS in the end would make sure that the DOM is in place for any action to be performed on it
You can use the $(document).ready(function(){ ... }) event handler. This event is triggered when the document is ready or essentially when the dom has been created. Binding you click handler here makes sure that the elem is actually available for your JS to bind to
You cant define a function in a way like this. you need to do this
$(document).ready(function() {
$('#click').click(function() {
alert('jQuery Works!');
//declare other function
});
});
`
Is there any difference in declaring my async javascript statically vs dynamically?
static
<html>
<head>
...
</head>
<body>
...
<div id='my-script-needs-me'></div>
<script type="text/javascript" src="https://foo.bar/myscript.js" async>
</script>
...
</body>
</html>
dynamic
<html>
<head>
...
</head>
<body>
...
<div id='my-script-needs-me'></div>
<script type="text/javascript">
var myScript = document.createElement("script");
myScript.src = 'https://foo.bar/myscript.js';
myScript.async = !0;
myScript.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(myScript);
</script>
...
</body>
</html>
I noticed that declaring a script statically let a browser detect it earlier and preload (chrome + firefox).
My goal is to load a javascript in async way in order not to block HTML rendering and other scripts execution. Sametime, I want it to be executed as soon as it's downloaded, having in mind that it requires one element to be in the DOM already. Once downloaded the script is executed and it accesses the my-script-needs-me div. One limitation, I cannot change the script itself.
supports async parameters allowing to make this call asynchronous.
The second way you described allows you to have the url as a parameter and bind it.
It allows too the use of a callback to do some stuff when your script is loaded.
let scriptElement = document.createElement('script');
let url = `https://maps.googleapis.com/maps/api/js?key=${apiKey}`;//&libraries=geometry
scriptElement.src = url;
//Chargement de l'API Google
scriptElement.onload = () => {
//API chargée, on peut lancer l'initialisation du composant
this._initializeMap();
};
I used this to load Google Maps API, it's not directly in the HTML, so i can modify the URL when my page loads. And when the API is loaded, I an launch treatments that need this API.
you can use defer for that instead of async.
your script will execute right after html be parsed.
Static
<html>
<head>
...
</head>
<body>
...
<div id='my-script-needs-me'></div>
<script type="text/javascript" src="https://foo.bar/myscript.js" async>
</script>
...
</body>
</html>
As you know, HTML is parsed top-bottom. So, if it placed within body tag, then as soon as parsed, if it is an IIFE or the file myscript.js contains a function call, it will execute immediately.
So, inside, body, put it the script at the bottom will help you to execute it after the div has loaded.
But we can't ensure because of caching.
If the browser cache the script and if it is an IIFE or contains a function call, we can't predict the behaviour.
Dynamic
In dynamic also, it depends on the order.
<html>
<head>
...
</head>
<body>
...
<div id='my-script-needs-me'></div>
<script type="text/javascript">
var myScript = document.createElement("script");
myScript.src = 'https://foo.bar/myscript.js';
myScript.async = !0;
myScript.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(myScript);
</script>
...
</body>
</html>
In both cases, it will render after HTML contents.
The best way to ensure it loads only after all contents are loaded is
Giving an eventListener on Window.
Check the code below
<html>
<head>
...
</head>
<body>
...
<div id='my-script-needs-me'></div>
<script type="text/javascript">
function load(){
var myScript = document.createElement("script");
myScript.src = 'https://foo.bar/myscript.js';
myScript.async = !0;
myScript.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(myScript);
}
window.addEventListener("DOMContentLoaded",load);
</script>
...
</body>
</html>
Check this line window.addEventListener("DOMContentLoaded",load);.
The DOMContentLoaded is similar to jQuery's $(document).ready(). It will trigger the callback function when the HTML is properly loaded. So, you don't have to check for the existence of the HTML Element.
From what I've learned it's better to go with static way to declare an async script (in my particular scenario) than dynamic. Here some of why(s):
static async script declaration is detected by a browser and kicked off right away (at the very top of the page processing);
(deferred from #1) a browser puts the script request earlier in requests queue and if you have enough (30-40 requests per page load) it could be crucial to be in first 10 requests, not at the position 30-40;
adding a script dynamically to the head from the body doesn't introduce any performance advantage against the static declaration as long as whole head is already processed and it won't delay execution of the statically declared script;
at the moment when we reach the script declaration, static will work instantly because it's already pre-loaded and ready to be executed (in most cases, async is crucial here) while the dynamic script declaration will just kick off the request to download the script and only after then execute it;
I hope my thoughts will help someone as well.
I have the following html code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/blazy/1.8.2/blazy.min.js" defer></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha256-8WqyJLuWKRBVhxXIL1jBDD7SDxU936oZkCnxQbWwJVw=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/js/lightbox.min.js" defer></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous" defer></script>
<!-- 26 dec flexslider js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer></script>
<script defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
</head>
<body>
</body>
</html>
I get an error, saying jQuery is not defined. Now even if I remove defer from my inline JS code, it says jQuery is undefined. For some reason I have to keep the jQuery plugins in the head and keep my JS code inline. My question is:
Why doesn't inline Javascript code get deferred when defer attribute is present on it?
Is there a way to imitate the defer behavior on my inline Javascript code? I can put that at the end of body tag if required.
The scripts with the defer attribute load in the order they are specified, but not before the document itself has been loaded. As defer has no effect on script tags unless they also have the src attribute, the first script that gets executed is your inline script. So at that time jQuery is not loaded yet.
You can solve this in at least two ways:
Put your inline script in a .js file and reference it with a src attribute (in addition to the defer attribute which you already had there), or
Let your inline script wait for the document and the deferred scripts to be loaded. The DOMContentLoaded event will fire when that has happened:
<script>
window.addEventListener('DOMContentLoaded', function() {
(function($) {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
})(jQuery);
});
</script>
NB: Notice that in the latter case $(document).ready(function() is not included any more, as that would wait for the same event (DOMContentLoaded). You could still include it like you had in your original code, but then jQuery would just execute the callback immediately, which makes no practical difference.
You can create a Base64 URL out of the script and put it into the src!
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer>
</script>
I built a quick test to see it in action.
You should see an alert with Hello world! last if defer is working:
<script defer>
alert('Why no defer?!?');
</script>
<!-- alert('Hello world!'); -->
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer></script>
<script>
alert('Buh-bye world!');
</script>
Doing it manually is a little laborious so if you have the luxury of compiling your HTML in some way (Handlebars, Angular, etc.) then that helps a lot.
I'm currently using:
<script src="data:text/javascript;base64,{{base64 "alert('Hello world!');"}}"
defer>
</script>
You can also use type="module":
<meta charset="utf-8">
<script type="module">
let t = document.getElementById('top');
console.log(t);
</script>
<h1 id="top">Top Questions</h1>
https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-type
From MDN docs:
defer
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded. The defer attribute should only be used on external scripts.
This is called an IIFE (Immediately Invoked Function Expression) which gets executed before DOM is available. So, in that case jQuery is undefined because it it not in the DOM.
defer loading with plain text Data URI - Chrome and FF
#noLib #vanillaJS
suggest not to use on Cross Browser PRODuction
until MS IE dies and MS Edge will adopt the Chromium open source ;)
the only way to defer script is external file or Data_URI (without using event DOMContentLoaded)
defer
spec script#attr-defer (MDN web docs): "This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.)"
Data_URI
spec Data_URI
with right type "text/javascript" there is no need to base64 at all... ;)
using plain text so you can use simple:
<script defer src="data:text/javascript,
//do something with b-lazy plugin, lightbox plugin and then with flexslider
lightbox.option({
resizeDuration: 200,
wrapAround: true
})
">
yes, it's little bit weird hack, but <script type="module"> are deferred by default, there is no other option to mix following in exact order:
module external files - deferred by default
module inline scripts - deferred by default
external files - optionally deferred
inline scripts - only with this hack - as I know (without libraries/frameworks)
Defer/async script tags are not good enough
There is a common knowledge that you should use <script src=".." async defer> (or set script.async = true before assigning src, when you do it from JS) and/or put your scripts at the very bottom of the page, so that as much as possible of the page gets loaded and rendered to the user, as fast as possible.
defer.js (note: I am the author of this script) is written in plain JavaScript, making lazy-loading other contents more fast and performant. You can defer any javascript files as well as inline script blocks efficiently.
If your page is just an HTML page enhanced with some JavaScript, then you're good with just <script async>. It takes time for browser to parse and execute those scripts, and each UI change may reflow your layout, make your load speed more slow, no one likes staring at a blank white page; users are impatient and will leave quickly.
In various cases, using async or defer does not deliver faster page speed than defer.js does.
I checked all the proposed solutions but all have their disadvantages. So I invented my own.
Put this inline script into your head tag or right after the start of body tag:
<script>var Defer = []; document.addEventListener('DOMContentLoaded', function() { while (Defer.length) Defer.shift().call(); }); </script>
This one liner will collect all the inline scripts you want to defer and run them respectively as soon as document is fully loaded. Now anytime you need to run an inline script deferred, just register it like:
<script>
alert('This alert will show immediately.');
Defer.push(function() {
alert('This alert will show only after document is loaded.');
// You can use anything which is not loaded yet, like jQuery
$(".selector").doSomeJqueryStuff();
});
// You can use it as many times as you like and in any place of your DOM.
Defer.push(function() {
// Any inline code you want to defer
});
</script>
This inline script will run only after document is loaded. That means you can run inline jQuery script having your jQuery stay at the end of your DOM.
You can use this data url as src attribute
data:application/javascript,eval(document.currentScript.textContent)
which takes this current script tag and evaluate its content as if it was inside an external file.
it also works with lazy attribute.
it uses document.currentScript which not supported by IE browsers.
<script defer src="https://cdn.jsdelivr.net/npm/vue"></script>
<script defer src="data:application/javascript,eval(document.currentScript.textContent)">
console.log('defered', typeof Vue); // function
</script>
<script>
console.log('not defered', typeof Vue); // undefined
</script>
There is a somewhat less obscure way to accomplish deferral that does not require callbacks, promises, or data urls ... although it does a little DOM manipulation in the background. The tiny library (109 bytes compressed/gziped) https://www.npmjs.com/package/deferscript let's you do this. The example below is based on the original post.
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer>
</script>
<script src="./deferscript.js" defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
All you have to do is insert a src attribute with the value ./deferscript.js.
If the problem is that jQuery variable $ is not defined, maybe you can create a fake $ function that returns a ready function waiting for the DOMContentLoaded?
All my inline scripts has $(document).ready(..... and the problem is that $ is not defined as the header scripts are deferred.
So, just add a fake $ in an inline script in head:
<script type="text/javascript">
var $ = function(element) {
return {
ready: function(callback) {
// in case the document is already rendered
if (document.readyState!="loading") callback();
// modern browsers
else if (document.addEventListener)
document.addEventListener("DOMContentLoaded", callback);
// IE <= 8
else document.attachEvent("onreadystatechange", function(){
if (document.readyState=="complete") callback();
});
}
};
};
</script>
I theoretically know the differences between async and defer used in <script> tag.
async: The script is executed asynchronously with the rest of the page (the script will be executed while the page continues the parsing).
defer: The script is executed when the page has finished parsing.
But can't understand the mechanism they follow.
I have the following 2 files-1.js and 1.html.
1.js
alert('Running external JS');
1.html
<html>
<body>
<script src="1.js" async></script>
<script>
for(var i=0;i<5;i++)
{
alert('HTML code1');
}
</script>
<script>
for(var i=0;i<5;i++)
{
alert('HTML code2');
}
</script>
</body>
</html>
The html file executes s.t. all the alert statements execute first and then at last the script 1.js executes i.e. it acts similarly as when I use defer in place of async in the <script>-tag.
As per my expectation as the external script is much smaller than the code in the HTML itself it should have been triggered anywhere in between the alert statements in the HTML document as I am using async but acting similar to the effect of using defer.
Please explain what happened here also please correct me if I am wrong.
My AJAX app is basically one index.html plus a bunch of .js modules. My setup function hooks up the js handler code to the appropriate DOM element. I suspect I need to use window.onload() rather than jquery's $(document).ready() since all the .js files need to be available (i.e. downloaded) at hookup time.
My understanding is that only the DOM tree is ready at $(document).ready(), but there's no guarantee that the .js files have been loaded. Is that correct?
PS. I don't need multiple onload handlers; a single window.onload() is fine for me.
You definitely have a misunderstanding in this case. The whole reason why it is considered best practice to include your script tags just before the close of the body tag is because script loads are blocking loads. Unless specifically coded otherwise (i.e.; google analytics), JavaScript files are loaded synchronously.
That said, if there are dependencies between script files, the order in which the files are loaded can be important.
No, you can safely use $(document).ready() as long as your script tags are loaded synchronously (in most cases, this means "normally"). The browser waits for a <script> to be loaded before continuing. Therefore, $(document).ready() includes all <script> tags in the source.
There are two exceptions to this, if the script tags contains an async or defer attribute. The prior meaning the compatible browsers can continue rendering the page, and the latter signifying that the script is executed when the page has finished.
I setup two files as a test:
syncscript.html
<html>
<head>
<title></title>
<style type="text/css">
</style>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
$(window).load(function(){
$(document.body).append('<p>window.load has run.');
});
$(document).ready(function(){
$(document.body).append('<p>document.ready has run.');
});
</script>
</head>
<body>
<p>Page has loaded. Now continuing page load and attempting to load additional script file (after 10 second pause).</p>
<script type="text/javascript">
var p = document.createElement('p');
p.innerHTML = '<p>Inline script preceding jssleep.php file has run.</p>';
document.body.appendChild(p);
</script>
<script type="text/javascript" src="http://jfcoder.com/test/jssleep.php"></script>
<script type="text/javascript">
var p = document.createElement('p');
p.innerHTML = '<p>This is an inline script that runs after the jssleep.php script file has loaded/run.</p>';
document.body.appendChild(p);
</script>
</body>
</html>
jssleep.php
<?php
header("content-type: text/javascript");
sleep(10);
?>
var p = document.createElement('p');
p.innerHTML = '<p>jssleep.php script file has loaded and run following <?php sleep(10); ?>.</p>';
document.body.appendChild(p);
This outputs (in Firefox):
Page has begun loading. Now continuing page load and attempting to
load additional script file (after 10 second pause).
Inline script preceding jssleep.php file has run.
jssleep.php script file has loaded and run following <?php sleep(10);
?>.
This is an inline script that runs after the jssleep.php script file
has loaded/run.
$(document).ready() has run.
$(window).load() has run.
That is correct. However window.onload also requires CSS and images to be downloaded, so may be a bit overkill.
What you can do is this:
var scriptsToLoad = 0;
// for each script:
s = document.createElement('script');
s.type = "text/javascript";
s.src = "path/to/file.js";
scriptsToLoad += 1;
s.onload = function() {scriptsToLoad -= 1;};
// after all scripts are added in the code:
var timer = setInterval(function() {
if( scriptsToLoad == 0) {
clearInterval(timer);
// do stuff here
}
},25);