Feature Detection and Browser Support Strategies

1. Modern Feature Detection Techniques

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 Usage Patterns

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() for Style Feature 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 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 Feature-based 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