Interpreted or Compiled ?
The choice to interpret or compile is not mutual. Both are used in union in the browser. To say JavaScript is an interpreted language or compiled language would be wrong. Source
Interpreter: Translates and executes code line-by-line. Slower execut ion but no need for a separate compilation step.
Compiler: Translates entire code into machine language before execution. Faster execution but requires compilation beforehand.
Compiler is invoked by the Main Thread, thus contributing to Jank. But the engine has since then moved to Concurrent Compilation as much as possible.
V8 use Just-In-Time (JIT) compilation, which compiles parts of the code to machine code for faster execution. NOT ahead of time like in traditional compilers. It mostly looks at functions being used frequently and optimizes them.
The optimizing compiler depends on previously seen type information so :
Write code that looks statically typed
Dont change data type of a variable too often.
Applicable for Object keys as well. The keys aren’t really ‘just strings’ as we make them out to be. They are property attributes .
Objects are seen as shapes to facilitate data accessing speeds.
Ex, an object {x:number, y:number} is an object type the compiler can optimize if it sees again — via Inline Cache, but {x:number, a:number} is different.
This will help the compiler, {x:number, a:number, y:number} even if we have to keep the unused key as undefined.
This does optimize somewhat, but In my opinion, should not come at the cost of code readibility.
{x:number, y:string} is different as well.
So it will bail out of optimization — deoptimize the optimized code.
More clear example can be found here and here.
Event Loop and the Main Thread is not exclusive to JavaScript
Main Thread is responsible for executing:
JavaScript code
Handling DOM updates
Executing user event handlers
Running rendering tasks (style calculation, layout, painting)
The event loop is a mechanism that coordinates the execution of different tasks on the main thread.
https://youtu.be/cCOL7MC4Pl0?si=n27_6LVvySvIyxai (Must Watch )
In the above diagram, the white box is the main thread and the passage is Event Loop.
T is for an item on the task queue and the other letters are part of the rendering pipeline. S - Style , L - Layout , P - Paint . S,L together is called reflow and is perhaps the most costly computation during the entire lifecycle.
This is why HTML parsing is paused when the script tag is encountered until the script is fetched and executed, as it directly affect the DOM. That is why we keep them at the end of our HTML body.
There are ways to mitigate this by using the async or defer keywords with script.
Scripts with defer are downloaded in parallel with HTML parsing but executed only after parsing is complete.
Scripts with async are also downloaded in parallel, but they are executed as soon as they’re ready, potentially before HTML parsing finishes.
https://youtu.be/0IsQqJ7pwhw?t=807
So by implication,
https://youtu.be/cCOL7MC4Pl0?t=150
This is inefficient and the sequence should be opposite. But, the repaint happens only after the entire code has been executed.
Caveat
Certain JavaScript Browser APIs can call into the Style and Layout Engines to acquire styling information and precise coordinates of elements.Ex, offsetHeight, getBoundingClientRect() . They synchronously returns the bounding box and coordinates from the underlying Layout Engine primitive.
If the coordinates or styles for the requested element have not been computed yet, the browser must immediately compute them via performing reflow. This immediate invocation of the Style and Layout engines to resolve undetermined coordinates is forced, therefore referred to as a Forced Reflow. So —
Batch your DOM changes
Instead of doing : Read, Write, Read Write
do Read, Read, Write, Write .
Source
To write code that effects rendering, use requestAnimationFrame
Usecase example — Animating a box moving across the screen.
setTimeOut may end up with around 3.5x number of calls compared to an rAF, which is about 3–4 times of the ability of the screen to display (60fps) ! Complete waste.
Rendering happens at the start of a frame — the unit of screen refresh, typically 60 times per second (16.67ms per frame). If the callback tasks end up taking a lot of time, the frames will start to drop or multiple re-renders on the same frame resulting in Jank , the choppy behavior on screen.
rAFs always start at the beginning of each frame.
These callbacks happen as part of the render steps.
This primarily ensures a render happens when it should, not unnecessarily too frequently. Source
For example, to show a loader, earlier I used to use setInterval like :
import React, { useEffect, useState } from 'react';function Loader() { const [progress, setProgress] = useState(0); useEffect(() => { const interval = setInterval(() => { setProgress((prev) => { if (prev >= 100) { clearInterval(interval); return 100; } return prev + 1; }); }, 50); // Adjust interval for faster or slower progress return () => clearInterval(interval); // Cleanup interval on unmount }, []); return ( <div style={{ width: '200px', border: '1px solid black', padding: '5px' }}> <div style={{ width: `${progress}%`, backgroundColor: 'blue', height: '10px', transition: 'width 0.1s ease-in-out', }} /> <p>{progress}%</p> </div> );}export default Loader;this felt choppy. But now I use requestAnimationFrame
import React, { useEffect, useState } from 'react';function SmoothLoader() { const [progress, setProgress] = useState(0); useEffect(() => { let startTime; function updateProgress(timestamp) { if (!startTime) startTime = timestamp; const elapsed = timestamp - startTime; const newProgress = Math.min((elapsed / 2000) * 100, 100); // 2000ms for full progress setProgress(newProgress); if (newProgress < 100) { requestAnimationFrame(updateProgress); } } requestAnimationFrame(updateProgress); }, []); return ( <div style={{ width: '200px', border: '1px solid black', padding: '5px' }}> <div style={{ width: `${progress}%`, backgroundColor: 'blue', height: '10px', transition: 'width 0.1s ease', }} /> <p>{Math.round(progress)}%</p> </div> );}export default SmoothLoader;This quques setState and re-renders in a good pace.
And, they also stop when tab is inactive.
If this helped, or if I got something wrong — find me on LinkedIn or my site. I read everything.