JavaScript Environment
Explore JavaScript scope, starting from the global scope to the local scope, and learn how to declare and use variables in different scopes.
Purpose
JavaScript is a versatile programming language that can be used to create interactive elements on a web page. Understanding the scope of variables in JavaScript is essential for writing efficient and maintainable code.
Function Declaration and Scope
When you declare a function in a JavaScript script tag, you can control its accessibility by using the export
keyword. Functions declared with the export
keyword, whether as a classic function or an arrow function, will be accessible from outside the script. For example:
export function myFunc() {
console.log("This is an exported function.");
}
export const myArrowFunc = () => {
console.log("This is an exported arrow function.");
};
If a function does not have the export
keyword, it will be treated as a private function scoped only to the script tag and will not be accessible from the outside. For example:
function privateFunc() {
console.log("This is a private function.");
}
const privateArrowFunc = () => {
console.log("This is a private arrow function.");
};
In summary, use the export
keyword for functions that need to be accessible globally or from other modules, and omit it for private functions that should remain scoped to the script tag.
1. DOMContentLoaded Event:
What it is
: The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.Purpose
: Useful if you want to execute a script after the HTML has been fully parsed but before the entire page (including images, etc.) has been fully loaded.
document.addEventListener("DOMContentLoaded", function() {
// Code to run when the DOM content is fully loaded
});
2 . window.onload Event:
The window.onload
event is triggered when the entire page, including all dependent resources such as images
, iframes
, and stylesheets
, has fully loaded. This is different from DOMContentLoaded
, which only waits for the HTML to be parsed and does not wait for external resources.
Differences Between DOMContentLoaded
and window.onload:
DOMContentLoaded
Fires when the initial HTML document is completely loaded and parsed, without waiting for stylesheets, images, or subframes.
window.onload
: Fires only after the entire content of the page has been loaded, including images, scripts, and stylesheets.
Purpose
Use window.onload when you need to ensure that all external resources (such as images and CSS files) are fully loaded before executing your JavaScript code.
<body>
<script>
// Attaching a window.onload event
window.onload = function() {
console.log("Entire page including resources has been loaded.");
// Example: Dynamically change an image source after the page loads
const img = document.querySelector('#myImage');
if (img) {
img.src = '/path/to/new/image.jpg';
}
};
</script>
<img id="myImage" src="/path/to/initial/image.jpg" alt="Example Image" />
</body>
When to Use: Use window.onload
when your script relies on fully loaded resources, such as images or iframes.
Use DOMContentLoaded
when you only need the HTML to be parsed before executing your script, making it suitable for faster execution without waiting for resources.
3. async Attribute in <script>
Tag:
Loading
: The script is fetched asynchronously (in parallel with the HTML parsing), but its execution happens as soon as the script is downloaded. This can disrupt the order of execution of scripts since the script may execute before or after other scripts.When to use
: Best for scripts that do not rely on the DOM or on each other, such as analytics scripts.Execution order
: No guarantee of execution order if there are multiple scripts with async.
<script async src="script.js"></script>
4. defer Attribute in <script>
Tag:
Loading
: The script is fetched asynchronously (in parallel with the HTML parsing), but execution happens only after the HTML document has been fully parsed (i.e., when the DOMContentLoaded event is about to fire).When to use
: Useful for scripts that rely on the DOM being fully available, but should not block the parsing of the HTML document.Execution order
: Scripts with defer are executed in the order they appear in the document.
<script defer src="script.js"></script>
Global Scope
To load your own JavaScript code, you can use the head
or body
section of your HTML document. You can declare variables and functions in the global scope, which are accessible from anywhere in the script.
The Difference Between Loading in the head
and body
Sections: When you load your JavaScript code in the head
section, the script is executed before the HTML content is loaded. If you load your JavaScript code in the body
section, the script is executed after the HTML content is loaded, which is the normal behavior for most scripts.
Loading JavaScript Code in the head
Section
<head>
<title>Global Scope Example</title>
<script src="<?= Request::baseUrl; ?>/js/custom.js"></script>
</head>
Note: The head
section is typically used to load external resources such as stylesheets, scripts, and metadata. It is recommended to load JavaScript code in the head
section to ensure that the script is executed before the HTML content is loaded.
Loading JavaScript Code in the body
Section
<body>
<h1>Global Scope Example</h1>
<script src="<?= Request::baseUrl; ?>/js/index.js"></script>
</body>
Note: The body
section is typically used to load scripts that interact with the HTML content. By loading the script in the body
section, you ensure that the script is executed after the HTML content is loaded, allowing it to interact with the elements on the page. You can use the Request
class to get the baseUrl
, which will return the path src/app
. Alternatively, you can directly use src/app/your-path
. Prisma PHP will create a new instance of the script and execute it after the HTML content is loaded, making it easier to interact with the elements on the page. This allows you to load your own dynamic script in different sections of the page and make it interact with the elements on the page.
Global scope
Variables declared outside of any function are considered global variables and can be accessed from anywhere in the script. Global variables are not recommended because they can be modified by any part of the script, leading to unexpected behavior.
<head>
<title>Global Scope Example</title>
<script>
// Global scope
var globalVariable = "I am a global variable";
function globalFunction() {
console.log(globalVariable);
}
</script>
</head>
<body>
<h1>Check the console for global variable output</h1>
<script>
// Accessing global variable and function
console.log(globalVariable);
globalFunction();
</script>
</body>
Local Scope
<body>
<script>
// Local scope using an immediately invoked function expression
// If you are using functions remember to expose to the global scope if needed using window.functionName = functionName;
(function () {
var localVariable = "I am a local variable";
function localFunction() {
console.log(localVariable);
}
// Call the function within the local scope
localFunction();
})();
// Trying to access localVariable or localFunction here would result in an error
// console.log(localVariable); // Uncaught ReferenceError: localVariable is not defined
// localFunction(); // Uncaught ReferenceError: localFunction is not defined
</script>
</body>
DOMContentLoaded
The DOMContentLoaded
event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. This event is useful if you want to execute a script after the HTML has been fully parsed but before the entire page (including images, etc.) has been fully loaded.
<body>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Global scope
var globalVariable = "I am a global variable";
// Local scope
let letVariable = "I am a local variable";
// Local scope
const constVariable = "I am a constant variable";
function globalFunction() {
console.log(globalVariable);
}
// Call the global function within DOMContentLoaded
globalFunction();
});
</script>
</body>
onHydrationComplete()
onHydrationComplete
lets you run code after the initial reactive hydration finishes—i.e., once refs, declarative state, attribute/if/loop bindings, and wire listeners are ready and the first batch updates have flushed. Think of it as a reactive equivalent to DOMContentLoaded
tailored for your hydration pipeline.
DOMContentLoaded
fires when the DOM is parsed. onHydrationComplete
fires when the reactive layer is initialized and stable.
Signature
public onHydrationComplete(callback: () => void): () => void
Behavior
- Registers
callback
to run after hydration completes. - If hydration already finished (
_isInitialHydrationComplete === true
), it calls yourcallback
immediately. - Returns an unsubscribe function to remove the listener.
- Safe to register multiple listeners; order is registration order.
When does it fire?
At the end of the hydration pipeline—after:
initRefs
,bootstrapDeclarativeState
,processInlineModuleScripts
initializeAllReferencedProps
,manageAttributeBindings
,processIfChains
,initLoopBindings
attachWireFunctionEvents
(when enabled)- Initial
_bindings
updates flushed in small chunks
Internally this is triggered via notifyHydrationComplete()
at the end of scheduleInitialHydration()
and also after incremental hydration via initReactiveOn(...)
.
Basic Usage
<script>
// Register a one-time effect after hydration:
const off = pphp.onHydrationComplete(() => {
// All reactive values/refs/bindings are ready here.
const firstInput = document.querySelector('input[data-autofocus]');
firstInput?.focus();
});
// Optionally unsubscribe (if you set up multi-use handlers somewhere):
// off();
</script>
Compare with DOMContentLoaded
// DOM is parsed but reactive bindings may NOT be ready yet
document.addEventListener('DOMContentLoaded', () => {
// Do minimal DOM setup only, avoid relying on reactive values here.
});
// Hydration is fully complete; safe to read reactive state/refs/bindings
pphp.onHydrationComplete(() => {
// Ideal for measuring layout, reading computed attrs, or wiring UI logic
});
Common Patterns
1) Focus/Selection after UI mounts
pphp.onHydrationComplete(() => {
const el = document.querySelector('[data-initial-focus]');
el?.focus();
});
2) Safe layout reads / measurements
pphp.onHydrationComplete(() => {
// bindings are applied; sizes are meaningful now
const panel = document.getElementById('metrics-panel');
const rect = panel?.getBoundingClientRect();
console.log('Panel size:', rect?.width, rect?.height);
});
3) One-shot promise helper
function whenHydrated() {
return new Promise((resolve) => pphp.onHydrationComplete(resolve));
}
await whenHydrated();
// ...run code that needs fully ready reactive state
4) Incremental hydration (portals/fragments)
If you hydrate a subtree using initReactiveOn(root, { preserveHierarchy, wire })
, notifyHydrationComplete()
is called at the end, so your
existing onHydrationComplete
listeners will also run after that subtree is ready.
Unsubscribe
const off = pphp.onHydrationComplete(() => {
console.log('Hydration finished');
});
// Later, if you no longer need it:
off();
Internals (for reference)
The hook stores callbacks in _hydrationListeners
. On completion, notifyHydrationComplete()
executes each callback. If _isInitialHydrationComplete
is already true, new subscribers are invoked immediately.
public onHydrationComplete(callback: () => void): () => void {
this._hydrationListeners.add(callback);
if (this._isInitialHydrationComplete) callback();
return () => this._hydrationListeners.delete(callback);
}
Best Practices
- Do use it for focus management, layout reads, and wiring third-party widgets that need the final DOM.
- Avoid heavy reactive writes inside the callback; if needed, queue them via
queueMicrotask
orrequestAnimationFrame
to keep the UI responsive. - If you attach global listeners inside the callback, remember to clean them up when appropriate.
FAQ
Q: Will it run more than once?
A: It runs each time the system calls notifyHydrationComplete()
. That includes initial hydration and any explicit incremental hydration you trigger on a subtree. If you only need a single run, call the returned off()
immediately after the first invocation.
Q: Can I rely on reactive values and bindings inside the callback?
A: Yes. The hook is called after the binding batches flush, so values/attrs are stable.
Q: How is this different from window.onload
?
A: window.onload
waits for all external resources (images, stylesheets). onHydrationComplete
cares about the reactive system being ready—which typically happens sooner and is the right moment to touch reactive state and bound DOM.