JavaScript itself is a single threaded language so in the traditional sense it is a blocking language.
One Thread == One Call Stack == One Thing At A Time
- Philip Roberts
When we are running blocking code nothing else can be ran. Think about this in terms of the browser.
- Its a bad user experience if a user has to sit and wait for each thing to load in sequence.
- The browser wants to constantly render (every 6 milli seconds). If we are blocking that then nothing can re-render and looks like the browser has frozen.
Run this code and look at how the button seems frozen during execution.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<button id="button">Click Me!</button>
<script>
function wait(ms) {
let start = Date.now(),
now = start;
while (now - start < ms) {
now = Date.now();
}
}
document.getElementById("button").addEventListener("click", () => {
wait(5000);
alert("Ran!");
});
</script>
</body>
</html>
As you can see this is a very bad user experience and keeps us from doing else while we wait for our code to finish executing.
However we can make JavaScript act ansynchronously through the callback queue and event loop.
Non-Blocking or Asynchronous code is when a line of code is executed and the program is able to move on before that line of code has finished.
setTimeout(expression, timeout); runs the code/function once after the timeout.
console.log('start of js file');
setTimeout(function () {
console.log('inside setTimeout');
}, 1000);
console.log('end of js file');
So it looks like the code was able to continue to execute while it waited for the timeout to hit 5 seconds.
If we modify the timeout to 0 seconds what do you believe the order printed to the screen will be?
The event is queued after the timeout.
setTimeout(expression, timeout); runs the code/function once after the timeout.
setInterval(expression, timeout); runs the code/function in intervals, with the length of the timeout between them.
console.log('start of js file');
setTimeout(function () {
console.log('inside setTimeout');
}, 1000);
setInterval(function () {
console.log('inside setInterval');
}, 500);
console.log('end of js file');
Mouse move fires everytime the mouse moves. Other events such as click are still fired and handled.
<!DOCTYPE html>
<html>
<head>
<style>
img {
position: absolute;
width: 100px;
top: 0;
left: 0;
}
body {
height: 15000px;
}
</style>
</head>
<body>
<h1>Welcome</h1>
<p>This page is used for practicing DOM manipulation</p>
<img src="https://avatars0.githubusercontent.com/u/583231?s=460&v=4">
<div class="notice">Nothing to notice.</div>
<script>
let heading = document.querySelector("h1");
let image = document.querySelector("img");
window.addEventListener("click", function (event) {
event.preventDefault();
heading.innerHTML += "!";
console.log('test')
});
let moveCounter = 0;
window.addEventListener("mousemove", function (event) {
console.log(`${moveCounter++}. moving ${event.screenX} ${event.screenY}`)
image.style.top = event.clientY + 'px'
image.style.left = event.clientX + 'px'
});
</script>
</body>
</html>
Debouncing can improve user experience by restricting the number of calls made to the server. For example calling to the server as a user types in something can result in the screen appearing to freeze eg: five characters entered could mean five separate requests that take 500ms. Debouncing and reducing the number of requests can prevent this.
Mouse move generates a lot of events. Do we need all those events?
We could act on one in 3 events instead.
window.addEventListener("mousemove", function (event) {
moveCounter++;
if ((moveCounter % 3) == 0) {
console.log(`${moveCounter}. moving ${event.screenX} ${event.screenY}`)
image.style.top = event.clientY + 'px'
image.style.left = event.clientX + 'px'
}
});
See Also Debouncing and Throttling Explained - on CSS Tricks
What the heck is the event loop anyway?
Further Adventures of the Event Loop - Erin Zimmer - JSConf EU 2018
- Refactor the code into a separate javascript file
- Add a scroll event listener
- Display the location in a console.log message. Hint: use the following formula
var scrollLeft = (window.pageXOffset !== undefined) ?
window.pageXOffset :
(document.documentElement || document.body.parentNode || document.body).scrollLeft;
var scrollTop = (window.pageYOffset !== undefined) ?
window.pageYOffset :
(document.documentElement || document.body.parentNode || document.body).scrollTop;
-
Use the scroll location to update the location of the image so it appears to stay next to the mouse cursor as the window scrolls
-
Experiment with throttling the scroll messages
Experiement with the event loop visualiser http://latentflip.com/loupe
Try adding the setTimer and setInterval as well event such as mouse move.