Feature Detection and Browser Support
1. Feature Detection Techniques (typeof, in, hasOwnProperty)
| Technique | Syntax | Description | Use Case |
|---|---|---|---|
| Property Check | 'method' in object |
Tests if property/method exists in object or prototype chain | Check API availability before use |
| Type Check | typeof obj.method === 'function' |
Verifies property exists and is callable function | Ensure method is executable |
| Feature Test | !!window.feature |
Coerces to boolean to check truthy existence | Quick availability check |
| hasOwnProperty | obj.hasOwnProperty('prop') |
Tests if property exists directly on object (not inherited) | Avoid prototype chain lookups |
| Constructor Check | typeof Constructor !== 'undefined' |
Verifies constructor/class is available | Test for API constructors like Promise, Map |
| Document Test | 'prop' in document.createElement('tag') |
Tests HTML element feature support | Check DOM element capabilities |
Example: Comprehensive feature detection pattern
// Check for fetch API
const hasFetch = typeof fetch === 'function' &&
typeof window.fetch !== 'undefined';
// Check for Promise support
const hasPromise = typeof Promise !== 'undefined' &&
Promise.toString().indexOf('[native code]') !== -1;
// Check for Array.prototype.includes
const hasArrayIncludes = 'includes' in Array.prototype;
// Check for modern DOM API
const hasQuerySelector = 'querySelector' in document;
// Check for Storage API
const hasLocalStorage = (function() {
try {
const test = '__storage_test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch(e) {
return false;
}
})();
2. typeof and in Operator for Checking Features
| Pattern | Syntax | Returns | Best Practice |
|---|---|---|---|
| typeof undefined | typeof variable === 'undefined' |
Safe check for undefined variables | Use for variables that may not be declared |
| typeof function | typeof func === 'function' |
Checks if value is callable function | Validate methods before invocation |
| typeof object | typeof obj === 'object' && obj !== null |
Object check excluding null | Always check for null separately |
| in operator (prototype) | 'method' in Object.prototype |
Checks entire prototype chain | Detect inherited properties |
| in operator (instance) | 'prop' in instance |
Checks own and inherited properties | Most common feature detection |
| hasOwnProperty | obj.hasOwnProperty('prop') |
Only own properties, not inherited | Avoid prototype pollution checks |
Example: typeof vs in operator comparison
// typeof - Safe for undeclared variables
if (typeof Promise !== 'undefined') {
// Promise is available
}
// in operator - Checks property existence
if ('fetch' in window) {
// window.fetch exists
}
// Combined approach for methods
if ('map' in Array.prototype &&
typeof Array.prototype.map === 'function') {
// Array.map is available and callable
}
// Avoid false positives
if (typeof document.querySelector === 'function') {
// Safe to use querySelector
}
// Check constructor availability
if (typeof Map !== 'undefined' &&
typeof Map === 'function') {
const myMap = new Map();
}
3. try-catch Blocks for Safe Feature Testing
| Scenario | Pattern | Error Handling | Use Case |
|---|---|---|---|
| Storage Access | try { localStorage.test } catch(e) {} |
SecurityError, QuotaExceededError | localStorage may throw in private mode |
| Feature Execution | try { new Constructor() } catch(e) {} |
Constructor not available or throws | Test constructor support safely |
| Property Access | try { obj.method() } catch(e) {} |
Method doesn't exist or not callable | Safe method invocation |
| API Test | try { API.test() } catch(e) {} |
API not supported or restricted | Browser security restrictions |
| Eval Alternative | try { new Function('...') } catch(e) {} |
CSP violations, syntax errors | Test CSP-restricted features |
Example: Safe storage detection with try-catch
// localStorage detection with private mode handling
function hasLocalStorage() {
try {
const test = '__test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch(e) {
return false;
}
}
// IndexedDB detection
function hasIndexedDB() {
try {
return !!window.indexedDB;
} catch(e) {
return false;
}
}
// Service Worker registration check
function canUseServiceWorker() {
try {
return 'serviceWorker' in navigator &&
typeof navigator.serviceWorker.register === 'function';
} catch(e) {
return false;
}
}
// Safe feature test with fallback
function detectFeature() {
try {
// Attempt to use feature
new IntersectionObserver(() => {});
return true;
} catch(e) {
// Feature not available
return false;
}
}
Warning: try-catch blocks have performance overhead. Use only when necessary for features that
may throw exceptions. Prefer simple typeof and in
checks for most feature detection.
4. CSS.supports() API for Style Detection
| Method | Syntax | Returns | Browser Support |
|---|---|---|---|
| CSS.supports() | CSS.supports('property', 'value') |
Boolean - true if supported | Modern Browsers |
| Condition String | CSS.supports('display: grid') |
Tests property:value declaration | Single string format |
| Two Arguments | CSS.supports('display', 'grid') |
Tests property and value separately | Property/value pair format |
| Complex Query | CSS.supports('(display: grid) and (gap: 1rem)') |
Tests multiple conditions with logic | Supports and/or/not operators |
| Vendor Prefix | CSS.supports('-webkit-appearance', 'none') |
Tests vendor-prefixed properties | Check legacy prefixed features |
Example: CSS feature detection patterns
// Check for CSS Grid support
const hasGrid = CSS.supports('display', 'grid') ||
CSS.supports('display: grid');
// Check for CSS Custom Properties
const hasVars = CSS.supports('--custom-prop', 'value') ||
CSS.supports('color', 'var(--custom-prop)');
// Check for Flexbox
const hasFlex = CSS.supports('display', 'flex');
// Check for sticky positioning
const hasSticky = CSS.supports('position', 'sticky') ||
CSS.supports('position', '-webkit-sticky');
// Complex condition with logical operators
const hasModernLayout = CSS.supports(
'(display: grid) and (gap: 1rem)'
);
// Fallback for browsers without CSS.supports
function cssSupports(property, value) {
if (typeof CSS !== 'undefined' && CSS.supports) {
return CSS.supports(property, value);
}
// Fallback: test on element style
const el = document.createElement('div');
el.style[property] = value;
return el.style[property] === value;
}
Note: CSS.supports() is available in all modern browsers. For legacy browser support, implement
a fallback using element style testing as shown in the example above.
5. Modernizr Library Integration and Custom Builds
| Feature | Usage | Benefit | Configuration |
|---|---|---|---|
| Feature Classes | html.flexbox .container {} |
CSS classes auto-added to html element | Automatic DOM class injection |
| JavaScript API | if(Modernizr.flexbox) {} |
Check features in JavaScript | Access via Modernizr object |
| Custom Builds | modernizr.com/download | Include only needed feature tests | Reduce bundle size significantly |
| Async Loading | Modernizr.load() |
Conditionally load polyfills | Load resources based on support |
| Custom Tests | Modernizr.addTest('name', fn) |
Add project-specific feature tests | Extend with custom detection |
| Prefixed API | Modernizr.prefixed('transform') |
Get vendor-prefixed property name | Handle browser prefixes automatically |
Example: Modernizr usage patterns
// Check feature support
if (Modernizr.localstorage) {
// Use localStorage
localStorage.setItem('key', 'value');
} else {
// Use cookie fallback
document.cookie = 'key=value';
}
// Conditional polyfill loading (legacy Modernizr.load)
if (!Modernizr.promises) {
// Load promise polyfill
import('es6-promise-polyfill');
}
// Add custom feature test
Modernizr.addTest('customfeature', function() {
return 'customAPI' in window &&
typeof window.customAPI === 'function';
});
// Get prefixed property name
const transformProp = Modernizr.prefixed('transform');
// Returns: 'transform', 'WebkitTransform', 'MozTransform', etc.
element.style[transformProp] = 'rotate(45deg)';
// CSS usage with Modernizr classes
// HTML: <html class="flexbox cssanimations">
// CSS:
// .flexbox .container { display: flex; }
// .no-flexbox .container { display: block; }
Note: Modern alternatives like
@supports in CSS and feature detection APIs have
reduced reliance on Modernizr. Consider if native solutions meet your needs before adding Modernizr dependency.
6. Dynamic Import for Conditional Polyfill Loading
| Pattern | Syntax | Use Case | Benefit |
|---|---|---|---|
| Conditional Import | if(!feature) import('./polyfill.js') |
Load polyfill only when needed | Reduce bundle size for modern browsers |
| Promise-based | import('module').then(m => m.fn()) |
Async module loading with promises | Non-blocking polyfill loading |
| Async/Await | const m = await import('mod') |
Clean async loading syntax | Better readability and error handling |
| Feature Check | feature || await import('poly') |
Short-circuit loading | Skip import if feature exists |
| Multiple Polyfills | Promise.all([import(...)]) |
Load multiple polyfills in parallel | Faster initialization |
| Lazy Initialization | () => import('heavy') |
Defer loading until first use | Improve initial page load |
Example: Dynamic polyfill loading strategies
// Basic conditional polyfill loading
async function loadPolyfills() {
const polyfills = [];
if (typeof Promise === 'undefined') {
polyfills.push(import('es6-promise-polyfill'));
}
if (!('fetch' in window)) {
polyfills.push(import('whatwg-fetch'));
}
if (!Array.prototype.includes) {
polyfills.push(import('./array-includes-polyfill'));
}
await Promise.all(polyfills);
}
// Initialize app after polyfills loaded
loadPolyfills().then(() => {
// Start application
initApp();
});
// Lazy load heavy polyfill on demand
let intersectionObserverPolyfill = null;
async function getIntersectionObserver() {
if ('IntersectionObserver' in window) {
return window.IntersectionObserver;
}
if (!intersectionObserverPolyfill) {
const module = await import('intersection-observer');
intersectionObserverPolyfill = module.default;
}
return intersectionObserverPolyfill;
}
// Usage
const Observer = await getIntersectionObserver();
const observer = new Observer(callback, options);
// Differential serving pattern
if (supportsModernFeatures()) {
// Load modern build (no polyfills)
await import('./app.modern.js');
} else {
// Load legacy build (with polyfills)
await import('./app.legacy.js');
}
Key Takeaways - Feature Detection
- Use typeof for safe checks of potentially undefined variables
- Prefer in operator for property existence in objects/prototypes
- Wrap risky operations (storage, CSP) in try-catch blocks
- Use CSS.supports() for modern CSS feature detection
- Load polyfills conditionally with dynamic imports for optimal performance
- Always test features before use - never assume browser support