JavaScript for Everyone
Summary:
JavaScript has evolved significantly since its introduction in the 1990s. Modern JavaScript development often assumes the availability of recent browser features, build pipelines, transpilers, package managers, and evergreen browser updates. While these technologies have improved developer productivity, they can also reduce the range of clients capable of running an application.
This article explores a compatibility-first approach to JavaScript development. By using broadly supported language features, implementing feature detection, embracing progressive enhancement, and understanding the history of browser scripting engines, developers can deliver applications that remain accessible to the widest possible audience.
Context
The web is often viewed through the lens of modern desktop and mobile browsers. However, many environments continue to rely upon older JavaScript engines and legacy browsers.
Examples include:
- Corporate intranet applications
- Embedded administration interfaces
- Industrial control systems
- Kiosk systems
- Legacy line-of-business applications
- Long-term support operating systems
- Assistive technologies using embedded browser controls
A compatibility-first development strategy does not reject modern features. Instead, it asks a simple question:
How can we deliver the best possible experience to modern browsers while maintaining functional access for older clients?
A Brief History of JavaScript
JavaScript did not emerge fully standardised.
During the early years of the web, multiple browser vendors implemented their own scripting engines.
Notable implementations included:
- Netscape JavaScript
- Microsoft JScript
- Opera's JavaScript implementation
Although the ECMAScript standard eventually brought consistency to the language, browser differences remained significant for many years.
Developers frequently encountered differences in:
- Event handling
- Document Object Model (DOM) behaviour
- Object implementations
- Security models
- Supported language features
An understanding of this history explains why many experienced developers still favour defensive programming techniques and compatibility testing.
Understanding the Compatibility Pyramid
The further back a browser's capabilities extend, the larger the potential audience becomes.
Modern ECMAScript
▲
│
ES Modules
▲
│
ES2015
▲
│
ES5
▲
│
JScript
▲
│
DOM Level 0
Each layer introduces additional capabilities.
Applications designed for maximum compatibility typically establish a foundation at the lower levels and progressively enhance functionality as more capabilities become available.
Why ES5 Remains Important
ECMAScript 5 (ES5) remains one of the most widely supported versions of JavaScript ever released.
Features such as:
- Variables using
var - Function declarations
- Arrays
- Objects
- Regular expressions
- Date handling
- String manipulation
are available across virtually all modern browsers and a large number of legacy environments.
Because of this, ES5 frequently serves as a practical baseline when broad compatibility is required.
Example
<syntaxhighlight lang="javascript"> var message = "Hello World";
function greet(name) {
return "Hello " + name;
} </syntaxhighlight>
This example executes successfully in a wide range of browsers spanning multiple decades.
Progressive Enhancement
Progressive enhancement is the practice of building a solution from a reliable foundation and then adding more advanced functionality when supported.
A common model is:
HTML ▲ │ CSS ▲ │ JavaScript ▲ │ Advanced JavaScript
Every layer enhances the user experience without making the previous layer unusable.
For example:
- HTML provides content.
- CSS improves presentation.
- JavaScript improves interactivity.
- Modern APIs provide additional convenience.
If one layer becomes unavailable, the layers below continue to function.
Graceful Degradation
Graceful degradation is closely related to progressive enhancement.
Rather than asking:
What can the newest browser do?
it asks:
What happens when this feature is unavailable?
For example:
<syntaxhighlight lang="javascript"> if (window.localStorage) {
window.localStorage.setItem("theme", "dark");
} </syntaxhighlight>
The application can continue operating even if persistent storage is unavailable.
The user may lose convenience, but core functionality remains intact.
Feature Detection vs Browser Detection
One of the most important compatibility practices is feature detection.
Avoid Browser Detection
<syntaxhighlight lang="javascript"> if (navigator.userAgent.indexOf("Chrome") > -1) {
// Chrome-specific logic
} </syntaxhighlight>
User agent strings can be inaccurate, spoofed, or change over time.
Prefer Feature Detection
<syntaxhighlight lang="javascript"> if (window.addEventListener) {
// Modern event handling
} else {
// Legacy fallback
} </syntaxhighlight>
Feature detection focuses on actual capability rather than assumptions.
Supporting Multiple Event Models
One of the historic challenges of cross-browser development involved event registration.
Modern Event Registration
<syntaxhighlight lang="javascript"> element.addEventListener("click", handler, false); </syntaxhighlight>
Legacy Internet Explorer
<syntaxhighlight lang="javascript"> element.attachEvent("onclick", handler); </syntaxhighlight>
A compatibility layer can support both:
<syntaxhighlight lang="javascript"> function addEvent(element, eventName, handler) {
if (element.addEventListener)
{
element.addEventListener(eventName, handler, false);
}
else if (element.attachEvent)
{
element.attachEvent("on" + eventName, handler);
}
} </syntaxhighlight>
This pattern proved effective for many years and remains useful when supporting older browsers.
Working with Modern APIs
Modern browsers provide many useful capabilities, including:
- Fetch API
- Promise
- MutationObserver
- IntersectionObserver
- Web Storage
- Service Workers
However, these capabilities should not be assumed.
Example
<syntaxhighlight lang="javascript"> if (window.fetch) {
fetch("/api/data");
} else {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data", true);
xhr.send();
} </syntaxhighlight>
This allows modern browsers to use newer functionality while maintaining support for older environments.
JavaScript Namespaces
Before native modules became widely available, many applications organised code using namespaces.
Example
<syntaxhighlight lang="javascript"> var PiRho = PiRho || {};
PiRho.Data = PiRho.Data || {}; PiRho.Data.Source = {}; </syntaxhighlight>
Benefits include:
- Logical code organisation
- Reduced global namespace pollution
- Broad browser compatibility
- No dependency on module loaders
Many large applications successfully used namespace-based architectures long before ES Modules were introduced.
ES Modules and Compatibility
ES Modules provide a modern mechanism for separating and organising code.
Example
<syntaxhighlight lang="javascript"> import { Widget } from "./Widget.js"; </syntaxhighlight>
Advantages include:
- Explicit dependencies
- Improved maintainability
- Better developer onboarding
- Enhanced tooling support
However, ES Modules should be viewed as an enhancement when broad compatibility is required.
One effective strategy is:
- Author code using ES Modules.
- Transpile modules into a namespace-based format.
- Continue supporting a legacy execution path.
This approach allows modern development practices while preserving compatibility with older browsers.
Common Pitfalls
Assuming Internet Explorer 11 Is the Oldest Browser
Many applications continue to run within environments older than Internet Explorer 11.
Support requirements should be determined by actual audience needs rather than assumptions.
Depending Entirely on Build Pipelines
Build tools can mask compatibility issues.
Developers should understand the code delivered to the browser and the features being relied upon.
Using Modern Features Without Fallbacks
Features such as:
- Arrow functions
- Async/await
- Fetch
- Promises
may require alternative implementations in older environments.
A fallback strategy should always be considered.
Ignoring Non-Browser JavaScript Engines
JavaScript executes in a variety of environments beyond mainstream browsers.
Embedded systems, administrative interfaces, and legacy software may use significantly older scripting engines.
Design and Architecture Considerations
When broad compatibility is a requirement, several principles become important.
Prioritise Stability
Long-term maintainability is often more valuable than adopting the latest language feature.
Optimise for Accessibility
Accessibility and compatibility frequently complement one another.
Applications that function with limited capabilities are often more robust overall.
Keep Dependencies to a Minimum
Every additional dependency introduces:
- Complexity
- Maintenance overhead
- Potential compatibility concerns
Native browser capabilities should be preferred whenever practical.
Understand Your Audience
The appropriate level of compatibility depends upon the intended users.
Public-facing websites often benefit from maximum reach, whereas internal applications may have more controlled requirements.
Practical Rule of Thumb
Before introducing any feature, ask:
Can the application still function if this capability is unavailable?
If the answer is yes, the feature can likely be implemented as a progressive enhancement.
If the answer is no, the feature should be carefully evaluated and justified.
Conclusion
Writing JavaScript for everyone is not about rejecting modern JavaScript. It is about recognising that the web serves an extraordinarily diverse range of users, devices, browsers, and software environments.
By using broadly supported language features, implementing feature detection, embracing progressive enhancement, and understanding the historical evolution of browser scripting engines, developers can create applications that remain useful for the widest possible audience.
Compatibility is not a limitation.
It is a design decision.
Related Topics
- Progressive Enhancement
- Graceful Degradation
- Accessibility
- HTML Compatibility
- Browser Compatibility
- ECMAScript
- JavaScript Namespaces
- ES Modules
References
- ECMAScript Language Specification
- Microsoft JScript Documentation
- W3C DOM Specifications
- MDN Web Docs
- WHATWG HTML Standard
- Browser Compatibility Data (BCD)