1. Feature Detection and Browser Support Strategies
1.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 APIconst hasFetch = typeof fetch === 'function' && typeof window.fetch !== 'undefined';// Check for Promise supportconst hasPromise = typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]') !== -1;// Check for Array.prototype.includesconst hasArrayIncludes = 'includes' in Array.prototype;// Check for modern DOM APIconst hasQuerySelector = 'querySelector' in document;// Check for Storage APIconst hasLocalStorage = (function() { try { const test = '__storage_test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch(e) { return false; }})();
1.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 variablesif (typeof Promise !== 'undefined') { // Promise is available}// in operator - Checks property existenceif ('fetch' in window) { // window.fetch exists}// Combined approach for methodsif ('map' in Array.prototype && typeof Array.prototype.map === 'function') { // Array.map is available and callable}// Avoid false positivesif (typeof document.querySelector === 'function') { // Safe to use querySelector}// Check constructor availabilityif (typeof Map !== 'undefined' && typeof Map === 'function') { const myMap = new Map();}
1.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 handlingfunction hasLocalStorage() { try { const test = '__test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch(e) { return false; }}// IndexedDB detectionfunction hasIndexedDB() { try { return !!window.indexedDB; } catch(e) { return false; }}// Service Worker registration checkfunction canUseServiceWorker() { try { return 'serviceWorker' in navigator && typeof navigator.serviceWorker.register === 'function'; } catch(e) { return false; }}// Safe feature test with fallbackfunction 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.
1.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 supportconst hasGrid = CSS.supports('display', 'grid') || CSS.supports('display: grid');// Check for CSS Custom Propertiesconst hasVars = CSS.supports('--custom-prop', 'value') || CSS.supports('color', 'var(--custom-prop)');// Check for Flexboxconst hasFlex = CSS.supports('display', 'flex');// Check for sticky positioningconst hasSticky = CSS.supports('position', 'sticky') || CSS.supports('position', '-webkit-sticky');// Complex condition with logical operatorsconst hasModernLayout = CSS.supports( '(display: grid) and (gap: 1rem)');// Fallback for browsers without CSS.supportsfunction 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.
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.
1.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 loadingasync 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 loadedloadPolyfills().then(() => { // Start application initApp();});// Lazy load heavy polyfill on demandlet 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;}// Usageconst Observer = await getIntersectionObserver();const observer = new Observer(callback, options);// Differential serving patternif (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
2. Essential Polyfill Writing Patterns
2.1 Polyfill Structure and Template Patterns
Component
Pattern
Purpose
Example
Feature Detection
if (!Type.prototype.method)
Check if polyfill needed
Prevent overwriting native implementation
IIFE Wrapper
(function() { ... })()
Isolate scope and prevent pollution
Avoid variable leakage to global scope
Strict Mode
'use strict';
Enforce better coding practices
Catch common errors early
Type Checking
if (this == null) throw TypeError
Validate context and arguments
Match native behavior for errors
ToObject Coercion
var O = Object(this)
Convert this to object safely
Handle primitives correctly
Length Coercion
len = O.length >>> 0
Convert length to uint32
Match array length behavior
Example: Complete polyfill template structure
// Standard Polyfill Template(function() { 'use strict'; // Feature detection - exit if already exists if (Array.prototype.includes) { return; } // Polyfill implementation Array.prototype.includes = function(searchElement, fromIndex) { // Step 1: Type check - throw if this is null/undefined if (this == null) { throw new TypeError('Array.prototype.includes called on null or undefined'); } // Step 2: Convert this to object var O = Object(this); // Step 3: Get and convert length to unsigned 32-bit integer var len = parseInt(O.length) || 0; // Step 4: Handle edge cases if (len === 0) { return false; } // Step 5: Process fromIndex parameter var n = parseInt(fromIndex) || 0; var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); // Step 6: Main algorithm while (k < len) { var currentElement = O[k]; // Handle NaN comparison (NaN === NaN should be true) if (searchElement === currentElement || (searchElement !== searchElement && currentElement !== currentElement)) { return true; } k++; } // Step 7: Return result return false; };})();
Note: Always wrap polyfills in an IIFE to avoid polluting global scope. Use strict mode to catch errors and ensure feature
detection runs first to avoid overwriting native implementations.
2.2 Prototype Extension Best Practices
Practice
Do This
Don't Do This
Reason
Check Existence
if (!proto.method)
Unconditional assignment
Preserve native implementation
Use defineProperty
Object.defineProperty()
proto.method = fn
Control enumerability and writability
Non-enumerable
enumerable: false
enumerable: true
Don't break for...in loops
Configurable
configurable: true
configurable: false
Allow future modifications
Test Context
if (this == null) throw
Assume this is valid
Match native error behavior
Handle Edge Cases
Check length, NaN, undefined
Assume happy path
Spec compliance and robustness
Example: Proper prototype extension with defineProperty
// Good: Using Object.definePropertyif (!Array.prototype.find) { Object.defineProperty(Array.prototype, 'find', { value: function(predicate) { if (this == null) { throw new TypeError('this is null or not defined'); } if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } var O = Object(this); var len = O.length >>> 0; var thisArg = arguments[1]; for (var k = 0; k < len; k++) { if (k in O) { var kValue = O[k]; if (predicate.call(thisArg, kValue, k, O)) { return kValue; } } } return undefined; }, configurable: true, writable: true, enumerable: false // Critical: prevents for-in enumeration });}// Bad: Direct assignment (enumerable by default)// Array.prototype.find = function() { ... }; // DON'T DO THIS
Warning: Never extend prototypes of native objects without feature
detection. Always use Object.defineProperty with enumerable: false to avoid breaking existing code.
2.3 Static Method Polyfill Implementation
Type
Pattern
Check Method
Example
Object Static
Object.assign = function() {}
if (!Object.assign)
Object.keys, Object.values, Object.entries
Array Static
Array.from = function() {}
if (!Array.from)
Array.from, Array.of, Array.isArray
Number Static
Number.isNaN = function() {}
if (!Number.isNaN)
Number.isFinite, Number.parseInt
String Static
String.fromCodePoint = function() {}
if (!String.fromCodePoint)
String.raw, String.fromCodePoint
Math Static
Math.trunc = function() {}
if (!Math.trunc)
Math.sign, Math.trunc, Math.cbrt
Example: Static method polyfills
// Object.assign polyfillif (!Object.assign) { Object.assign = function(target) { 'use strict'; if (target == null) { throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { for (var nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; };}// Array.from polyfillif (!Array.from) { Array.from = function(arrayLike, mapFn, thisArg) { var C = this; var items = Object(arrayLike); var len = items.length >>> 0; var A = typeof C === 'function' ? Object(new C(len)) : new Array(len); var k = 0; while (k < len) { var kValue = items[k]; if (mapFn) { A[k] = typeof thisArg === 'undefined' ? mapFn(kValue, k) : mapFn.call(thisArg, kValue, k); } else { A[k] = kValue; } k++; } A.length = len; return A; };}// Number.isNaN polyfill (more accurate than global isNaN)if (!Number.isNaN) { Number.isNaN = function(value) { // NaN is the only value that is not equal to itself return value !== value; };}
Note: Use accessor descriptors (get/set) for computed
properties. Use data descriptors (value/writable) for methods. Never mix both in
the same descriptor.
2.5 UMD and ES Module Polyfill Patterns
Format
Detection
Export Pattern
Use Case
CommonJS
typeof module !== 'undefined'
module.exports = polyfill
Node.js environments
AMD
typeof define === 'function' && define.amd
define([], factory)
RequireJS, legacy browsers
ES Module
Separate .mjs file
export default polyfill
Modern bundlers, native ESM
Global
Fallback for all
window.polyfill = ...
Browser script tags
UMD
Combines all patterns
Check order: AMD, CommonJS, Global
Universal compatibility
Example: UMD wrapper pattern
// Universal Module Definition (UMD) Pattern(function(root, factory) { 'use strict'; // AMD (RequireJS) if (typeof define === 'function' && define.amd) { define([], factory); } // CommonJS (Node.js) else if (typeof module === 'object' && module.exports) { module.exports = factory(); } // Browser globals else { root.myPolyfill = factory(); }}(typeof self !== 'undefined' ? self : this, function() { 'use strict'; // Feature detection if ('myFeature' in window) { return window.myFeature; } // Polyfill implementation function MyPolyfill() { // Implementation here } MyPolyfill.prototype.method = function() { // Method implementation }; // Install globally if needed if (typeof window !== 'undefined') { window.myFeature = MyPolyfill; } // Return for module systems return MyPolyfill;}));// ES Module format (separate file)// polyfill.mjsexport default function polyfill() { if (!Array.prototype.at) { Array.prototype.at = function(index) { var len = this.length; var k = index >= 0 ? index : len + index; return (k >= 0 && k < len) ? this[k] : undefined; }; }}// Usage in ES modules// import polyfill from './polyfill.mjs';// polyfill();
Note: For maximum compatibility, use UMD pattern in polyfills.
Modern projects can use ES modules (.mjs) with build tool transpilation for
legacy support.
2.6 Idempotency and Safe Execution Patterns
Pattern
Implementation
Purpose
Benefit
Existence Check
if (!Type.prototype.method)
Only add if missing
Prevent double-loading issues
Early Return
if (hasFeature) return;
Exit immediately if native exists
Zero overhead for modern browsers
Version Check
if (polyfill.version) return;
Track polyfill installation
Avoid conflicts with other polyfills
Strict Equality
feature === undefined
Precise undefined checking
Distinguish from null or falsy values
IIFE Isolation
(function(){ ... })()
Create private scope
Prevent variable name collisions
Namespace Guard
window.POLYFILLS = {}
Track loaded polyfills
Coordinate multiple polyfill scripts
Example: Idempotent polyfill patterns
// Pattern 1: Simple existence check(function() { 'use strict'; // Early return if already exists if (Array.prototype.includes) { return; } // Polyfill implementation Array.prototype.includes = function(search) { // Implementation... };})();// Pattern 2: Version tracking(function() { 'use strict'; // Check if already loaded if (window.__POLYFILLS__ && window.__POLYFILLS__.arrayIncludes) { return; } // Initialize polyfill registry window.__POLYFILLS__ = window.__POLYFILLS__ || {}; if (!Array.prototype.includes) { Array.prototype.includes = function(search) { // Implementation... }; } // Mark as loaded window.__POLYFILLS__.arrayIncludes = '1.0.0';})();// Pattern 3: Safe multiple execution(function(global) { 'use strict'; // Create polyfill only once function createPolyfill() { // Check each feature individually if (!Array.prototype.find) { Object.defineProperty(Array.prototype, 'find', { value: function(predicate) { /* ... */ }, configurable: true, writable: true, enumerable: false }); } if (!Array.prototype.findIndex) { Object.defineProperty(Array.prototype, 'findIndex', { value: function(predicate) { /* ... */ }, configurable: true, writable: true, enumerable: false }); } } // Safe to call multiple times - only executes missing polyfills createPolyfill();})(typeof window !== 'undefined' ? window : global);
Key Takeaways - Polyfill Writing
Always wrap polyfills in IIFE with strict mode
Use feature detection before adding any polyfill
Set enumerable: false when extending prototypes
Match native error behavior with proper type checking
Use UMD pattern for maximum compatibility
Make polyfills idempotent - safe to load multiple times
if (!Array.prototype.map) { Array.prototype.map = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined'); } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } var O = Object(this); var len = O.length >>> 0; var A = new Array(len); var k = 0; while (k < len) { if (k in O) { A[k] = callback.call(thisArg, O[k], k, O); } k++; } return A; };}
Example: Array.prototype.find and findIndex polyfills
// Array.find polyfillif (!Array.prototype.find) { Array.prototype.find = function(predicate, thisArg) { if (this == null) throw new TypeError('this is null or not defined'); if (typeof predicate !== 'function') throw new TypeError('predicate must be a function'); var O = Object(this); var len = O.length >>> 0; for (var k = 0; k < len; k++) { if (k in O) { var kValue = O[k]; if (predicate.call(thisArg, kValue, k, O)) { return kValue; } } } return undefined; };}// Array.findIndex polyfillif (!Array.prototype.findIndex) { Array.prototype.findIndex = function(predicate, thisArg) { if (this == null) throw new TypeError('this is null or not defined'); if (typeof predicate !== 'function') throw new TypeError('predicate must be a function'); var O = Object(this); var len = O.length >>> 0; for (var k = 0; k < len; k++) { if (k in O && predicate.call(thisArg, O[k], k, O)) { return k; } } return -1; };}
if (!Array.prototype.includes) { Array.prototype.includes = function(searchElement, fromIndex) { if (this == null) { throw new TypeError('this is null or not defined'); } var O = Object(this); var len = O.length >>> 0; if (len === 0) return false; var n = fromIndex | 0; var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); // Handle NaN comparison (NaN === NaN should be true) while (k < len) { var currentElement = O[k]; if (searchElement === currentElement || (searchElement !== searchElement && currentElement !== currentElement)) { return true; } k++; } return false; };}
if (!Object.assign) { Object.assign = function(target) { 'use strict'; if (target == null) { throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { for (var nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; };}
Example: Object.keys, values, and entries polyfills
// Object.keys polyfillif (!Object.keys) { Object.keys = function(obj) { if (obj !== Object(obj)) { throw new TypeError('Object.keys called on non-object'); } var keys = []; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { keys.push(key); } } return keys; };}// Object.values polyfillif (!Object.values) { Object.values = function(obj) { if (obj !== Object(obj)) { throw new TypeError('Object.values called on non-object'); } var values = []; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { values.push(obj[key]); } } return values; };}// Object.entries polyfillif (!Object.entries) { Object.entries = function(obj) { if (obj !== Object(obj)) { throw new TypeError('Object.entries called on non-object'); } var entries = []; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { entries.push([key, obj[key]]); } } return entries; };}// Object.fromEntries polyfillif (!Object.fromEntries) { Object.fromEntries = function(iterable) { var obj = {}; for (var pair of iterable) { if (Object(pair) !== pair) { throw new TypeError('iterable for fromEntries should yield objects'); } obj[pair[0]] = pair[1]; } return obj; };}
// String.prototype.padStartif (!String.prototype.padStart) { String.prototype.padStart = function(targetLength, padString) { targetLength = targetLength >> 0; padString = String(padString !== undefined ? padString : ' '); if (this.length > targetLength) { return String(this); } targetLength = targetLength - this.length; if (targetLength > padString.length) { padString += padString.repeat(targetLength / padString.length); } return padString.slice(0, targetLength) + String(this); };}// String.prototype.padEndif (!String.prototype.padEnd) { String.prototype.padEnd = function(targetLength, padString) { targetLength = targetLength >> 0; padString = String(padString !== undefined ? padString : ' '); if (this.length > targetLength) { return String(this); } targetLength = targetLength - this.length; if (targetLength > padString.length) { padString += padString.repeat(targetLength / padString.length); } return String(this) + padString.slice(0, targetLength); };}// String.prototype.repeat (needed for padStart/padEnd)if (!String.prototype.repeat) { String.prototype.repeat = function(count) { 'use strict'; if (this == null) throw new TypeError('can\'t convert ' + this + ' to object'); var str = '' + this; count = +count; if (count != count) count = 0; if (count < 0) throw new RangeError('repeat count must be non-negative'); if (count == Infinity) throw new RangeError('repeat count must be less than infinity'); count = Math.floor(count); if (str.length == 0 || count == 0) return ''; var maxCount = str.length * count; count = Math.floor(Math.log(count) / Math.log(2)); while (count) { str += str; count--; } str += str.substring(0, maxCount - str.length); return str; };}
3.4 Number Methods (isNaN, isFinite, parseInt, parseFloat)
Method
Syntax
Returns
Key Difference
Number.isNaN
Number.isNaN(value)
true only if value is NaN
Doesn't coerce - more reliable than global isNaN
Number.isFinite
Number.isFinite(value)
true if finite number
Doesn't coerce - stricter than global isFinite
Number.isInteger
Number.isInteger(value)
true if integer number
No decimal part
Number.isSafeInteger
Number.isSafeInteger(value)
true if safe integer
Between -(2^53-1) and 2^53-1
Number.parseInt
Number.parseInt(string, radix)
Integer parsed from string
Same as global parseInt
Number.parseFloat
Number.parseFloat(string)
Float parsed from string
Same as global parseFloat
Example: Number static method polyfills
// Number.isNaN - more reliable than global isNaNif (!Number.isNaN) { Number.isNaN = function(value) { // NaN is the only value that is not equal to itself return typeof value === 'number' && value !== value; };}// Number.isFinite - stricter than global isFiniteif (!Number.isFinite) { Number.isFinite = function(value) { return typeof value === 'number' && isFinite(value); };}// Number.isIntegerif (!Number.isInteger) { Number.isInteger = function(value) { return typeof value === 'number' && isFinite(value) && Math.floor(value) === value; };}// Number.isSafeIntegerif (!Number.isSafeInteger) { Number.isSafeInteger = function(value) { return Number.isInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER; };}// Number.parseInt and parseFloat (ES6 additions)if (!Number.parseInt) { Number.parseInt = parseInt;}if (!Number.parseFloat) { Number.parseFloat = parseFloat;}// Number constants polyfillsif (!Number.EPSILON) { Number.EPSILON = Math.pow(2, -52);}if (!Number.MAX_SAFE_INTEGER) { Number.MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;}if (!Number.MIN_SAFE_INTEGER) { Number.MIN_SAFE_INTEGER = -(Math.pow(2, 53) - 1);}
Note:Number.isNaN() and Number.isFinite() don't coerce values to
numbers, unlike their global counterparts. Use them for stricter type checking: Number.isNaN('NaN')
returns false, while isNaN('NaN') returns true.
3.5 Symbol and Symbol Registry Polyfills
Feature
Syntax
Purpose
Limitations
Symbol()
Symbol(description)
Create unique symbol
Cannot be fully polyfilled (primitives)
Symbol.for()
Symbol.for(key)
Get/create global symbol
Registry can be emulated
Symbol.keyFor()
Symbol.keyFor(sym)
Get key for global symbol
Works with Symbol.for
Well-known Symbols
Symbol.iterator, etc
Standard protocol symbols
Partial support possible
Example: Basic Symbol polyfill (limited functionality)
// Basic Symbol polyfill (cannot create true primitives)if (typeof Symbol === 'undefined') { (function() { var symbolCounter = 0; var symbolRegistry = {}; // Symbol constructor window.Symbol = function Symbol(description) { if (this instanceof Symbol) { throw new TypeError('Symbol is not a constructor'); } var symbol = { __symbol__: true, __description__: description, __id__: symbolCounter++, toString: function() { return 'Symbol(' + (description || '') + ')'; }, valueOf: function() { return this; } }; return symbol; }; // Symbol.for - global symbol registry Symbol.for = function(key) { if (symbolRegistry[key]) { return symbolRegistry[key]; } var symbol = Symbol(key); symbolRegistry[key] = symbol; return symbol; }; // Symbol.keyFor Symbol.keyFor = function(sym) { for (var key in symbolRegistry) { if (symbolRegistry[key] === sym) { return key; } } return undefined; }; // Well-known symbols Symbol.iterator = Symbol('Symbol.iterator'); Symbol.toStringTag = Symbol('Symbol.toStringTag'); Symbol.hasInstance = Symbol('Symbol.hasInstance'); Symbol.toPrimitive = Symbol('Symbol.toPrimitive'); })();}// Usage examplevar sym1 = Symbol('description');var sym2 = Symbol.for('global');var sym3 = Symbol.for('global');console.log(sym2 === sym3); // trueconsole.log(Symbol.keyFor(sym2)); // 'global'
Warning: Symbols cannot be fully polyfilled in ES5 environments as they are primitive types.
Polyfills use objects, which means typeof polyfillSymbol returns 'object' instead of
'symbol'. Use with caution and test thoroughly.
3.6 Iterator and Generator Function Polyfills
Feature
Protocol
Method
Returns
Iterable
[Symbol.iterator]()
Returns iterator object
Object with next() method
Iterator
next()
Advances to next value
{value, done} object
Generator
function*
Creates iterator with yield
Cannot be fully polyfilled
for...of
Consumes iterables
Loops over iterable values
Requires transpilation
Example: Custom iterator implementation
// Add iterator to custom objectvar myIterable = { data: [1, 2, 3, 4, 5], [Symbol.iterator]: function() { var index = 0; var data = this.data; return { next: function() { if (index < data.length) { return { value: data[index++], done: false }; } else { return { done: true }; } } }; }};// Usage with for...of (requires transpilation or native support)// for (var value of myIterable) {// console.log(value); // 1, 2, 3, 4, 5// }// Manual iteration (works in ES5)var iterator = myIterable[Symbol.iterator]();var result = iterator.next();while (!result.done) { console.log(result.value); result = iterator.next();}// Add iterator to Array-like objectsfunction makeIterable(arrayLike) { arrayLike[Symbol.iterator] = function() { var index = 0; var self = this; return { next: function() { if (index < self.length) { return { value: self[index++], done: false }; } return { done: true }; } }; }; return arrayLike;}// Add iterator to String (if not supported)if (!String.prototype[Symbol.iterator]) { String.prototype[Symbol.iterator] = function() { var index = 0; var string = this; return { next: function() { if (index < string.length) { return { value: string[index++], done: false }; } return { done: true }; } }; };}
Note: Generator functions (function*) cannot be polyfilled and require
transpilation with tools like Babel. The regenerator-runtime package provides generator support for environments
without native support.
Key Takeaways - ECMAScript Features
Array methods: map, filter, find for ES5/ES6 compatibility
Object methods: Use Object.assign for merging, entries/values for iteration
String methods: includes, startsWith, endsWith for ES6 string operations
Number methods: Number.isNaN and isFinite provide strict type checking
Symbol polyfills: Limited - use objects but cannot replicate primitive
behavior
Iterators: Can be polyfilled; generators require transpilation
4. ES6+ Modern JavaScript Feature Polyfills
4.1 Promise and Promise.all/race/allSettled Polyfills
Method
Syntax
Returns
Behavior
Promise Constructor
new Promise((resolve, reject) => {})
Promise instance
Creates deferred computation
Promise.resolve
Promise.resolve(value)
Resolved promise with value
Wraps value in resolved promise
Promise.reject
Promise.reject(reason)
Rejected promise with reason
Creates rejected promise
Promise.all
Promise.all(iterable)
Promise of array of values
Resolves when all resolve, rejects on first rejection
Note: Full Promise/A+ compliant polyfill is complex. Use es6-promise or promise-polyfill npm packages for
production. The examples above show simplified implementations for understanding.
Warning: Polyfilled Set and Map use array-based storage with indexOf, which
performs poorly with large datasets and doesn't support object keys properly. Use native implementations when
available.
4.3 WeakSet and WeakMap Implementation
Feature
WeakSet
WeakMap
Key Limitation
Key Type
Objects only
Objects only
Primitives not allowed
Garbage Collection
Weak references
Weak references
Cannot be fully polyfilled
Iteration
Not supported
Not supported
No forEach, keys, values
Size Property
Not available
Not available
Cannot determine size
Methods
add, has, delete
set, get, has, delete
Limited API surface
Example: WeakMap polyfill (limited - no weak references)
// WeakMap polyfill (cannot implement weak references)if (typeof WeakMap === 'undefined') { (function() { var counter = 0; window.WeakMap = function WeakMap() { this._id = '__weakmap_' + (counter++); }; WeakMap.prototype.set = function(key, value) { if (typeof key !== 'object' || key === null) { throw new TypeError('Invalid value used as weak map key'); } // Store data on the key object itself var entry = key[this._id]; if (!entry) { entry = {}; Object.defineProperty(key, this._id, { value: entry, enumerable: false, configurable: true }); } entry.value = value; return this; }; WeakMap.prototype.get = function(key) { if (typeof key !== 'object' || key === null) { return undefined; } var entry = key[this._id]; return entry ? entry.value : undefined; }; WeakMap.prototype.has = function(key) { if (typeof key !== 'object' || key === null) { return false; } return this._id in key; }; WeakMap.prototype.delete = function(key) { if (typeof key !== 'object' || key === null) { return false; } if (this._id in key) { delete key[this._id]; return true; } return false; }; })();}// WeakSet follows similar patternif (typeof WeakSet === 'undefined') { window.WeakSet = function WeakSet() { this._map = new WeakMap(); }; WeakSet.prototype.add = function(value) { if (typeof value !== 'object' || value === null) { throw new TypeError('Invalid value used in weak set'); } this._map.set(value, true); return this; }; WeakSet.prototype.has = function(value) { return this._map.has(value); }; WeakSet.prototype.delete = function(value) { return this._map.delete(value); };}
Warning: WeakMap/WeakSet polyfills cannot implement weak
references in ES5. Objects won't be garbage collected. This is for API compatibility only - not for
memory management.
Warning:Proxy cannot be polyfilled - it requires engine-level
support. For intercepting property access in legacy browsers, use accessor properties (getters/setters) or the
deprecated Object.observe alternatives.
Note: Class syntax requires transpilation with Babel or
TypeScript. Use @babel/preset-env or configure target browsers to automatically transpile classes.
4.6 Async/Await and Generator Polyfills
Feature
Syntax
Polyfill Approach
Runtime Required
Async Functions
async function() {}
Transform to Promise chains
Promise polyfill
Await Keyword
await promise
Transform to .then()
Transpilation + Promise
Generator Functions
function* gen() {}
State machine transformation
regenerator-runtime
Yield Keyword
yield value
Transform to switch/case
regenerator-runtime
Async Generators
async function* gen() {}
Combined transformation
Promise + regenerator
Example: Async/await transpilation pattern
// ES2017 Async/Await Syntaxasync function fetchUserData(id) { try { const user = await fetch('/api/user/' + id); const data = await user.json(); return data; } catch(error) { console.error('Error:', error); throw error; }}// Transpiled to ES5 (Simplified)function fetchUserData(id) { return new Promise(function(resolve, reject) { fetch('/api/user/' + id) .then(function(user) { return user.json(); }) .then(function(data) { resolve(data); }) .catch(function(error) { console.error('Error:', error); reject(error); }); });}// Alternative: Using regenerator-runtimefunction fetchUserData(id) { return regeneratorRuntime.async(function fetchUserData$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: context$1$0.prev = 0; context$1$0.next = 3; return regeneratorRuntime.awrap(fetch('/api/user/' + id)); case 3: user = context$1$0.sent; context$1$0.next = 6; return regeneratorRuntime.awrap(user.json()); case 6: data = context$1$0.sent; return context$1$0.abrupt('return', data); // ... error handling cases } });}
Example: Generator function transpilation
// ES6 Generatorfunction* countGenerator() { yield 1; yield 2; yield 3;}// Transpiled with regenerator-runtimevar countGenerator = regeneratorRuntime.mark(function countGenerator() { return regeneratorRuntime.wrap(function countGenerator$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: context$1$0.next = 2; return 1; case 2: context$1$0.next = 4; return 2; case 4: context$1$0.next = 6; return 3; case 6: case 'end': return context$1$0.stop(); } }, countGenerator, this);});// Usage remains the samevar gen = countGenerator();console.log(gen.next().value); // 1console.log(gen.next().value); // 2console.log(gen.next().value); // 3
Note: Install regenerator-runtime for generator and async/await support in legacy
browsers:
npm install --save regenerator-runtime// In entry file:import 'regenerator-runtime/runtime';
Key Takeaways - ES6+ Features
Promise: Use es6-promise or promise-polyfill for full A+ compliance
Set/Map: Polyfillable but with performance limitations
WeakMap/WeakSet: API compatibility only - no weak references
Proxy: Cannot be polyfilled - requires native support
Class syntax: Requires Babel/TypeScript transpilation
Example: querySelector polyfill for very old browsers
// querySelector polyfill (basic - uses Sizzle or native methods)if (!document.querySelector) { document.querySelector = function(selector) { // Simple ID selector if (selector.charAt(0) === '#') { return document.getElementById(selector.slice(1)); } // Simple class selector if (selector.charAt(0) === '.') { var elements = document.getElementsByClassName ? document.getElementsByClassName(selector.slice(1)) : document.getElementsByTagName('*'); return elements[0] || null; } // Simple tag selector var elements = document.getElementsByTagName(selector); return elements[0] || null; };}// querySelectorAll polyfill (basic)if (!document.querySelectorAll) { document.querySelectorAll = function(selector) { var style = document.createElement('style'); var elements = []; var element; document.documentElement.firstChild.appendChild(style); document._qsa = []; style.styleSheet.cssText = selector + '{x-qsa:expression(document._qsa && document._qsa.push(this))}'; window.scrollBy(0, 0); while (document._qsa.length) { element = document._qsa.shift(); element.style.removeAttribute('x-qsa'); elements.push(element); } document.documentElement.firstChild.removeChild(style); return elements; };}// Modern approach: Use Sizzle library for complex selectors// https://github.com/jquery/sizzle
Example: getElementsByClassName polyfill for IE8
// getElementsByClassName polyfillif (!document.getElementsByClassName) { document.getElementsByClassName = function(className) { return document.querySelectorAll('.' + className); }; Element.prototype.getElementsByClassName = function(className) { return this.querySelectorAll('.' + className); };}// Alternative implementation without querySelectorAllif (!document.getElementsByClassName) { document.getElementsByClassName = function(className) { var elements = document.getElementsByTagName('*'); var result = []; var pattern = new RegExp('(^|\\s)' + className + '(\\s|$)'); for (var i = 0; i < elements.length; i++) { if (pattern.test(elements[i].className)) { result.push(elements[i]); } } return result; };}
Note: For complex CSS selector support in legacy browsers, use Sizzle library (jQuery's selector engine) or limit to simple selectors (ID, class,
tag).
5.2 Element.classList and className Manipulation
Method
Syntax
Description
Returns
classList.add
element.classList.add('class1', 'class2')
Adds one or more classes
undefined
classList.remove
element.classList.remove('class1')
Removes one or more classes
undefined
classList.toggle
element.classList.toggle('class')
Toggles class presence
Boolean - true if added
classList.contains
element.classList.contains('class')
Checks if class exists
Boolean
classList.replace
element.classList.replace('old', 'new')
Replaces one class with another
Boolean - true if replaced
classList.item
element.classList.item(index)
Returns class at index
String or null
Example: classList polyfill for IE9
// classList polyfillif (!('classList' in document.createElement('_'))) { (function(view) { 'use strict'; if (!('Element' in view)) return; var classListProp = 'classList', protoProp = 'prototype', elemCtrProto = view.Element[protoProp], objCtr = Object, strTrim = String[protoProp].trim || function() { return this.replace(/^\s+|\s+$/g, ''); }, arrIndexOf = Array[protoProp].indexOf || function(item) { var i = 0, len = this.length; for (; i < len; i++) { if (i in this && this[i] === item) { return i; } } return -1; }; var DOMTokenList = function(elem) { var classes = strTrim.call(elem.getAttribute('class') || ''); var tokens = classes ? classes.split(/\s+/) : []; var i = 0; var len = tokens.length; for (; i < len; i++) { this.push(tokens[i]); } this._updateClassName = function() { elem.setAttribute('class', this.toString()); }; }; var classListProto = DOMTokenList[protoProp] = []; classListProto.item = function(i) { return this[i] || null; }; classListProto.contains = function(token) { token += ''; return arrIndexOf.call(this, token) !== -1; }; classListProto.add = function() { var tokens = arguments; var i = 0; var len = tokens.length; var token; var updated = false; do { token = tokens[i] + ''; if (arrIndexOf.call(this, token) === -1) { this.push(token); updated = true; } } while (++i < len); if (updated) { this._updateClassName(); } }; classListProto.remove = function() { var tokens = arguments; var i = 0; var len = tokens.length; var token; var updated = false; var index; do { token = tokens[i] + ''; index = arrIndexOf.call(this, token); while (index !== -1) { this.splice(index, 1); updated = true; index = arrIndexOf.call(this, token); } } while (++i < len); if (updated) { this._updateClassName(); } }; classListProto.toggle = function(token, force) { token += ''; var result = this.contains(token); var method = result ? force !== true && 'remove' : force !== false && 'add'; if (method) { this[method](token); } return (force === true || force === false) ? force : !result; }; classListProto.toString = function() { return this.join(' '); }; // Expose as property if (objCtr.defineProperty) { var classListPropDesc = { get: function() { return new DOMTokenList(this); }, enumerable: true, configurable: true }; try { objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } catch(ex) { // IE8 doesn't support enumerable: true objCtr.defineProperty(elemCtrProto, classListProp, { get: classListPropDesc.get }); } } }(window));}
Note:classList is supported in IE10+. For IE9 and below, this polyfill provides
full API compatibility including multiple class names in add/remove.
5.3 addEventListener and Event Model Polyfills
Method
Standard
IE Legacy
Key Difference
Add Listener
addEventListener(type, fn, capture)
attachEvent('on'+type, fn)
Event name prefix, this context
Remove Listener
removeEventListener(type, fn, capture)
detachEvent('on'+type, fn)
Must use same function reference
Event Object
Passed as parameter
window.event
Access method differs
Prevent Default
event.preventDefault()
event.returnValue = false
Different property
Stop Propagation
event.stopPropagation()
event.cancelBubble = true
Different property
Event Target
event.target
event.srcElement
Different property name
Example: addEventListener polyfill for IE8
// addEventListener polyfill for IE8if (!Element.prototype.addEventListener) { Element.prototype.addEventListener = function(type, fn, capture) { var element = this; // Create wrapper to fix context and event object var wrapper = function(e) { e = e || window.event; // Fix event object e.target = e.target || e.srcElement; e.currentTarget = element; e.preventDefault = e.preventDefault || function() { e.returnValue = false; }; e.stopPropagation = e.stopPropagation || function() { e.cancelBubble = true; }; // Call with correct context return fn.call(element, e); }; // Store wrapper for removal if (!element._eventListeners) { element._eventListeners = {}; } if (!element._eventListeners[type]) { element._eventListeners[type] = []; } element._eventListeners[type].push({ listener: fn, wrapper: wrapper }); // Attach event element.attachEvent('on' + type, wrapper); }; Element.prototype.removeEventListener = function(type, fn, capture) { if (!this._eventListeners || !this._eventListeners[type]) { return; } var listeners = this._eventListeners[type]; for (var i = 0; i < listeners.length; i++) { if (listeners[i].listener === fn) { this.detachEvent('on' + type, listeners[i].wrapper); listeners.splice(i, 1); break; } } }; // Also add to Window and Document if (!window.addEventListener) { Window.prototype.addEventListener = Element.prototype.addEventListener; Window.prototype.removeEventListener = Element.prototype.removeEventListener; Document.prototype.addEventListener = Element.prototype.addEventListener; Document.prototype.removeEventListener = Element.prototype.removeEventListener; }}// Event object normalizationfunction normalizeEvent(e) { e = e || window.event; if (!e.target) e.target = e.srcElement; if (!e.currentTarget) e.currentTarget = this; if (!e.relatedTarget) e.relatedTarget = e.fromElement || e.toElement; if (!e.which) e.which = e.charCode || e.keyCode; if (!e.preventDefault) { e.preventDefault = function() { e.returnValue = false; }; } if (!e.stopPropagation) { e.stopPropagation = function() { e.cancelBubble = true; }; } return e;}
Warning: IE8's attachEvent executes handlers in reverse
order and with different this context (window instead of element). The polyfill fixes
these issues.
// AbortController polyfill (basic implementation)(function() { if ('AbortController' in window) return; function AbortSignal() { this.aborted = false; this.onabort = null; this._listeners = []; } AbortSignal.prototype.addEventListener = function(type, listener) { if (type === 'abort') { this._listeners.push(listener); } }; AbortSignal.prototype.removeEventListener = function(type, listener) { if (type === 'abort') { var index = this._listeners.indexOf(listener); if (index !== -1) { this._listeners.splice(index, 1); } } }; AbortSignal.prototype.dispatchEvent = function() { if (this.onabort) this.onabort(); this._listeners.forEach(function(listener) { listener(); }); }; function AbortController() { this.signal = new AbortSignal(); } AbortController.prototype.abort = function() { if (this.signal.aborted) return; this.signal.aborted = true; this.signal.dispatchEvent(); }; window.AbortController = AbortController; window.AbortSignal = AbortSignal;})();// Usage with fetchvar controller = new AbortController();var signal = controller.signal;fetch('/api/data', { signal: signal }) .then(function(response) { return response.json(); }) .catch(function(error) { if (error.name === 'AbortError') { console.log('Fetch aborted'); } });// Cancel request after 5 secondssetTimeout(function() { controller.abort();}, 5000);
Note: For production use, install whatwg-fetch and
abortcontroller-polyfill packages for full spec compliance. The examples above are simplified for
understanding.
// FormData polyfill for missing methods (IE11)(function() { if ('FormData' in window) { var originalFormData = window.FormData; // Add get method if missing if (!FormData.prototype.get) { FormData.prototype.get = function(name) { var entries = this.getAll(name); return entries.length ? entries[0] : null; }; } // Add getAll method if missing if (!FormData.prototype.getAll) { FormData.prototype.getAll = function(name) { // Fallback: cannot iterate in IE, return empty array return []; }; } // Add has method if missing if (!FormData.prototype.has) { FormData.prototype.has = function(name) { return this.get(name) !== null; }; } // Add set method if missing if (!FormData.prototype.set) { FormData.prototype.set = function(name, value, filename) { this.delete(name); this.append(name, value, filename); }; } // Add delete method if missing if (!FormData.prototype.delete) { FormData.prototype.delete = function(name) { // Cannot be implemented in IE }; } // Add entries iterator if missing if (!FormData.prototype.entries) { FormData.prototype.entries = function() { throw new Error('FormData.entries() not supported in this browser'); }; } }})();// Usagevar formData = new FormData();formData.append('username', 'john_doe');formData.append('email', 'john@example.com');formData.append('avatar', fileInput.files[0]);// Send via fetchfetch('/api/profile', { method: 'POST', body: formData});
Example: File constructor polyfill
// File constructor polyfill(function() { try { new File([], ''); return; // Native support } catch(e) {} // Polyfill File constructor window.File = function(bits, name, options) { options = options || {}; var blob = new Blob(bits, options); blob.name = name; blob.lastModified = options.lastModified || Date.now(); blob.lastModifiedDate = new Date(blob.lastModified); // Make it look like a File Object.defineProperty(blob, 'constructor', { value: File }); return blob; }; File.prototype = Object.create(Blob.prototype); File.prototype.constructor = File;})();// Usagevar file = new File(['Hello, World!'], 'hello.txt', { type: 'text/plain', lastModified: Date.now()});console.log(file.name); // 'hello.txt'console.log(file.size); // 13
Warning: FormData iteration methods (entries(), keys(),
values()) cannot be fully polyfilled in IE11. Use form-data polyfill
package for complete support.
// Simplified ResizeObserver polyfill (conceptual)// For production, use: https://github.com/que-etc/resize-observer-polyfill(function() { if ('ResizeObserver' in window) return; function ResizeObserver(callback) { this.callback = callback; this.elements = new Map(); var self = this; this._checkSizes = function() { var entries = []; self.elements.forEach(function(lastSize, element) { var rect = element.getBoundingClientRect(); var currentSize = { width: rect.width, height: rect.height }; if (lastSize.width !== currentSize.width || lastSize.height !== currentSize.height) { entries.push({ target: element, contentRect: rect }); self.elements.set(element, currentSize); } }); if (entries.length) { self.callback(entries, self); } }; // Poll for size changes (not performant - use real polyfill) this.interval = setInterval(this._checkSizes, 100); } ResizeObserver.prototype.observe = function(element) { var rect = element.getBoundingClientRect(); this.elements.set(element, { width: rect.width, height: rect.height }); }; ResizeObserver.prototype.unobserve = function(element) { this.elements.delete(element); }; ResizeObserver.prototype.disconnect = function() { clearInterval(this.interval); this.elements.clear(); }; window.ResizeObserver = ResizeObserver;})();// Usagevar resizeObserver = new ResizeObserver(function(entries) { entries.forEach(function(entry) { console.log('Element resized:', entry.target); console.log('New size:', entry.contentRect.width, entry.contentRect.height); });});resizeObserver.observe(document.querySelector('.responsive-element'));
Warning: The IntersectionObserver and ResizeObserver polyfills shown are simplified for demonstration. They use polling which is not performant. For
production, use official polyfills: intersection-observer and resize-observer-polyfill
npm packages.
Key Takeaways - Web APIs
Fetch API: Use whatwg-fetch for full spec compliance with XHR fallback
URLSearchParams: Polyfillable with manual query string parsing
FormData: IE10+ support, iteration methods need polyfills
Blob/FileReader: Convert callback-based APIs to Promises
requestAnimationFrame: Fallback to setTimeout with 16ms delay
Observers: Use official polyfills - custom implementations are complex
7. Storage and Data Management Polyfills
7.1 localStorage and sessionStorage Polyfills
API
Method
Description
Persistence
localStorage
localStorage.setItem(key, value)
Store data persistently
Until explicitly deleted
sessionStorage
sessionStorage.setItem(key, value)
Store data for session
Until tab/window closed
getItem
storage.getItem(key)
Retrieve stored value
Returns string or null
removeItem
storage.removeItem(key)
Delete specific item
-
clear
storage.clear()
Delete all items
-
key
storage.key(index)
Get key by index
Returns key name or null
length
storage.length
Number of stored items
Read-only property
Example: localStorage polyfill using cookies
// localStorage polyfill for older browsers (IE7 and below)(function() { if ('localStorage' in window && window.localStorage !== null) { return; } var Storage = function() { this.length = 0; this._data = {}; }; Storage.prototype.setItem = function(key, value) { if (!(key in this._data)) { this.length++; } this._data[key] = String(value); this._save(); }; Storage.prototype.getItem = function(key) { return this._data.hasOwnProperty(key) ? this._data[key] : null; }; Storage.prototype.removeItem = function(key) { if (key in this._data) { delete this._data[key]; this.length--; this._save(); } }; Storage.prototype.clear = function() { this._data = {}; this.length = 0; this._save(); }; Storage.prototype.key = function(index) { var keys = Object.keys(this._data); return index >= 0 && index < keys.length ? keys[index] : null; }; Storage.prototype._save = function() { // Save to cookie (with size limits) var serialized = JSON.stringify(this._data); document.cookie = this._cookieName + '=' + encodeURIComponent(serialized) + '; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/'; }; Storage.prototype._load = function() { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i].trim(); if (cookie.indexOf(this._cookieName + '=') === 0) { var value = cookie.substring(this._cookieName.length + 1); try { this._data = JSON.parse(decodeURIComponent(value)); this.length = Object.keys(this._data).length; } catch(e) {} break; } } }; // Create localStorage var localStorage = new Storage(); localStorage._cookieName = '__localStorage__'; localStorage._load(); // Create sessionStorage var sessionStorage = new Storage(); sessionStorage._cookieName = '__sessionStorage__'; sessionStorage._load(); // Make read-only try { Object.defineProperty(window, 'localStorage', { value: localStorage, writable: false }); Object.defineProperty(window, 'sessionStorage', { value: sessionStorage, writable: false }); } catch(e) { window.localStorage = localStorage; window.sessionStorage = sessionStorage; }})();
Warning: localStorage may throw SecurityError in private
browsing mode or when storage quota is exceeded. Always wrap storage operations in try-catch
blocks.
7.2 IndexedDB Compatibility and Polyfills
Feature
API
Description
Support
Database
indexedDB.open(name, version)
Open/create database
IE10+
Object Store
db.createObjectStore(name, options)
Create table equivalent
In onupgradeneeded callback
Transaction
db.transaction(stores, mode)
Create transaction scope
Modes: readonly, readwrite
CRUD Operations
store.add/get/put/delete()
Data manipulation
Returns IDBRequest
Cursors
store.openCursor()
Iterate over records
Event-driven iteration
Vendor Prefixes
webkitIndexedDB, mozIndexedDB
Legacy browser support
Prefix normalization needed
Example: IndexedDB vendor prefix normalization
// Normalize IndexedDB across browserswindow.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;// Check if IndexedDB is availableif (!window.indexedDB) { console.warn('IndexedDB not supported');}// Promise wrapper for IndexedDBvar IDB = { open: function(name, version) { return new Promise(function(resolve, reject) { var request = indexedDB.open(name, version); request.onerror = function() { reject(request.error); }; request.onsuccess = function() { resolve(request.result); }; request.onupgradeneeded = function(event) { var db = event.target.result; // Handle upgrades if (!db.objectStoreNames.contains('store')) { db.createObjectStore('store', { keyPath: 'id', autoIncrement: true }); } }; }); }, add: function(db, storeName, data) { return new Promise(function(resolve, reject) { var transaction = db.transaction([storeName], 'readwrite'); var store = transaction.objectStore(storeName); var request = store.add(data); request.onsuccess = function() { resolve(request.result); }; request.onerror = function() { reject(request.error); }; }); }, get: function(db, storeName, key) { return new Promise(function(resolve, reject) { var transaction = db.transaction([storeName], 'readonly'); var store = transaction.objectStore(storeName); var request = store.get(key); request.onsuccess = function() { resolve(request.result); }; request.onerror = function() { reject(request.error); }; }); }};// UsageIDB.open('myDatabase', 1) .then(function(db) { return IDB.add(db, 'store', { name: 'John', age: 30 }); }) .then(function(id) { console.log('Added with ID:', id); }) .catch(function(error) { console.error('IndexedDB error:', error); });
Note: IndexedDB is asynchronous and event-driven. Use Promise
wrappers or libraries like idb or localForage for easier API.
7.3 Cookie API and Document.cookie Extensions
Operation
Method
Options
Notes
Set Cookie
document.cookie = 'key=value'
expires, path, domain, secure, samesite
Append, not replace
Get Cookie
Parse document.cookie
-
Returns all cookies as string
Delete Cookie
Set with past expiration
expires=Thu, 01 Jan 1970
Must match path/domain
Size Limit
~4KB per cookie
~20-50 cookies per domain
Browser dependent
HttpOnly
Server-side only flag
Cannot access via JavaScript
Security feature
SameSite
SameSite=Strict|Lax|None
CSRF protection
Modern browsers
Example: Cookie utility library
// Cookie utility functionsvar Cookies = { set: function(name, value, options) { options = options || {}; var cookieString = encodeURIComponent(name) + '=' + encodeURIComponent(value); // Expires if (options.expires) { var expires; if (typeof options.expires === 'number') { expires = new Date(); expires.setTime(expires.getTime() + options.expires * 24 * 60 * 60 * 1000); } else { expires = options.expires; } cookieString += '; expires=' + expires.toUTCString(); } // Path cookieString += '; path=' + (options.path || '/'); // Domain if (options.domain) { cookieString += '; domain=' + options.domain; } // Secure if (options.secure) { cookieString += '; secure'; } // SameSite if (options.sameSite) { cookieString += '; samesite=' + options.sameSite; } document.cookie = cookieString; }, get: function(name) { var nameEQ = encodeURIComponent(name) + '='; var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i].trim(); if (cookie.indexOf(nameEQ) === 0) { return decodeURIComponent(cookie.substring(nameEQ.length)); } } return null; }, remove: function(name, options) { options = options || {}; options.expires = -1; this.set(name, '', options); }, getAll: function() { var cookies = {}; var cookieArray = document.cookie.split(';'); for (var i = 0; i < cookieArray.length; i++) { var cookie = cookieArray[i].trim(); var parts = cookie.split('='); if (parts.length === 2) { cookies[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); } } return cookies; }, has: function(name) { return this.get(name) !== null; }};// UsageCookies.set('username', 'john_doe', { expires: 7, // 7 days path: '/', secure: true, sameSite: 'Lax'});var username = Cookies.get('username');console.log(username); // 'john_doe'Cookies.remove('username');var allCookies = Cookies.getAll();console.log(allCookies);
Example: Storage wrapper using cookies as fallback
// Universal storage with cookie fallbackvar UniversalStorage = { _useLocalStorage: (function() { try { var test = '__test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch(e) { return false; } })(), setItem: function(key, value, days) { if (this._useLocalStorage) { try { localStorage.setItem(key, value); return; } catch(e) {} } // Fallback to cookies Cookies.set(key, value, { expires: days || 365 }); }, getItem: function(key) { if (this._useLocalStorage) { try { return localStorage.getItem(key); } catch(e) {} } // Fallback to cookies return Cookies.get(key); }, removeItem: function(key) { if (this._useLocalStorage) { try { localStorage.removeItem(key); return; } catch(e) {} } // Fallback to cookies Cookies.remove(key); }};// Usage - automatically uses localStorage or cookiesUniversalStorage.setItem('token', 'abc123');var token = UniversalStorage.getItem('token');
Warning: Cookies have ~4KB size limit per cookie and are sent
with every HTTP request. Use localStorage for larger data. Never store sensitive data in cookies without
Secure and HttpOnly flags.
7.4 WebSQL to IndexedDB Migration Polyfills
Feature
WebSQL
IndexedDB
Status
Standard
W3C Working Draft
W3C Recommendation
WEBSQL DEPRECATED
API Style
SQL queries
NoSQL key-value
Different paradigms
Browser Support
Chrome, Safari (removed in new versions)
All modern browsers
Migrate to IndexedDB
Storage Limit
~5-10MB default
~50MB+ (quota API)
IndexedDB more flexible
Transactions
SQL transactions
Object store transactions
Both support ACID
Example: WebSQL migration helper
// WebSQL migration utilityvar WebSQLMigration = { hasWebSQL: function() { return 'openDatabase' in window; }, hasIndexedDB: function() { return 'indexedDB' in window; }, migrateFromWebSQL: function(webSQLDB, indexedDBName, tableName, storeName) { return new Promise(function(resolve, reject) { if (!this.hasWebSQL() || !this.hasIndexedDB()) { reject(new Error('Required APIs not available')); return; } // Read from WebSQL webSQLDB.transaction(function(tx) { tx.executeSql('SELECT * FROM ' + tableName, [], function(tx, results) { var rows = []; for (var i = 0; i < results.rows.length; i++) { rows.push(results.rows.item(i)); } // Write to IndexedDB var request = indexedDB.open(indexedDBName, 1); request.onupgradeneeded = function(e) { var db = e.target.result; if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName, { keyPath: 'id' }); } }; request.onsuccess = function(e) { var db = e.target.result; var transaction = db.transaction([storeName], 'readwrite'); var store = transaction.objectStore(storeName); rows.forEach(function(row) { store.add(row); }); transaction.oncomplete = function() { resolve(rows.length); }; transaction.onerror = function() { reject(transaction.error); }; }; request.onerror = function() { reject(request.error); }; }); }); }.bind(this)); }};// Usagevar db = openDatabase('mydb', '1.0', 'My Database', 2 * 1024 * 1024);WebSQLMigration.migrateFromWebSQL(db, 'myIndexedDB', 'users', 'users') .then(function(count) { console.log('Migrated ' + count + ' records'); }) .catch(function(error) { console.error('Migration failed:', error); });
Note: WebSQL is deprecated and removed from modern browsers.
Migrate existing WebSQL data to IndexedDB or use libraries like localForage that abstract storage.
7.5 Memory Storage Fallback Implementations
Strategy
Persistence
Use Case
Limitations
In-Memory Object
Session only (cleared on reload)
Last resort fallback
No persistence, lost on refresh
sessionStorage
Tab/window session
Temporary data for session
Lost when tab closed
localStorage
Persistent across sessions
Primary storage method
May fail in private mode
Cookie
Persistent (with expiration)
Fallback for localStorage
4KB limit, sent with requests
IndexedDB
Persistent, large storage
Complex data, large datasets
Async API complexity
Example: Complete storage fallback implementation
// Storage facade with fallback chainvar StorageFacade = (function() { 'use strict'; var storageType = null; var memoryStorage = {}; // Test storage availability function testStorage(type) { try { var storage = window[type]; var test = '__storage_test__'; storage.setItem(test, test); storage.removeItem(test); return true; } catch(e) { return false; } } // Determine best available storage function init() { if (testStorage('localStorage')) { storageType = 'localStorage'; } else if (testStorage('sessionStorage')) { storageType = 'sessionStorage'; } else if ('cookie' in document) { storageType = 'cookie'; } else { storageType = 'memory'; } console.log('Using storage type:', storageType); } function setItem(key, value) { switch(storageType) { case 'localStorage': case 'sessionStorage': try { window[storageType].setItem(key, value); } catch(e) { console.warn('Storage quota exceeded, falling back to memory'); memoryStorage[key] = value; } break; case 'cookie': Cookies.set(key, value, { expires: 365 }); break; case 'memory': default: memoryStorage[key] = value; break; } } function getItem(key) { switch(storageType) { case 'localStorage': case 'sessionStorage': try { return window[storageType].getItem(key); } catch(e) { return memoryStorage[key] || null; } case 'cookie': return Cookies.get(key); case 'memory': default: return memoryStorage[key] || null; } } function removeItem(key) { switch(storageType) { case 'localStorage': case 'sessionStorage': try { window[storageType].removeItem(key); } catch(e) { delete memoryStorage[key]; } break; case 'cookie': Cookies.remove(key); break; case 'memory': default: delete memoryStorage[key]; break; } } function clear() { switch(storageType) { case 'localStorage': case 'sessionStorage': try { window[storageType].clear(); } catch(e) { memoryStorage = {}; } break; case 'cookie': var cookies = Cookies.getAll(); Object.keys(cookies).forEach(function(key) { Cookies.remove(key); }); break; case 'memory': default: memoryStorage = {}; break; } } function getStorageType() { return storageType; } // Initialize on load init(); // Public API return { setItem: setItem, getItem: getItem, removeItem: removeItem, clear: clear, getStorageType: getStorageType };})();// Usage - automatically uses best available storageStorageFacade.setItem('user', JSON.stringify({ name: 'John', age: 30 }));var user = JSON.parse(StorageFacade.getItem('user'));console.log('Using storage:', StorageFacade.getStorageType());
localStorage/sessionStorage: Always wrap in try-catch for private mode
handling
IndexedDB: Normalize vendor prefixes, use Promise wrappers for easier API
Cookies: 4KB limit, use for fallback or when persistence required
WebSQL: Deprecated - migrate to IndexedDB immediately
Fallback strategy: localStorage → sessionStorage → cookies → memory
Libraries: Consider localForage or similar for abstraction
8. CSS and Styling Polyfills
8.1 CSS Custom Properties (Variables) Polyfills
Feature
Syntax
Browser Support
Polyfill Method
Define Variable
:root { --main-color: #06c; }
Modern browsers
Parse CSS, replace with computed values
Use Variable
color: var(--main-color);
IE not supported
Runtime substitution
Fallback Value
var(--color, blue)
Second parameter
Parse fallback in polyfill
JavaScript Access
getComputedStyle().getPropertyValue('--var')
Modern only
Custom property tracking
Dynamic Update
element.style.setProperty('--var', value)
Modern only
Watch for changes, re-apply
Example: CSS Custom Properties polyfill
// CSS Variables polyfill for IE11(function() { if (window.CSS && CSS.supports && CSS.supports('color', 'var(--test)')) { return; // Native support } var CSSVariables = { variables: {}, init: function() { this.parseStyleSheets(); this.applyVariables(); this.watchForChanges(); }, parseStyleSheets: function() { var sheets = document.styleSheets; for (var i = 0; i < sheets.length; i++) { try { var rules = sheets[i].cssRules || sheets[i].rules; if (!rules) continue; for (var j = 0; j < rules.length; j++) { this.parseRule(rules[j]); } } catch(e) { // Cross-origin stylesheets may throw console.warn('Cannot access stylesheet:', e); } } }, parseRule: function(rule) { // Parse variable definitions from :root or custom selectors if (rule.selectorText && (rule.selectorText.indexOf(':root') >= 0 || rule.selectorText.indexOf('html') >= 0)) { var style = rule.style; for (var i = 0; i < style.length; i++) { var prop = style[i]; if (prop.indexOf('--') === 0) { this.variables[prop] = style.getPropertyValue(prop).trim(); } } } // Parse @media rules recursively if (rule.cssRules) { for (var i = 0; i < rule.cssRules.length; i++) { this.parseRule(rule.cssRules[i]); } } }, applyVariables: function() { var sheets = document.styleSheets; for (var i = 0; i < sheets.length; i++) { try { var rules = sheets[i].cssRules || sheets[i].rules; if (!rules) continue; for (var j = 0; j < rules.length; j++) { this.processRule(rules[j]); } } catch(e) {} } // Also process inline styles var elements = document.querySelectorAll('[style*="var("]'); for (var i = 0; i < elements.length; i++) { this.processInlineStyle(elements[i]); } }, processRule: function(rule) { if (!rule.style) return; var style = rule.style; var changes = {}; for (var i = 0; i < style.length; i++) { var prop = style[i]; var value = style.getPropertyValue(prop); if (value.indexOf('var(') >= 0) { var resolved = this.resolveValue(value); changes[prop] = resolved; } } // Apply resolved values for (var prop in changes) { style.setProperty(prop, changes[prop], style.getPropertyPriority(prop)); } // Process nested rules if (rule.cssRules) { for (var i = 0; i < rule.cssRules.length; i++) { this.processRule(rule.cssRules[i]); } } }, processInlineStyle: function(element) { var style = element.getAttribute('style'); if (!style) return; var resolved = this.resolveValue(style); if (resolved !== style) { element.setAttribute('style', resolved); } }, resolveValue: function(value) { var self = this; // Match var(--variable-name) or var(--variable-name, fallback) return value.replace(/var\(\s*(--[^,\)]+)(?:\s*,\s*([^\)]+))?\s*\)/g, function(match, varName, fallback) { var resolved = self.variables[varName.trim()]; if (resolved !== undefined) { return resolved; } else if (fallback) { return fallback.trim(); } return match; // Keep original if not found }); }, watchForChanges: function() { // Watch for new style elements if (window.MutationObserver) { var observer = new MutationObserver(function(mutations) { var needsUpdate = false; mutations.forEach(function(mutation) { if (mutation.addedNodes.length) { needsUpdate = true; } }); if (needsUpdate) { setTimeout(function() { this.parseStyleSheets(); this.applyVariables(); }.bind(this), 100); } }.bind(this)); observer.observe(document.documentElement, { childList: true, subtree: true }); } }, // Public API for JavaScript access getVariable: function(name) { return this.variables[name]; }, setVariable: function(name, value) { this.variables[name] = value; this.applyVariables(); } }; // Initialize on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { CSSVariables.init(); }); } else { CSSVariables.init(); } // Expose API window.CSSVariablesPolyfill = CSSVariables;})();
Note: CSS variable polyfills like css-vars-ponyfill provide more complete
implementations with better performance. This example shows the basic concept.
// CSS Grid gap polyfill for IE11(function() { // Check if gap is supported if (CSS.supports('gap', '1px')) { return; } var GridGapPolyfill = { init: function() { var grids = document.querySelectorAll('[style*="grid"], .grid, [class*="grid"]'); for (var i = 0; i < grids.length; i++) { this.processGrid(grids[i]); } }, processGrid: function(grid) { var computed = window.getComputedStyle(grid); // Check for grid display if (computed.display.indexOf('grid') === -1) { return; } // Get gap values var gap = this.getGapValue(grid); if (gap.row || gap.column) { this.applyGapFallback(grid, gap); } }, getGapValue: function(element) { var style = element.getAttribute('style') || ''; var gap = { row: 0, column: 0 }; // Parse gap, grid-gap, row-gap, column-gap var gapMatch = style.match(/(?:grid-)?gap:\s*([^;]+)/); if (gapMatch) { var values = gapMatch[1].trim().split(/\s+/); gap.row = this.parseValue(values[0]); gap.column = values[1] ? this.parseValue(values[1]) : gap.row; } var rowGapMatch = style.match(/(?:grid-)?row-gap:\s*([^;]+)/); if (rowGapMatch) { gap.row = this.parseValue(rowGapMatch[1]); } var colGapMatch = style.match(/(?:grid-)?column-gap:\s*([^;]+)/); if (colGapMatch) { gap.column = this.parseValue(colGapMatch[1]); } return gap; }, parseValue: function(value) { var num = parseFloat(value); return isNaN(num) ? 0 : num; }, applyGapFallback: function(grid, gap) { var children = grid.children; for (var i = 0; i < children.length; i++) { var child = children[i]; // Apply margin fallback if (gap.row) { child.style.marginBottom = gap.row + 'px'; } if (gap.column) { child.style.marginRight = gap.column + 'px'; } } // Adjust grid container to compensate if (gap.row) { grid.style.marginBottom = '-' + gap.row + 'px'; } if (gap.column) { grid.style.marginRight = '-' + gap.column + 'px'; } } }; // Initialize if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { GridGapPolyfill.init(); }); } else { GridGapPolyfill.init(); }})();
Warning: CSS Grid polyfills for IE11 have significant
limitations. Consider using Autoprefixer with PostCSS for build-time transformations or
provide float-based fallbacks.
8.3 CSS calc() Function Polyfills
Feature
Syntax
Browser Support
Polyfill Approach
Basic calc()
width: calc(100% - 20px)
IE9+
Parse and compute at runtime
Nested calc()
calc(100% - calc(10px * 2))
Modern browsers
Recursive evaluation
Mixed Units
calc(100vh - 50px)
Requires viewport units
Convert to pixels at runtime
Vendor Prefixes
-webkit-calc(), -moz-calc()
Old browsers
Add all prefixes
Example: CSS calc() polyfill
// CSS calc() polyfill for older browsersvar CalcPolyfill = { init: function() { // Check for calc support if (this.supportsCalc()) { return; } this.processStyleSheets(); this.processInlineStyles(); }, supportsCalc: function() { var div = document.createElement('div'); div.style.width = 'calc(10px)'; return div.style.width !== ''; }, processStyleSheets: function() { var sheets = document.styleSheets; for (var i = 0; i < sheets.length; i++) { try { var rules = sheets[i].cssRules || sheets[i].rules; if (!rules) continue; for (var j = 0; j < rules.length; j++) { this.processRule(rules[j]); } } catch(e) {} } }, processRule: function(rule) { if (!rule.style) return; var style = rule.style; for (var i = 0; i < style.length; i++) { var prop = style[i]; var value = style.getPropertyValue(prop); if (value.indexOf('calc(') >= 0) { var computed = this.evaluateCalc(value, null); if (computed) { style.setProperty(prop, computed); } } } }, processInlineStyles: function() { var elements = document.querySelectorAll('[style*="calc("]'); for (var i = 0; i < elements.length; i++) { var element = elements[i]; var style = element.getAttribute('style'); if (style) { var processed = this.processStyleString(style, element); element.setAttribute('style', processed); } } }, processStyleString: function(style, element) { var self = this; return style.replace(/([^:]+):\s*([^;]*calc\([^;]+)(?:;|$)/g, function(match, prop, value) { var computed = self.evaluateCalc(value, element); return computed ? prop + ': ' + computed + ';' : match; }); }, evaluateCalc: function(value, element) { var self = this; // Extract calc() expressions return value.replace(/calc\(([^\)]+)\)/g, function(match, expression) { try { // Convert percentage and viewport units if element provided if (element) { expression = self.convertUnits(expression, element); } // Evaluate the mathematical expression var result = self.evalMath(expression); return result + 'px'; } catch(e) { console.warn('Failed to evaluate calc:', expression, e); return match; } }); }, convertUnits: function(expression, element) { var parent = element.parentElement; // Convert % to px based on parent if (expression.indexOf('%') >= 0 && parent) { var parentWidth = parent.offsetWidth; expression = expression.replace(/(\d+(?:\.\d+)?)%/g, function(match, num) { return (parseFloat(num) * parentWidth / 100) + ''; }); } // Convert viewport units expression = expression.replace(/(\d+(?:\.\d+)?)vw/g, function(match, num) { return (parseFloat(num) * window.innerWidth / 100) + ''; }); expression = expression.replace(/(\d+(?:\.\d+)?)vh/g, function(match, num) { return (parseFloat(num) * window.innerHeight / 100) + ''; }); // Remove px unit for calculation expression = expression.replace(/(\d+(?:\.\d+)?)px/g, '$1'); return expression; }, evalMath: function(expression) { // Simple mathematical expression evaluator expression = expression.replace(/\s+/g, ''); // Basic validation - only allow numbers, operators, parentheses, and dots if (!/^[\d\+\-\*\/\(\)\.\s]+$/.test(expression)) { throw new Error('Invalid expression'); } // Evaluate using Function constructor (safer than eval) return new Function('return ' + expression)(); }};// Initialize on loadif (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { CalcPolyfill.init(); });} else { CalcPolyfill.init();}
Warning: Runtime calc() polyfills don't handle responsive
changes automatically. Consider using CSS preprocessors (SASS/LESS) for build-time calculations
instead.
8.4 Media Queries and Responsive Design Polyfills
Feature
Browser Support
Polyfill
Notes
@media queries
IE9+
Respond.js for IE8
Min/max-width only
matchMedia()
IE10+
matchMedia.js polyfill
JavaScript query API
@supports
Modern browsers
CSS.supports() polyfill
Feature detection
prefers-color-scheme
Modern browsers
JavaScript detection
Dark mode support
Viewport meta
Mobile browsers
Not needed for desktop
<meta name="viewport">
Example: matchMedia polyfill
// window.matchMedia polyfill for IE9if (!window.matchMedia) { window.matchMedia = (function() { 'use strict'; var styleMedia = (window.styleMedia || window.media); if (!styleMedia) { var style = document.createElement('style'); var script = document.getElementsByTagName('script')[0]; var info = null; style.type = 'text/css'; style.id = 'matchmediajs-test'; script.parentNode.insertBefore(style, script); info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle; styleMedia = { matchMedium: function(media) { var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }'; if (style.styleSheet) { style.styleSheet.cssText = text; } else { style.textContent = text; } return info.width === '1px'; } }; } return function(media) { return { matches: styleMedia.matchMedium(media || 'all'), media: media || 'all' }; }; })();}// Usagevar mql = window.matchMedia('(min-width: 768px)');if (mql.matches) { console.log('Desktop view');} else { console.log('Mobile view');}// Add listener for changes (modern API)if (mql.addEventListener) { mql.addEventListener('change', function(e) { console.log('Media query changed:', e.matches); });} else { // Legacy API mql.addListener(function(mql) { console.log('Media query changed:', mql.matches); });}
Example: Prefers-color-scheme detection
// Dark mode / prefers-color-scheme polyfillvar ColorScheme = { query: '(prefers-color-scheme: dark)', getPreference: function() { // Try matchMedia first if (window.matchMedia && window.matchMedia(this.query).matches) { return 'dark'; } // Fallback: check localStorage var saved = localStorage.getItem('color-scheme'); if (saved) { return saved; } // Fallback: check time of day var hour = new Date().getHours(); if (hour >= 20 || hour <= 6) { return 'dark'; } return 'light'; }, setPreference: function(scheme) { localStorage.setItem('color-scheme', scheme); this.apply(scheme); }, apply: function(scheme) { document.documentElement.setAttribute('data-color-scheme', scheme); // For older browsers, add class if (scheme === 'dark') { document.documentElement.classList.add('dark-mode'); } else { document.documentElement.classList.remove('dark-mode'); } }, watch: function() { var self = this; if (window.matchMedia) { var mql = window.matchMedia(this.query); var handler = function(e) { var scheme = e.matches ? 'dark' : 'light'; self.apply(scheme); }; // Modern API if (mql.addEventListener) { mql.addEventListener('change', handler); } else { // Legacy API mql.addListener(handler); } } }, init: function() { var preference = this.getPreference(); this.apply(preference); this.watch(); }};// InitializeColorScheme.init();// CSS usage:// [data-color-scheme="dark"] { background: #000; color: #fff; }// .dark-mode { background: #000; color: #fff; }
Note: For IE8 support, use Respond.js. For modern media queries like
prefers-reduced-motion, provide JavaScript-based alternatives.
8.5 CSS4 Selector Polyfills (:has, :is, :where)
Selector
Description
Support
Polyfill Method
:has()
Parent selector
NEW
JavaScript class toggling
:is()
Matches-any pseudo-class
Modern browsers
Expand to multiple selectors
:where()
Zero-specificity :is()
Modern browsers
Expand to multiple selectors
:not() complex
Multiple selectors in :not()
Modern browsers
Expand to multiple :not()
:focus-visible
Keyboard focus indicator
Modern browsers
focus-visible.js polyfill
Example: :has() selector polyfill
// :has() selector polyfillvar HasSelectorPolyfill = { supported: null, isSupported: function() { if (this.supported !== null) { return this.supported; } try { document.querySelector(':has(*)'); this.supported = true; } catch(e) { this.supported = false; } return this.supported; }, init: function() { if (this.isSupported()) { return; } // Add class to indicate polyfill is active document.documentElement.classList.add('no-has-selector'); // Process common :has() patterns this.processPattern('[data-has]'); }, processPattern: function(attribute) { var elements = document.querySelectorAll(attribute); for (var i = 0; i < elements.length; i++) { var element = elements[i]; var selector = element.getAttribute(attribute); this.applyHasLogic(element, selector); } }, applyHasLogic: function(element, selector) { // Check if element has matching children var hasMatch = element.querySelector(selector) !== null; if (hasMatch) { element.classList.add('has-' + this.sanitizeSelector(selector)); } else { element.classList.remove('has-' + this.sanitizeSelector(selector)); } // Watch for changes this.watchElement(element, selector); }, sanitizeSelector: function(selector) { return selector.replace(/[^a-z0-9-]/gi, '-'); }, watchElement: function(element, selector) { if (!window.MutationObserver) { return; } var self = this; var observer = new MutationObserver(function() { self.applyHasLogic(element, selector); }); observer.observe(element, { childList: true, subtree: true }); }};// InitializeHasSelectorPolyfill.init();// Usage in HTML:// <div data-has=".error">...</div>//// CSS:// div:has(.error) { border-color: red; }// /* Fallback for polyfill: */// .no-has-selector div.has-error { border-color: red; }
Example: :focus-visible polyfill
// :focus-visible polyfill(function() { var hadKeyboardEvent = true; var hadFocusVisibleRecently = false; var hadFocusVisibleRecentlyTimeout = null; var inputTypesWhitelist = { text: true, search: true, url: true, tel: true, email: true, password: true, number: true, date: true, month: true, week: true, time: true, datetime: true, 'datetime-local': true }; function focusTriggersKeyboardModality(el) { var type = el.type; var tagName = el.tagName; if (tagName === 'INPUT' && inputTypesWhitelist[type] && !el.readOnly) { return true; } if (tagName === 'TEXTAREA' && !el.readOnly) { return true; } if (el.isContentEditable) { return true; } return false; } function addFocusVisibleClass(el) { if (el.classList.contains('focus-visible')) { return; } el.classList.add('focus-visible'); el.setAttribute('data-focus-visible-added', ''); } function removeFocusVisibleClass(el) { if (!el.hasAttribute('data-focus-visible-added')) { return; } el.classList.remove('focus-visible'); el.removeAttribute('data-focus-visible-added'); } function onKeyDown(e) { if (e.metaKey || e.altKey || e.ctrlKey) { return; } hadKeyboardEvent = true; } function onPointerDown() { hadKeyboardEvent = false; } function onFocus(e) { if (focusTriggersKeyboardModality(e.target) || hadKeyboardEvent) { addFocusVisibleClass(e.target); } } function onBlur(e) { if (e.target.classList.contains('focus-visible')) { hadFocusVisibleRecently = true; clearTimeout(hadFocusVisibleRecentlyTimeout); hadFocusVisibleRecentlyTimeout = setTimeout(function() { hadFocusVisibleRecently = false; }, 100); removeFocusVisibleClass(e.target); } } // Check if already supported try { document.querySelector(':focus-visible'); return; } catch(e) { // Not supported, apply polyfill } // Add class to root document.documentElement.classList.add('js-focus-visible'); // Add event listeners document.addEventListener('keydown', onKeyDown, true); document.addEventListener('mousedown', onPointerDown, true); document.addEventListener('pointerdown', onPointerDown, true); document.addEventListener('touchstart', onPointerDown, true); document.addEventListener('focus', onFocus, true); document.addEventListener('blur', onBlur, true);})();// CSS usage:// .js-focus-visible :focus:not(.focus-visible) { outline: none; }// :focus-visible { outline: 2px solid blue; }
Warning: CSS4 selectors like :has() cannot be fully polyfilled
with perfect performance. Consider using data attributes and JavaScript for complex parent selectors.
8.6 CSS Container Queries Polyfill Implementation
Feature
Status
Polyfill Approach
Limitations
@container
EXPERIMENTAL
ResizeObserver + class toggling
Performance overhead
container-type
Modern browsers
Data attribute fallback
Requires polyfill awareness
container-name
Modern browsers
Custom data attributes
Manual setup
Size queries
width, height, inline-size, block-size
Measure container dimensions
Reflow performance
cqi/cqw/cqh units
Container query units
Calculate relative units
Complex calculations
Example: Container queries polyfill
// Container Queries polyfillvar ContainerQueriesPolyfill = { supported: null, containers: [], isSupported: function() { if (this.supported !== null) { return this.supported; } this.supported = CSS.supports('container-type', 'inline-size'); return this.supported; }, init: function() { if (this.isSupported()) { return; } // Find all containers var elements = document.querySelectorAll('[data-container]'); for (var i = 0; i < elements.length; i++) { this.registerContainer(elements[i]); } }, registerContainer: function(element) { var queries = this.parseQueries(element); if (!queries.length) { return; } var container = { element: element, queries: queries }; this.containers.push(container); this.observeContainer(container); this.evaluateQueries(container); }, parseQueries: function(element) { var data = element.getAttribute('data-container'); if (!data) return []; // Expected format: "sm:400px,md:768px,lg:1024px" var queries = []; var parts = data.split(','); for (var i = 0; i < parts.length; i++) { var query = parts[i].trim().split(':'); if (query.length === 2) { queries.push({ name: query[0].trim(), minWidth: parseInt(query[1]) }); } } return queries; }, observeContainer: function(container) { if (!window.ResizeObserver) { // Fallback to window resize var self = this; window.addEventListener('resize', function() { self.evaluateQueries(container); }); return; } var self = this; var observer = new ResizeObserver(function() { self.evaluateQueries(container); }); observer.observe(container.element); }, evaluateQueries: function(container) { var width = container.element.offsetWidth; var activeQueries = []; // Determine which queries match for (var i = 0; i < container.queries.length; i++) { var query = container.queries[i]; if (width >= query.minWidth) { activeQueries.push(query.name); } } // Apply classes this.applyClasses(container.element, activeQueries); }, applyClasses: function(element, activeQueries) { // Remove old container query classes var classes = element.className.split(' '); var newClasses = classes.filter(function(cls) { return cls.indexOf('cq-') !== 0; }); // Add new classes for (var i = 0; i < activeQueries.length; i++) { newClasses.push('cq-' + activeQueries[i]); } element.className = newClasses.join(' '); // Also set data attribute for CSS targeting if (activeQueries.length) { element.setAttribute('data-cq', activeQueries.join(' ')); } else { element.removeAttribute('data-cq'); } }};// Initialize on loadif (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { ContainerQueriesPolyfill.init(); });} else { ContainerQueriesPolyfill.init();}// Usage:// HTML: <div data-container="sm:400,md:768,lg:1024">// <div class="card">Content</div>// </div>//// CSS:// @container (min-width: 768px) {// .card { display: grid; }// }// /* Polyfill fallback: */// [data-cq~="md"] .card { display: grid; }// .cq-md .card { display: grid; }
Example: Container query units polyfill (cqw, cqh)
// Container query units (cqw, cqh) polyfillvar CQUnitsPolyfill = { init: function() { this.processContainers(); }, processContainers: function() { var containers = document.querySelectorAll('[data-cq-units]'); for (var i = 0; i < containers.length; i++) { this.processContainer(containers[i]); } }, processContainer: function(container) { var elements = container.querySelectorAll('[style*="cqw"], [style*="cqh"]'); for (var i = 0; i < elements.length; i++) { this.convertUnits(elements[i], container); } // Watch for size changes this.observeContainer(container); }, convertUnits: function(element, container) { var style = element.getAttribute('style'); if (!style) return; var containerWidth = container.offsetWidth; var containerHeight = container.offsetHeight; // Convert cqw (container query width) style = style.replace(/(\d+(?:\.\d+)?)cqw/g, function(match, value) { return (parseFloat(value) * containerWidth / 100) + 'px'; }); // Convert cqh (container query height) style = style.replace(/(\d+(?:\.\d+)?)cqh/g, function(match, value) { return (parseFloat(value) * containerHeight / 100) + 'px'; }); element.setAttribute('style', style); }, observeContainer: function(container) { if (!window.ResizeObserver) return; var self = this; var observer = new ResizeObserver(function() { self.processContainer(container); }); observer.observe(container); }};// Initializeif (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { CQUnitsPolyfill.init(); });} else { CQUnitsPolyfill.init();}
Note: Container queries have native support in modern browsers. Use the
container-query-polyfill npm package for production or consider the @container PostCSS
plugin.
Key Takeaways - CSS & Styling
CSS Variables: Use css-vars-ponyfill for IE11, parse and substitute at
runtime
Grid/Flexbox: Autoprefixer handles vendor prefixes, use float fallback for
IE9
calc(): Native support from IE9+, use preprocessors for complex
calculations
Media Queries: matchMedia polyfill for JS, Respond.js for IE8
CSS4 Selectors: :has() requires JavaScript, :focus-visible has good
polyfill
Container Queries: ResizeObserver + class toggling, native support growing
9. Web Components Polyfills
9.1 Custom Elements v1 Polyfill Implementation
Feature
API
Browser Support
Polyfill
customElements.define()
customElements.define('my-element', MyElement)
Modern browsers
@webcomponents/custom-elements
Element Class
class MyElement extends HTMLElement
ES6 class syntax
Babel transpilation + polyfill
Lifecycle Callbacks
connectedCallback, disconnectedCallback
Define behavior
MutationObserver for tracking
Attributes
observedAttributes, attributeChangedCallback
Watch attribute changes
Polyfill observes mutations
Autonomous Elements
<my-element>
Custom tag names
Full polyfill support
Customized Built-ins
<button is="my-button">
Extends native elements
Limited Safari support
Example: Custom Elements polyfill
// Custom Elements v1 polyfill (simplified)(function() { if ('customElements' in window) { return; // Native support } var CustomElementRegistry = function() { this._definitions = new Map(); this._whenDefinedPromises = new Map(); this._upgradeQueue = []; }; CustomElementRegistry.prototype.define = function(name, constructor, options) { // Validate tag name if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(name)) { throw new Error('Invalid custom element name: ' + name); } if (this._definitions.has(name)) { throw new Error('Element already defined: ' + name); } // Store definition this._definitions.set(name, { name: name, constructor: constructor, options: options || {} }); // Resolve whenDefined promise if (this._whenDefinedPromises.has(name)) { this._whenDefinedPromises.get(name).resolve(); this._whenDefinedPromises.delete(name); } // Upgrade existing elements this._upgradeElements(name); }; CustomElementRegistry.prototype.get = function(name) { var def = this._definitions.get(name); return def ? def.constructor : undefined; }; CustomElementRegistry.prototype.whenDefined = function(name) { if (this._definitions.has(name)) { return Promise.resolve(); } if (!this._whenDefinedPromises.has(name)) { var resolve, reject; var promise = new Promise(function(res, rej) { resolve = res; reject = rej; }); promise.resolve = resolve; promise.reject = reject; this._whenDefinedPromises.set(name, promise); } return this._whenDefinedPromises.get(name); }; CustomElementRegistry.prototype._upgradeElements = function(name) { var elements = document.getElementsByTagName(name); for (var i = 0; i < elements.length; i++) { this._upgradeElement(elements[i], name); } }; CustomElementRegistry.prototype._upgradeElement = function(element, name) { if (element.__upgraded) { return; } var definition = this._definitions.get(name); if (!definition) { return; } // Create instance var instance = new definition.constructor(); // Copy attributes for (var i = 0; i < element.attributes.length; i++) { var attr = element.attributes[i]; instance.setAttribute(attr.name, attr.value); } // Copy children while (element.firstChild) { instance.appendChild(element.firstChild); } // Replace element element.parentNode.replaceChild(instance, element); // Mark as upgraded instance.__upgraded = true; // Call connectedCallback if present if (instance.connectedCallback) { instance.connectedCallback(); } }; CustomElementRegistry.prototype._observeNewElements = function() { var self = this; if (!window.MutationObserver) { return; } var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { if (node.nodeType === Node.ELEMENT_NODE) { var name = node.tagName.toLowerCase(); if (self._definitions.has(name)) { self._upgradeElement(node, name); } } }); }); }); observer.observe(document.documentElement, { childList: true, subtree: true }); }; // Create global registry var registry = new CustomElementRegistry(); registry._observeNewElements(); Object.defineProperty(window, 'customElements', { value: registry, writable: false, configurable: true }); // Polyfill HTMLElement if needed if (typeof HTMLElement !== 'function') { window.HTMLElement = function() {}; }})();// Usage exampleclass MyButton extends HTMLElement { constructor() { super(); this.addEventListener('click', this._handleClick.bind(this)); } connectedCallback() { this.innerHTML = '<button>' + (this.getAttribute('label') || 'Click me') + '</button>'; } disconnectedCallback() { // Cleanup } static get observedAttributes() { return ['label']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'label') { this.querySelector('button').textContent = newValue; } } _handleClick() { this.dispatchEvent(new CustomEvent('my-click', { detail: { message: 'Clicked!' } })); }}customElements.define('my-button', MyButton);
Note: Use @webcomponents/webcomponentsjs polyfill package for production. It
includes Custom Elements, Shadow DOM, and HTML Templates polyfills.
9.2 Shadow DOM v1 Polyfill and Limitations
Feature
API
Support
Polyfill Limitations
attachShadow()
element.attachShadow({ mode: 'open' })
Modern browsers
Not true encapsulation
Shadow Root
element.shadowRoot
Access shadow content
Open mode only in polyfill
Style Encapsulation
Scoped styles inside shadow
Native isolation
Scoped class names instead
Slot Elements
<slot> for content projection
Native support
Polyfill redistributes content
:host selector
Target host element from shadow
Native support
Replaced with scoped classes
::slotted()
Style slotted content
Native support
Limited polyfill support
Example: Shadow DOM polyfill (simplified)
// Shadow DOM v1 polyfill (simplified concept)(function() { if ('attachShadow' in Element.prototype) { return; // Native support } var ShadowRoot = function(host, mode) { this.host = host; this.mode = mode; this._innerHTML = ''; this._children = []; // Create shadow container this._container = document.createElement('div'); this._container.className = '__shadow-root__'; this._container.style.display = 'contents'; // Pass-through layout }; // Implement appendChild, removeChild, etc. ShadowRoot.prototype.appendChild = function(node) { this._children.push(node); this._container.appendChild(node); return node; }; Object.defineProperty(ShadowRoot.prototype, 'innerHTML', { get: function() { return this._container.innerHTML; }, set: function(html) { this._container.innerHTML = html; this._scopeStyles(); this._processSlots(); } }); ShadowRoot.prototype._scopeStyles = function() { var styles = this._container.querySelectorAll('style'); var scopeId = this.host.getAttribute('data-shadow-id') || 'shadow-' + Math.random().toString(36).substr(2, 9); this.host.setAttribute('data-shadow-id', scopeId); for (var i = 0; i < styles.length; i++) { var style = styles[i]; var css = style.textContent; // Scope selectors css = css.replace(/:host\b/g, '[data-shadow-id="' + scopeId + '"]'); css = css.replace(/([^{}]+)\{/g, function(match, selectors) { if (selectors.indexOf('@') === 0) return match; // Skip at-rules var scoped = selectors.split(',').map(function(sel) { sel = sel.trim(); if (sel.indexOf('[data-shadow-id') >= 0) return sel; return '[data-shadow-id="' + scopeId + '"] ' + sel; }).join(', '); return scoped + ' {'; }); style.textContent = css; } // Add scope to all elements var elements = this._container.querySelectorAll('*'); for (var i = 0; i < elements.length; i++) { elements[i].setAttribute('data-shadow-child', scopeId); } }; ShadowRoot.prototype._processSlots = function() { var slots = this._container.querySelectorAll('slot'); var host = this.host; for (var i = 0; i < slots.length; i++) { var slot = slots[i]; var slotName = slot.getAttribute('name') || ''; // Find content for this slot var content; if (slotName) { content = host.querySelectorAll('[slot="' + slotName + '"]'); } else { // Default slot gets all children without slot attribute content = Array.prototype.filter.call(host.childNodes, function(node) { return !node.getAttribute || !node.getAttribute('slot'); }); } // Move content into slot while (slot.firstChild) { slot.removeChild(slot.firstChild); } for (var j = 0; j < content.length; j++) { slot.appendChild(content[j].cloneNode(true)); } } }; // Polyfill attachShadow Element.prototype.attachShadow = function(options) { if (this.__shadowRoot) { throw new Error('Shadow root already attached'); } var mode = options && options.mode || 'open'; var shadowRoot = new ShadowRoot(this, mode); this.__shadowRoot = shadowRoot; // Clear host content and append shadow container while (this.firstChild) { shadowRoot._originalContent = shadowRoot._originalContent || []; shadowRoot._originalContent.push(this.firstChild); this.removeChild(this.firstChild); } this.appendChild(shadowRoot._container); return shadowRoot; }; // Polyfill shadowRoot getter Object.defineProperty(Element.prototype, 'shadowRoot', { get: function() { return this.__shadowRoot && this.__shadowRoot.mode === 'open' ? this.__shadowRoot : null; } });})();// Usage exampleclass ShadowCard extends HTMLElement { constructor() { super(); var shadow = this.attachShadow({ mode: 'open' }); shadow.innerHTML = ` <style> :host { display: block; border: 1px solid #ccc; border-radius: 4px; padding: 16px; } .header { font-weight: bold; margin-bottom: 8px; } .content { color: #666; } </style> <div class="header"> <slot name="header">Default Header</slot> </div> <div class="content"> <slot>Default Content</slot> </div> `; }}customElements.define('shadow-card', ShadowCard);
Warning: Shadow DOM polyfills cannot provide true style
encapsulation. Global styles may still leak in. Consider using CSS Modules or scoped CSS instead for
better polyfill support.
9.3 HTML Templates and Template Element Polyfills
Feature
API
Browser Support
Polyfill Method
<template>
Inert content container
IE not supported
Hidden div fallback
content property
template.content
DocumentFragment
Create fragment manually
cloneNode()
template.content.cloneNode(true)
Clone template content
Standard cloning works
Inert Scripts
Scripts don't execute in template
Native behavior
Remove type or src attributes
Example: HTML Template polyfill
// HTML Template element polyfill(function() { if ('content' in document.createElement('template')) { return; // Native support } // Polyfill template element var templates = document.getElementsByTagName('template'); for (var i = 0; i < templates.length; i++) { var template = templates[i]; // Create DocumentFragment for content var content = document.createDocumentFragment(); var child; while (child = template.firstChild) { content.appendChild(child); } // Store content as property Object.defineProperty(template, 'content', { get: function() { return this._content || (this._content = content); } }); // Hide template template.style.display = 'none'; } // Observe new templates if (window.MutationObserver) { var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { if (node.tagName === 'TEMPLATE') { initTemplate(node); } }); }); }); observer.observe(document.documentElement, { childList: true, subtree: true }); } function initTemplate(template) { if (template.content) return; var content = document.createDocumentFragment(); var child; while (child = template.firstChild) { content.appendChild(child); } Object.defineProperty(template, 'content', { get: function() { return this._content || (this._content = content); } }); template.style.display = 'none'; }})();// Usage examplevar template = document.getElementById('my-template');var clone = document.importNode(template.content, true);document.body.appendChild(clone);// HTML:// <template id="my-template">// <div class="card">// <h3>Title</h3>// <p>Content</p>// </div>// </template>
Note: The <template> element is well-supported in modern browsers. For IE,
use the polyfill or fallback to hidden <div> containers.
9.4 Slot Element and Content Projection
Feature
Syntax
Description
Polyfill
Default Slot
<slot></slot>
Unnamed slot for default content
Content redistribution
Named Slot
<slot name="header"></slot>
Target specific content
Match by slot attribute
Slotted Content
<div slot="header">
Assign content to slot
Move/clone content
Fallback Content
Content inside <slot>
Shown if no slotted content
Keep if no match found
assignedNodes()
slot.assignedNodes()
Get slotted nodes
Track assigned content
slotchange event
Fires when slot content changes
React to content updates
MutationObserver
Example: Slot element polyfill
// Slot element polyfill (simplified)var SlotPolyfill = { processSlots: function(shadowRoot, hostElement) { var slots = shadowRoot.querySelectorAll('slot'); for (var i = 0; i < slots.length; i++) { this.processSlot(slots[i], hostElement); } }, processSlot: function(slot, hostElement) { var slotName = slot.getAttribute('name'); var assignedNodes = []; if (slotName) { // Named slot - find matching content var matches = hostElement.querySelectorAll('[slot="' + slotName + '"]'); assignedNodes = Array.prototype.slice.call(matches); } else { // Default slot - find unassigned content assignedNodes = Array.prototype.filter.call(hostElement.childNodes, function(node) { if (node.nodeType === Node.TEXT_NODE) { return node.textContent.trim() !== ''; } return !node.getAttribute || !node.getAttribute('slot'); }); } // Store assigned nodes for API slot._assignedNodes = assignedNodes; // Clear slot while (slot.firstChild) { slot.removeChild(slot.firstChild); } // Insert assigned content or fallback if (assignedNodes.length > 0) { assignedNodes.forEach(function(node) { slot.appendChild(node.cloneNode(true)); }); } // If no assigned nodes, slot keeps its fallback content }, // Implement assignedNodes() method polyfillAssignedNodes: function() { if (!('assignedNodes' in HTMLSlotElement.prototype)) { HTMLSlotElement.prototype.assignedNodes = function(options) { return this._assignedNodes || []; }; } if (!('assignedElements' in HTMLSlotElement.prototype)) { HTMLSlotElement.prototype.assignedElements = function(options) { var nodes = this._assignedNodes || []; return nodes.filter(function(node) { return node.nodeType === Node.ELEMENT_NODE; }); }; } }, watchSlotChanges: function(slot, hostElement) { if (!window.MutationObserver) return; var self = this; var observer = new MutationObserver(function(mutations) { self.processSlot(slot, hostElement); // Dispatch slotchange event var event = document.createEvent('Event'); event.initEvent('slotchange', true, false); slot.dispatchEvent(event); }); observer.observe(hostElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['slot'] }); }};// Usage in custom elementclass SlottedCard extends HTMLElement { constructor() { super(); var shadow = this.attachShadow({ mode: 'open' }); shadow.innerHTML = ` <style> .card { border: 1px solid #ccc; padding: 16px; } .header { font-size: 18px; font-weight: bold; margin-bottom: 8px; } .footer { margin-top: 8px; font-size: 12px; color: #666; } </style> <div class="card"> <div class="header"> <slot name="header">Default Header</slot> </div> <div class="content"> <slot>Default content goes here</slot> </div> <div class="footer"> <slot name="footer">Default Footer</slot> </div> </div> `; // Process slots with polyfill if needed if (!('assignedNodes' in HTMLSlotElement.prototype)) { SlotPolyfill.processSlots(shadow, this); } }}customElements.define('slotted-card', SlottedCard);// HTML usage:// <slotted-card>// <h2 slot="header">My Title</h2>// <p>This is my content</p>// <span slot="footer">Copyright 2025</span>// </slotted-card>
Note: Slot polyfills work by cloning content, not moving it.
Event listeners on original content won't work. Re-attach listeners after slotting.
9.5 HTML Imports Alternative Implementations
Technology
Status
Alternative
Use Case
HTML Imports
DEPRECATED
ES Modules
Component loading
<link rel="import">
Never standardized
fetch() + innerHTML
Load HTML fragments
ES Modules
Modern browsers
<script type="module">
Preferred method
Dynamic import()
Modern browsers
import('./component.js')
Lazy loading
Template Includes
No native support
Build tools / SSR
Server-side composition
Example: HTML Imports alternative with fetch
// HTML Imports alternative using fetchvar HTMLLoader = { cache: new Map(), import: function(url) { // Return cached promise if exists if (this.cache.has(url)) { return this.cache.get(url); } // Fetch and parse HTML var promise = fetch(url) .then(function(response) { if (!response.ok) { throw new Error('Failed to load: ' + url); } return response.text(); }) .then(function(html) { // Parse HTML into document fragment var template = document.createElement('template'); template.innerHTML = html; return { url: url, content: template.content, querySelector: function(selector) { return template.content.querySelector(selector); }, querySelectorAll: function(selector) { return template.content.querySelectorAll(selector); } }; }); this.cache.set(url, promise); return promise; }, importAll: function(urls) { return Promise.all(urls.map(this.import.bind(this))); }};// UsageHTMLLoader.import('/components/card.html') .then(function(imported) { // Get template from imported document var template = imported.querySelector('template#card-template'); var clone = document.importNode(template.content, true); document.body.appendChild(clone); }) .catch(function(error) { console.error('Import failed:', error); });// Load multiple componentsHTMLLoader.importAll([ '/components/header.html', '/components/footer.html', '/components/sidebar.html']).then(function(imports) { console.log('All components loaded');});
Example: ES Module approach for Web Components
// Modern approach: ES Modules for Web Components// component.jsexport class MyComponent extends HTMLElement { constructor() { super(); var shadow = this.attachShadow({ mode: 'open' }); shadow.innerHTML = this.getTemplate(); } getTemplate() { return ` <style> :host { display: block; padding: 16px; } </style> <div class="component"> <slot></slot> </div> `; } connectedCallback() { console.log('Component connected'); }}// Auto-register when module loadscustomElements.define('my-component', MyComponent);// main.js - Import and useimport { MyComponent } from './component.js';// Component is auto-registered, just use itdocument.body.innerHTML = '<my-component>Hello!</my-component>';// Or lazy loadasync function loadComponent() { const module = await import('./component.js'); console.log('Component loaded and registered');}// Load on demanddocument.getElementById('load-btn').addEventListener('click', loadComponent);
Warning: HTML Imports are deprecated and removed from browsers.
Use ES Modules with import statements or dynamic import() for component loading.
9.6 Scoped CSS and Style Encapsulation
Method
Approach
Browser Support
Pros/Cons
Shadow DOM
Native style encapsulation
Modern browsers
True isolation, polyfill limitations
Scoped Attribute
<style scoped>
REMOVED FROM SPEC
Never fully supported
CSS Modules
Build-time scoping
All browsers
Requires build step
BEM Naming
Naming convention
All browsers
Manual, no true isolation
CSS-in-JS
JavaScript-generated styles
All browsers
Runtime overhead
Data Attributes
Scope with unique attributes
All browsers
Simple, no build tools
Example: Scoped CSS polyfill with data attributes
// Scoped CSS polyfillvar ScopedCSS = { scopeId: 0, applyScoped: function(container) { var scopeId = 'scope-' + (++this.scopeId); // Add scope attribute to container container.setAttribute('data-scope', scopeId); // Find style elements var styles = container.querySelectorAll('style[scoped]'); for (var i = 0; i < styles.length; i++) { this.scopeStyle(styles[i], scopeId); } // Add scope to all descendants var elements = container.querySelectorAll('*'); for (var i = 0; i < elements.length; i++) { if (elements[i].tagName !== 'STYLE') { elements[i].setAttribute('data-scope-child', scopeId); } } }, scopeStyle: function(styleElement, scopeId) { var css = styleElement.textContent; // Scope all selectors css = css.replace(/([^{}]+)\{/g, function(match, selectors) { // Skip at-rules if (selectors.trim().indexOf('@') === 0) { return match; } var scoped = selectors.split(',').map(function(selector) { selector = selector.trim(); // Scope the selector return '[data-scope="' + scopeId + '"] ' + selector; }).join(', '); return scoped + ' {'; }); styleElement.textContent = css; styleElement.removeAttribute('scoped'); // Move style to head for better performance if (styleElement.parentNode !== document.head) { document.head.appendChild(styleElement); } }, // Auto-process on DOM ready init: function() { var containers = document.querySelectorAll('[data-scoped-css]'); for (var i = 0; i < containers.length; i++) { this.applyScoped(containers[i]); } // Watch for new containers this.observeDOM(); }, observeDOM: function() { if (!window.MutationObserver) return; var self = this; var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.hasAttribute && node.hasAttribute('data-scoped-css')) { self.applyScoped(node); } } }); }); }); observer.observe(document.documentElement, { childList: true, subtree: true }); }};// Initializeif (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { ScopedCSS.init(); });} else { ScopedCSS.init();}// Usage:// <div data-scoped-css>// <style scoped>// .title { color: blue; }// p { font-size: 14px; }// </style>// // <h1 class="title">Scoped Title</h1>// <p>Scoped paragraph</p>// </div>
Example: CSS-in-JS approach for components
// Simple CSS-in-JS for Web Componentsclass StyledComponent extends HTMLElement { constructor() { super(); this.scopeId = 'component-' + Math.random().toString(36).substr(2, 9); this.setAttribute('data-component-id', this.scopeId); this.injectStyles(); this.render(); } getStyles() { return { '.container': { padding: '16px', border: '1px solid #ccc', borderRadius: '4px' }, '.title': { fontSize: '18px', fontWeight: 'bold', marginBottom: '8px' }, '.content': { color: '#666' } }; } injectStyles() { var styles = this.getStyles(); var css = ''; // Convert styles object to CSS string for (var selector in styles) { var scopedSelector = '[data-component-id="' + this.scopeId + '"] ' + selector; css += scopedSelector + ' {'; var rules = styles[selector]; for (var prop in rules) { var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase(); css += cssProp + ': ' + rules[prop] + ';'; } css += '}'; } // Create and inject style element var styleEl = document.createElement('style'); styleEl.textContent = css; document.head.appendChild(styleEl); // Store reference for cleanup this._styleElement = styleEl; } render() { this.innerHTML = ` <div class="container"> <div class="title">${this.getAttribute('title') || 'Title'}</div> <div class="content"> <slot></slot> </div> </div> `; } disconnectedCallback() { // Cleanup styles when component removed if (this._styleElement && this._styleElement.parentNode) { this._styleElement.parentNode.removeChild(this._styleElement); } }}customElements.define('styled-component', StyledComponent);// Usage:// <styled-component title="My Component">// This is the content// </styled-component>
Key Takeaways - Web Components
Custom Elements: Use @webcomponents/custom-elements polyfill for IE11
Templates: Well-supported, simple polyfill for IE using DocumentFragment
Slots: Polyfill clones content instead of moving it, re-attach listeners
HTML Imports: Deprecated - use ES Modules with import()
Scoped CSS: Use data attributes or CSS-in-JS for reliable scoping
10. Performance and Animation Polyfills
10.1 requestIdleCallback Polyfill Implementation
Feature
API
Browser Support
Use Case
requestIdleCallback()
requestIdleCallback(callback, options)
Modern browsers
Schedule low-priority work
IdleDeadline
deadline.timeRemaining()
Returns time left in idle period
Check available time
didTimeout
deadline.didTimeout
Boolean flag
Timeout exceeded check
cancelIdleCallback()
cancelIdleCallback(handle)
Cancel pending callback
Cleanup scheduled work
timeout option
{ timeout: 2000 }
Force execution after delay
Maximum wait time
Example: requestIdleCallback polyfill
// requestIdleCallback polyfill(function() { if ('requestIdleCallback' in window) { return; // Native support } var requestIdleCallback = function(callback, options) { var start = Date.now(); var timeout = (options && options.timeout) || 1; return setTimeout(function() { callback({ didTimeout: false, timeRemaining: function() { // Estimate 50ms frame budget return Math.max(0, 50 - (Date.now() - start)); } }); }, 1); }; var cancelIdleCallback = function(id) { clearTimeout(id); }; window.requestIdleCallback = requestIdleCallback; window.cancelIdleCallback = cancelIdleCallback;})();// Usage examplefunction processLargeDataset(items) { var index = 0; function processChunk(deadline) { // Process items while time remains while (index < items.length && (deadline.timeRemaining() > 0 || deadline.didTimeout)) { processItem(items[index]); index++; // Process at least one item per callback if (deadline.didTimeout) break; } // Schedule next chunk if more items remain if (index < items.length) { requestIdleCallback(processChunk, { timeout: 1000 }); } else { console.log('Processing complete'); } } function processItem(item) { // Simulate work console.log('Processing:', item); } // Start processing requestIdleCallback(processChunk, { timeout: 1000 });}// Process 1000 items during idle timeprocessLargeDataset(Array.from({ length: 1000 }, (_, i) => i));
Example: Enhanced polyfill with frame budget tracking
// Enhanced requestIdleCallback with better timingvar IdleCallbackPolyfill = (function() { var channel = null; var frameDeadline = 0; var pendingTimeout = null; var idleCallbackId = 0; var idleCallbacks = new Map(); // Use MessageChannel for better timing if (typeof MessageChannel !== 'undefined') { channel = new MessageChannel(); channel.port1.onmessage = function() { var currentTime = performance.now(); var didTimeout = false; // Process all pending callbacks idleCallbacks.forEach(function(callback, id) { var timeRemaining = Math.max(0, frameDeadline - currentTime); if (timeRemaining > 0 || didTimeout) { callback({ didTimeout: didTimeout, timeRemaining: function() { return Math.max(0, frameDeadline - performance.now()); } }); idleCallbacks.delete(id); } }); }; } function scheduleIdleCallback() { // Aim for 50ms per frame (60fps = 16.67ms, idle = 50ms) frameDeadline = performance.now() + 50; if (channel) { channel.port2.postMessage(null); } else { setTimeout(function() { processIdleCallbacks(false); }, 0); } } function processIdleCallbacks(didTimeout) { var currentTime = performance.now(); idleCallbacks.forEach(function(callback, id) { callback({ didTimeout: didTimeout, timeRemaining: function() { return Math.max(0, frameDeadline - performance.now()); } }); idleCallbacks.delete(id); }); } function requestIdleCallback(callback, options) { var timeout = (options && options.timeout) || Infinity; var id = ++idleCallbackId; idleCallbacks.set(id, callback); // Schedule callback scheduleIdleCallback(); // Handle timeout if (timeout < Infinity) { setTimeout(function() { if (idleCallbacks.has(id)) { var cb = idleCallbacks.get(id); idleCallbacks.delete(id); cb({ didTimeout: true, timeRemaining: function() { return 0; } }); } }, timeout); } return id; } function cancelIdleCallback(id) { idleCallbacks.delete(id); } return { requestIdleCallback: requestIdleCallback, cancelIdleCallback: cancelIdleCallback };})();// Apply polyfill if neededif (!('requestIdleCallback' in window)) { window.requestIdleCallback = IdleCallbackPolyfill.requestIdleCallback; window.cancelIdleCallback = IdleCallbackPolyfill.cancelIdleCallback;}
Note: requestIdleCallback is useful for non-critical tasks like
analytics, preloading, and background processing. Polyfills approximate idle time but don't match native
precision.
10.2 Web Animations API Polyfills
Feature
API
Browser Support
Polyfill
element.animate()
element.animate(keyframes, options)
Modern browsers
web-animations-js
Animation object
animation.play(), pause(), cancel()
Control playback
Polyfill provides full API
Keyframes
Array or object syntax
Define animation states
CSS @keyframes fallback
Timing options
duration, easing, delay, iterations
Animation configuration
requestAnimationFrame
playbackRate
animation.playbackRate = 2
Speed control
Adjust timing in polyfill
finished Promise
animation.finished.then()
Completion callback
Custom Promise implementation
Example: Basic Web Animations API polyfill
// Simple Web Animations API polyfill (basic features)(function() { if ('animate' in Element.prototype) { return; // Native support } var Animation = function(element, keyframes, options) { this.element = element; this.keyframes = this._normalizeKeyframes(keyframes); this.options = this._normalizeOptions(options); this.startTime = null; this.currentTime = 0; this.playState = 'idle'; this.playbackRate = 1; this._rafId = null; this._finishedPromise = null; }; Animation.prototype._normalizeKeyframes = function(keyframes) { // Convert object format to array format if (!Array.isArray(keyframes)) { var keys = Object.keys(keyframes); return keys.map(function(key) { var frame = {}; frame[key] = keyframes[key]; return frame; }); } return keyframes; }; Animation.prototype._normalizeOptions = function(options) { if (typeof options === 'number') { return { duration: options }; } return { duration: options.duration || 0, delay: options.delay || 0, easing: options.easing || 'linear', iterations: options.iterations || 1, fill: options.fill || 'auto' }; }; Animation.prototype.play = function() { if (this.playState === 'running') return; this.playState = 'running'; this.startTime = performance.now() - this.currentTime; this._animate(); return this; }; Animation.prototype.pause = function() { if (this.playState !== 'running') return; this.playState = 'paused'; this.currentTime = performance.now() - this.startTime; if (this._rafId) { cancelAnimationFrame(this._rafId); this._rafId = null; } }; Animation.prototype.cancel = function() { this.playState = 'idle'; this.currentTime = 0; this.startTime = null; if (this._rafId) { cancelAnimationFrame(this._rafId); this._rafId = null; } // Reset to first frame this._applyFrame(0); }; Animation.prototype.finish = function() { this.currentTime = this.options.duration * this.options.iterations; this._applyFrame(1); this.playState = 'finished'; if (this._finishedResolve) { this._finishedResolve(this); } }; Animation.prototype._animate = function() { var self = this; this._rafId = requestAnimationFrame(function() { if (self.playState !== 'running') return; var elapsed = (performance.now() - self.startTime) * self.playbackRate; var duration = self.options.duration; var iterations = self.options.iterations; var totalDuration = duration * iterations; if (elapsed >= totalDuration) { self.finish(); return; } // Calculate progress (0 to 1) var progress = (elapsed % duration) / duration; progress = self._applyEasing(progress, self.options.easing); self._applyFrame(progress); self.currentTime = elapsed; self._animate(); }); }; Animation.prototype._applyFrame = function(progress) { var keyframes = this.keyframes; if (keyframes.length === 0) return; // Simple two-keyframe interpolation var startFrame = keyframes[0]; var endFrame = keyframes[keyframes.length - 1]; for (var prop in endFrame) { if (endFrame.hasOwnProperty(prop)) { var startValue = this._parseValue(startFrame[prop] || '0'); var endValue = this._parseValue(endFrame[prop]); var currentValue = startValue + (endValue - startValue) * progress; if (prop === 'opacity' || prop === 'scale') { this.element.style[prop] = currentValue; } else { this.element.style[prop] = currentValue + 'px'; } } } }; Animation.prototype._parseValue = function(value) { if (typeof value === 'number') return value; return parseFloat(value) || 0; }; Animation.prototype._applyEasing = function(progress, easing) { // Simple easing functions switch(easing) { case 'ease-in': return progress * progress; case 'ease-out': return progress * (2 - progress); case 'ease-in-out': return progress < 0.5 ? 2 * progress * progress : -1 + (4 - 2 * progress) * progress; case 'linear': default: return progress; } }; Object.defineProperty(Animation.prototype, 'finished', { get: function() { if (!this._finishedPromise) { var self = this; this._finishedPromise = new Promise(function(resolve) { self._finishedResolve = resolve; }); } return this._finishedPromise; } }); // Polyfill Element.prototype.animate Element.prototype.animate = function(keyframes, options) { var animation = new Animation(this, keyframes, options); animation.play(); return animation; };})();// Usage examplevar element = document.getElementById('box');var animation = element.animate([ { transform: 'translateX(0px)', opacity: 1 }, { transform: 'translateX(300px)', opacity: 0.5 }], { duration: 1000, easing: 'ease-in-out', iterations: Infinity, direction: 'alternate'});// Control animationanimation.pause();animation.play();animation.cancel();// Wait for completionanimation.finished.then(function() { console.log('Animation finished');});
Warning: Full Web Animations API polyfill is complex. Use the
official web-animations-js polyfill for production. This example shows basic concepts only.
10.3 PerformanceObserver and Performance Timeline
API
Method
Browser Support
Use Case
PerformanceObserver
new PerformanceObserver(callback)
Modern browsers
Monitor performance entries
observe()
observer.observe({ entryTypes: ['measure'] })
Start observing
Specify entry types to watch
Entry Types
navigation, resource, mark, measure, paint
Different metrics
Track specific events
performance.mark()
performance.mark('start')
Create timestamp
Mark points in time
performance.measure()
performance.measure('task', 'start', 'end')
Calculate duration
Time between marks
getEntries()
performance.getEntries()
Get all entries
Retrieve performance data
Example: PerformanceObserver polyfill
// PerformanceObserver polyfill (basic implementation)(function() { if ('PerformanceObserver' in window) { return; // Native support } var PerformanceObserver = function(callback) { this.callback = callback; this.entryTypes = []; this.buffered = false; this._entries = []; }; PerformanceObserver.prototype.observe = function(options) { this.entryTypes = options.entryTypes || []; this.buffered = options.buffered || false; // Get existing entries if buffered if (this.buffered) { var existingEntries = this._getExistingEntries(); if (existingEntries.length > 0) { this._notify(existingEntries); } } // Start monitoring this._startMonitoring(); }; PerformanceObserver.prototype.disconnect = function() { this.entryTypes = []; }; PerformanceObserver.prototype._getExistingEntries = function() { var entries = []; this.entryTypes.forEach(function(type) { var typeEntries = performance.getEntriesByType(type); entries = entries.concat(typeEntries); }); return entries; }; PerformanceObserver.prototype._startMonitoring = function() { var self = this; // Poll for new entries this._pollInterval = setInterval(function() { var newEntries = self._getNewEntries(); if (newEntries.length > 0) { self._notify(newEntries); } }, 100); }; PerformanceObserver.prototype._getNewEntries = function() { var allEntries = this._getExistingEntries(); var newEntries = []; allEntries.forEach(function(entry) { if (this._entries.indexOf(entry) === -1) { newEntries.push(entry); this._entries.push(entry); } }.bind(this)); return newEntries; }; PerformanceObserver.prototype._notify = function(entries) { var list = { getEntries: function() { return entries; }, getEntriesByType: function(type) { return entries.filter(function(e) { return e.entryType === type; }); }, getEntriesByName: function(name, type) { return entries.filter(function(e) { return e.name === name && (!type || e.entryType === type); }); } }; this.callback(list, this); }; // Polyfill mark and measure if needed if (!performance.mark) { var marks = {}; performance.mark = function(name) { marks[name] = performance.now(); }; performance.measure = function(name, startMark, endMark) { var startTime = marks[startMark] || 0; var endTime = marks[endMark] || performance.now(); var duration = endTime - startTime; // Store measure entry var entry = { name: name, entryType: 'measure', startTime: startTime, duration: duration }; if (!performance._measures) { performance._measures = []; } performance._measures.push(entry); return entry; }; performance.getEntriesByType = function(type) { if (type === 'measure') { return performance._measures || []; } return []; }; } window.PerformanceObserver = PerformanceObserver;})();// Usage examplevar observer = new PerformanceObserver(function(list) { list.getEntries().forEach(function(entry) { console.log('Performance entry:', entry.name, entry.duration + 'ms'); });});observer.observe({ entryTypes: ['measure', 'mark'] });// Measure performanceperformance.mark('task-start');// Do some worksetTimeout(function() { performance.mark('task-end'); performance.measure('task-duration', 'task-start', 'task-end');}, 1000);
Note: PerformanceObserver is useful for real user monitoring
(RUM). Track metrics like First Contentful Paint (FCP), Largest Contentful Paint (LCP), and custom
timings.
Warning:Always validate keys in polyfills that merge or assign
properties. Block __proto__, constructor, and prototype to prevent prototype pollution attacks.
11.2 CSP-Compatible Polyfill Implementation
CSP Directive
Impact on Polyfills
Workaround
Support
script-src 'unsafe-eval'
Blocks eval(), Function()
Pre-compile or avoid eval
Required for some polyfills
script-src 'unsafe-inline'
Blocks inline scripts
External scripts only
Use nonce or hash
style-src 'unsafe-inline'
Blocks inline styles
External stylesheets
CSS polyfills affected
script-src 'nonce-*'
Requires nonce attribute
Add nonce to polyfill script
Secure alternative
strict-dynamic
Propagates trust
Dynamically loaded scripts inherit
Modern CSP
Example: CSP-compatible polyfill patterns
// BAD: Uses eval (blocked by CSP)function badPolyfill(code) { eval(code); // Blocked by CSP}// BAD: Uses Function constructor (blocked by CSP)function badDynamicFunction(param, body) { return new Function(param, body); // Blocked by CSP}// GOOD: Pre-compiled functionfunction cspSafePolyfill() { // All code is pre-defined, no eval needed if (!Array.prototype.includes) { Array.prototype.includes = function(searchElement, fromIndex) { 'use strict'; var O = Object(this); var len = parseInt(O.length) || 0; if (len === 0) { return false; } var n = parseInt(fromIndex) || 0; var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); while (k < len) { if (O[k] === searchElement) { return true; } k++; } return false; }; }}// CSP-compatible dynamic injectionfunction injectPolyfillScript(src, nonce) { var script = document.createElement('script'); script.src = src; script.async = true; // Add nonce for CSP compliance if (nonce) { script.setAttribute('nonce', nonce); } // Add integrity hash for subresource integrity if (src.indexOf('cdn') >= 0) { script.integrity = 'sha384-...'; // SRI hash script.crossOrigin = 'anonymous'; } document.head.appendChild(script);}// Get nonce from existing scriptfunction getCurrentNonce() { var scripts = document.getElementsByTagName('script'); for (var i = 0; i < scripts.length; i++) { var nonce = scripts[i].getAttribute('nonce'); if (nonce) { return nonce; } } return null;}// Usagevar nonce = getCurrentNonce();injectPolyfillScript('https://cdn.polyfill.io/v3/polyfill.min.js', nonce);
Example: CSP-safe CSS injection
// CSP-safe style injectionvar CSSInjector = { nonce: null, init: function() { this.nonce = this.getNonce(); }, getNonce: function() { // Try to get nonce from existing style tags var styles = document.getElementsByTagName('style'); for (var i = 0; i < styles.length; i++) { var nonce = styles[i].getAttribute('nonce'); if (nonce) return nonce; } // Try to get from script tags var scripts = document.getElementsByTagName('script'); for (var i = 0; i < scripts.length; i++) { var nonce = scripts[i].getAttribute('nonce'); if (nonce) return nonce; } return null; }, inject: function(css, id) { // Check if already injected if (id && document.getElementById(id)) { return; } var style = document.createElement('style'); if (id) { style.id = id; } // Add nonce for CSP compliance if (this.nonce) { style.setAttribute('nonce', this.nonce); } style.textContent = css; document.head.appendChild(style); return style; }, injectExternal: function(href) { var link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; // Add integrity for CDN resources if (href.indexOf('cdn') >= 0) { link.integrity = 'sha384-...'; link.crossOrigin = 'anonymous'; } document.head.appendChild(link); }};// InitializeCSSInjector.init();// Inject styles with nonceCSSInjector.inject(` .polyfilled-class { display: flex; flex-direction: column; }`, 'flexbox-polyfill-styles');
Note: For strict CSP compliance, use nonce-based scripts and
avoid eval/Function. Pre-compile polyfills at build time or use external scripts with SRI hashes.
11.3 Safe eval() Alternatives and Function Constructors
Unsafe Pattern
Risk
Safe Alternative
Use Case
eval(code)
Arbitrary code execution
JSON.parse() for data
Parsing trusted data
new Function(body)
Dynamic code creation
Pre-defined functions
Fixed logic only
setTimeout(string)
Eval-like behavior
setTimeout(function)
Delayed execution
setInterval(string)
Eval-like behavior
setInterval(function)
Repeated execution
innerHTML with scripts
Script injection
textContent or DOMPurify
Content sanitization
Example: Safe alternatives to eval()
// UNSAFE: Using evalfunction unsafeCalculate(expression) { return eval(expression); // DANGEROUS!}// SAFE: Limited expression evaluatorfunction safeCalculate(expression) { // Whitelist approach - only allow safe operations var sanitized = expression.replace(/[^0-9+\-*/(). ]/g, ''); if (sanitized !== expression) { throw new Error('Invalid expression'); } // Use Function constructor with validation (still requires CSP 'unsafe-eval') try { var fn = new Function('return ' + sanitized); return fn(); } catch(e) { throw new Error('Calculation error: ' + e.message); }}// SAFER: Parser-based approach (no eval)function safeMathEvaluator(expression) { var tokens = tokenize(expression); return evaluate(tokens); function tokenize(expr) { var regex = /(\d+\.?\d*|[+\-*/()])/g; return expr.match(regex) || []; } function evaluate(tokens) { var index = 0; function parseExpression() { var left = parseTerm(); while (index < tokens.length && (tokens[index] === '+' || tokens[index] === '-')) { var op = tokens[index++]; var right = parseTerm(); left = op === '+' ? left + right : left - right; } return left; } function parseTerm() { var left = parseFactor(); while (index < tokens.length && (tokens[index] === '*' || tokens[index] === '/')) { var op = tokens[index++]; var right = parseFactor(); left = op === '*' ? left * right : left / right; } return left; } function parseFactor() { if (tokens[index] === '(') { index++; var result = parseExpression(); index++; // skip ')' return result; } return parseFloat(tokens[index++]); } return parseExpression(); }}// Usageconsole.log(safeMathEvaluator('2 + 3 * 4')); // 14console.log(safeMathEvaluator('(2 + 3) * 4')); // 20// SAFE: JSON parsing instead of evalfunction parseData(jsonString) { // NEVER use eval for JSON try { return JSON.parse(jsonString); } catch(e) { console.error('Invalid JSON:', e); return null; }}
Example: Safe setTimeout/setInterval usage
// BAD: String-based setTimeout (eval-like)setTimeout('console.log("hello")', 1000); // DANGEROUS!// GOOD: Function-based setTimeoutsetTimeout(function() { console.log('hello');}, 1000);// Safe timer utilityvar SafeTimer = { setTimeout: function(callback, delay) { if (typeof callback !== 'function') { throw new TypeError('Callback must be a function'); } return window.setTimeout(callback, delay); }, setInterval: function(callback, delay) { if (typeof callback !== 'function') { throw new TypeError('Callback must be a function'); } return window.setInterval(callback, delay); }, clearTimeout: function(id) { return window.clearTimeout(id); }, clearInterval: function(id) { return window.clearInterval(id); }};// Usagevar timerId = SafeTimer.setTimeout(function() { console.log('Safe timer executed');}, 1000);SafeTimer.clearTimeout(timerId);
Warning:Never use eval() or Function() constructor with
user-supplied input. Always use JSON.parse() for data and pre-defined functions for logic.
Note: Always audit third-party polyfills before production use.
Check for eval usage, prototype pollution, and unexpected network requests. Use npm audit regularly.
11.5 Supply Chain Security for Polyfill Dependencies
Attack Vector
Description
Mitigation
Tools
Malicious Packages
Typosquatting, backdoors
Verify package names
npm, Socket.dev
Compromised CDN
Modified polyfill.io or CDN files
Use SRI hashes
Subresource Integrity
Dependency Confusion
Internal package name collision
Private registry, scoped packages
npm config
Version Pinning
Automatic updates to malicious versions
Lock files, exact versions
package-lock.json
Transitive Dependencies
Vulnerable nested dependencies
Audit regularly
npm audit, Snyk
Example: Subresource Integrity (SRI) for CDN polyfills
// Load polyfill from CDN with SRI protectionfunction loadPolyfillWithSRI(url, integrity, callback) { var script = document.createElement('script'); script.src = url; script.async = true; // Add integrity hash for SRI script.integrity = integrity; script.crossOrigin = 'anonymous'; // Handle load success script.onload = function() { if (callback) callback(null); }; // Handle load failure script.onerror = function() { console.error('Failed to load polyfill from CDN'); // Fallback to local copy loadLocalPolyfill(callback); }; document.head.appendChild(script);}function loadLocalPolyfill(callback) { var script = document.createElement('script'); script.src = '/js/polyfills/fallback.js'; script.async = true; script.onload = function() { if (callback) callback(null); }; script.onerror = function() { console.error('Failed to load local polyfill'); if (callback) callback(new Error('All polyfill sources failed')); }; document.head.appendChild(script);}// Usage with SRI hashloadPolyfillWithSRI( 'https://cdn.jsdelivr.net/npm/core-js@3.27.2/minified.min.js', 'sha384-qyF1Z4o7cH2u8gBWpQYY3U0KLTbVjP3xPmj9Qj5gD7Y8FpH4kW5NxZcB1qL9M4jR', function(err) { if (err) { console.error('Polyfill loading failed'); } else { console.log('Polyfill loaded successfully'); } });// Generate SRI hash for local files// Command: openssl dgst -sha384 -binary polyfill.js | openssl base64 -A
Warning: The polyfill.io CDN service was compromised in 2024.
Always use SRI hashes for CDN resources and have local fallbacks. Self-host critical polyfills when possible.
Note: Differential serving can reduce bundle sizes by 30-50% for modern
browsers while maintaining backward compatibility. Use module/nomodule for automatic detection.
12.2 Dynamic Import and Code Splitting Strategies
Strategy
Use Case
Loading Time
Implementation
Route-based
Load code per page/route
On navigation
import('./page.js')
Component-based
Lazy load components
On visibility/interaction
React.lazy, Vue async
Vendor chunks
Separate node_modules
Initial load
splitChunks config
Polyfill chunks
Load only if needed
Conditional
Feature detection + import()
Prefetch/Preload
Anticipate future needs
Idle time
<link rel="prefetch">
Example: Conditional polyfill loading
// Load polyfills only when neededasync function loadPolyfills() { var polyfills = []; // Check what's needed if (!('Promise' in window)) { polyfills.push(import('core-js/features/promise')); } if (!('fetch' in window)) { polyfills.push(import('whatwg-fetch')); } if (!('IntersectionObserver' in window)) { polyfills.push(import('intersection-observer')); } if (!window.customElements) { polyfills.push(import('@webcomponents/webcomponentsjs/webcomponents-bundle')); } // Wait for all polyfills to load if (polyfills.length > 0) { await Promise.all(polyfills); console.log('Polyfills loaded:', polyfills.length); }}// Initialize app after polyfillsloadPolyfills() .then(() => { return import('./app.js'); }) .then((app) => { app.init(); }) .catch((error) => { console.error('Failed to load:', error); });// Webpack magic comments for chunk namingasync function loadFeature() { const module = await import( /* webpackChunkName: "heavy-feature" */ /* webpackPrefetch: true */ './heavy-feature.js' ); return module;}
Note: Use multiple CDN fallbacks for reliability. Always include
local fallback and use SRI hashes for security.
12.5 Service Worker and Offline Polyfill Caching
Strategy
Description
Use Case
Cache Priority
Cache First
Serve from cache, fallback to network
Static polyfills
High
Network First
Try network, fallback to cache
Frequently updated polyfills
Medium
Stale While Revalidate
Serve cache, update in background
Best of both worlds
Balanced
Network Only
Always fetch from network
Development
None
Cache Only
Only serve cached content
Offline-first apps
Required
Example: Service Worker for polyfill caching
// service-worker.jsvar CACHE_NAME = 'polyfills-v1';var POLYFILL_URLS = [ '/polyfills/core-js.min.js', '/polyfills/fetch.min.js', '/polyfills/intersection-observer.min.js', '/polyfills/resize-observer.min.js'];// Install - cache polyfillsself.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Caching polyfills'); return cache.addAll(POLYFILL_URLS); }) .then(function() { return self.skipWaiting(); }) );});// Activate - cleanup old cachesself.addEventListener('activate', function(event) { event.waitUntil( caches.keys() .then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheName !== CACHE_NAME) { console.log('Deleting old cache:', cacheName); return caches.delete(cacheName); } }) ); }) .then(function() { return self.clients.claim(); }) );});// Fetch - serve from cache with network fallbackself.addEventListener('fetch', function(event) { var url = new URL(event.request.url); // Only handle polyfill requests if (url.pathname.indexOf('/polyfills/') !== 0) { return; } event.respondWith( caches.match(event.request) .then(function(response) { if (response) { console.log('Serving from cache:', url.pathname); return response; } console.log('Fetching from network:', url.pathname); return fetch(event.request) .then(function(response) { // Cache the new response if (response.status === 200) { var responseClone = response.clone(); caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseClone); }); } return response; }); }) .catch(function() { console.error('Failed to load:', url.pathname); }) );});// Register service worker (in main app)if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') .then(function(registration) { console.log('Service Worker registered:', registration); }) .catch(function(error) { console.error('Service Worker registration failed:', error); });}
Example: Stale-while-revalidate strategy
// Advanced caching with stale-while-revalidateself.addEventListener('fetch', function(event) { var url = new URL(event.request.url); if (url.pathname.indexOf('/polyfills/') !== 0) { return; } event.respondWith( caches.open(CACHE_NAME).then(function(cache) { return cache.match(event.request).then(function(cachedResponse) { // Fetch from network in background var fetchPromise = fetch(event.request).then(function(networkResponse) { // Update cache with fresh response if (networkResponse.status === 200) { cache.put(event.request, networkResponse.clone()); } return networkResponse; }); // Return cached response immediately if available // Otherwise wait for network return cachedResponse || fetchPromise; }); }) );});
Note: Service Workers enable offline-first polyfill delivery.
Use cache-first strategy for stable polyfills, network-first for frequently updated ones.
// devtools-helpers.jsvar PolyfillDebugger = { // Check if polyfill is loaded isPolyfillLoaded: function(featureName) { var polyfillMarkers = { 'Promise': function() { return window.Promise && Promise.toString().indexOf('[native code]') === -1; }, 'fetch': function() { return window.fetch && fetch.polyfill === true; }, 'IntersectionObserver': function() { return window.IntersectionObserver && IntersectionObserver.toString().indexOf('[native code]') === -1; } }; var checker = polyfillMarkers[featureName]; return checker ? checker() : false; }, // List all loaded polyfills listLoadedPolyfills: function() { var polyfills = []; var features = [ 'Promise', 'fetch', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Symbol', 'Array.from', 'Object.assign', 'IntersectionObserver', 'ResizeObserver', 'requestAnimationFrame', 'CustomEvent' ]; features.forEach(function(feature) { if (this.isPolyfillLoaded(feature)) { polyfills.push(feature); } }.bind(this)); console.table(polyfills.map(function(name) { return { Feature: name, Polyfilled: '✓' }; })); return polyfills; }, // Set breakpoint on polyfill breakOnPolyfill: function(methodName) { var original = window[methodName]; if (!original) { console.error('Method not found:', methodName); return; } window[methodName] = function() { debugger; // Breakpoint here return original.apply(this, arguments); }; console.log('Breakpoint set on:', methodName); }, // Trace polyfill calls tracePolyfill: function(obj, methodName) { var original = obj[methodName]; if (!original) { console.error('Method not found:', methodName); return; } obj[methodName] = function() { console.group('Polyfill Call: ' + methodName); console.log('Arguments:', arguments); console.trace('Call Stack'); var result = original.apply(this, arguments); console.log('Return Value:', result); console.groupEnd(); return result; }; console.log('Tracing enabled for:', methodName); }, // Inspect polyfill source inspectPolyfill: function(featureName) { var feature = window[featureName]; if (!feature) { console.error('Feature not found:', featureName); return; } console.group('Polyfill Inspection: ' + featureName); console.log('Type:', typeof feature); console.log('Source:', feature.toString()); console.log('Properties:', Object.getOwnPropertyNames(feature)); // Check if native or polyfilled var isNative = feature.toString().indexOf('[native code]') !== -1; console.log('Native:', isNative); console.log('Polyfilled:', !isNative); console.groupEnd(); }, // Monitor polyfill performance monitorPerformance: function(obj, methodName, iterations) { iterations = iterations || 1000; var original = obj[methodName]; if (!original) { console.error('Method not found:', methodName); return; } console.log('Benchmarking ' + methodName + ' (' + iterations + ' iterations)...'); var start = performance.now(); for (var i = 0; i < iterations; i++) { original.call(obj); } var end = performance.now(); var duration = end - start; var avgTime = duration / iterations; console.log('Total Time: ' + duration.toFixed(2) + 'ms'); console.log('Average Time: ' + avgTime.toFixed(4) + 'ms'); console.log('Ops/Second: ' + (1000 / avgTime).toFixed(0)); }, // Check code coverage for polyfills checkCoverage: function() { console.log('Run this in Chrome DevTools:'); console.log('1. Open Coverage tab (Cmd+Shift+P > "Show Coverage")'); console.log('2. Click record button'); console.log('3. Interact with your app'); console.log('4. Click stop to see unused polyfills'); console.log('5. Filter by "polyfill" to see polyfill coverage'); }, // Debug feature detection debugFeatureDetection: function() { var features = { 'Promise': typeof Promise !== 'undefined', 'fetch': typeof fetch !== 'undefined', 'Map': typeof Map !== 'undefined', 'Set': typeof Set !== 'undefined', 'Symbol': typeof Symbol !== 'undefined', 'Proxy': typeof Proxy !== 'undefined', 'WeakMap': typeof WeakMap !== 'undefined', 'IntersectionObserver': typeof IntersectionObserver !== 'undefined', 'ResizeObserver': typeof ResizeObserver !== 'undefined', 'CustomElements': typeof customElements !== 'undefined', 'ShadowDOM': 'attachShadow' in Element.prototype, 'ES6 Classes': (function() { try { eval('class Test {}'); return true; } catch (e) { return false; } })() }; console.table(features); return features; }};// Console helpers - add to global scope for easy accesswindow.polyfillDebug = PolyfillDebugger;// Usage examples:// polyfillDebug.listLoadedPolyfills();// polyfillDebug.breakOnPolyfill('fetch');// polyfillDebug.tracePolyfill(Array.prototype, 'find');// polyfillDebug.inspectPolyfill('Promise');// polyfillDebug.monitorPerformance(Array.prototype, 'map', 10000);// polyfillDebug.debugFeatureDetection();
Note: Use Chrome DevTools Coverage to identify unused
polyfills. Filter by "polyfill" and remove unused code to reduce bundle size.
18.2 Common Polyfill Issues and Solutions
Issue
Symptoms
Root Cause
Solution
Polyfill Not Loading
Feature undefined, errors
Missing import, CSP block
Check network, CSP headers
Order of Execution
Race conditions, undefined refs
Async loading conflicts
Load polyfills synchronously
Polyfill Conflicts
Incorrect behavior, overrides
Multiple polyfills clash
Consolidate, check order
Performance Degradation
Slow page load, jank
Heavy polyfills, poor impl
Profile, optimize, lazy load
Memory Leaks
Growing heap, crashes
Retained references
Use WeakMap, cleanup
CSP Violations
Blocked execution
unsafe-eval in polyfill
Use CSP-safe alternatives
Build Tool Issues
Missing polyfills in prod
Incorrect transpilation
Configure Babel properly
Example: Common issue detection and fixes
// polyfill-troubleshooter.jsvar PolyfillTroubleshooter = { // Diagnose polyfill issues diagnose: function() { var issues = []; // Check 1: Are polyfills loading? if (!this.checkPolyfillsLoaded()) { issues.push({ severity: 'error', issue: 'Polyfills not loading', solution: 'Check network tab for failed requests' }); } // Check 2: Are there conflicts? var conflicts = this.detectConflicts(); if (conflicts.length > 0) { issues.push({ severity: 'warning', issue: 'Polyfill conflicts detected', conflicts: conflicts, solution: 'Remove duplicate polyfills or adjust load order' }); } // Check 3: Performance issues? var perfIssues = this.checkPerformance(); if (perfIssues.length > 0) { issues.push({ severity: 'warning', issue: 'Performance issues detected', details: perfIssues, solution: 'Consider lazy loading or optimizing polyfills' }); } // Check 4: Memory leaks? this.checkMemoryLeaks().then(function(hasLeaks) { if (hasLeaks) { issues.push({ severity: 'error', issue: 'Potential memory leak detected', solution: 'Use Chrome DevTools Memory profiler' }); } }); // Check 5: CSP violations? if (this.checkCSPViolations()) { issues.push({ severity: 'error', issue: 'CSP violations detected', solution: 'Use CSP-safe polyfills or adjust CSP headers' }); } this.reportIssues(issues); return issues; }, // Check if polyfills are loaded checkPolyfillsLoaded: function() { var scripts = document.querySelectorAll('script[src*="polyfill"]'); if (scripts.length === 0) { console.warn('No polyfill scripts found'); return false; } var allLoaded = true; scripts.forEach(function(script) { if (!script.complete || script.readyState === 'loading') { console.error('Polyfill not loaded:', script.src); allLoaded = false; } }); return allLoaded; }, // Detect polyfill conflicts detectConflicts: function() { var conflicts = []; // Check for duplicate polyfills var methods = [ { obj: Array.prototype, name: 'find' }, { obj: Array.prototype, name: 'includes' }, { obj: Object, name: 'assign' }, { obj: String.prototype, name: 'includes' } ]; methods.forEach(function(method) { var descriptor = Object.getOwnPropertyDescriptor(method.obj, method.name); if (descriptor && descriptor.configurable === false) { // Check if it was polyfilled after being native var source = method.obj[method.name].toString(); var isNative = source.indexOf('[native code]') !== -1; if (!isNative && !descriptor.configurable) { conflicts.push({ method: method.name, issue: 'Non-configurable polyfill override', recommendation: 'Check polyfill load order' }); } } }); return conflicts; }, // Check performance issues checkPerformance: function() { var issues = []; // Check load time var resources = performance.getEntriesByType('resource'); resources.forEach(function(resource) { if (resource.name.indexOf('polyfill') !== -1) { if (resource.duration > 500) { issues.push({ file: resource.name, issue: 'Slow loading (>500ms)', duration: resource.duration.toFixed(2) + 'ms' }); } if (resource.transferSize > 100 * 1024) { issues.push({ file: resource.name, issue: 'Large file size (>100KB)', size: (resource.transferSize / 1024).toFixed(2) + 'KB' }); } } }); return issues; }, // Check for memory leaks checkMemoryLeaks: function() { if (!performance.memory) { console.warn('performance.memory not available'); return Promise.resolve(false); } var initialHeap = performance.memory.usedJSHeapSize; return new Promise(function(resolve) { setTimeout(function() { var currentHeap = performance.memory.usedJSHeapSize; var growth = currentHeap - initialHeap; var growthMB = growth / 1024 / 1024; if (growthMB > 10) { console.warn('Significant memory growth detected: ' + growthMB.toFixed(2) + 'MB'); resolve(true); } else { resolve(false); } }, 5000); }); }, // Check for CSP violations checkCSPViolations: function() { var hasViolations = false; // Listen for CSP violations document.addEventListener('securitypolicyviolation', function(e) { console.error('CSP Violation:', e.violatedDirective); hasViolations = true; }); return hasViolations; }, // Report all issues reportIssues: function(issues) { if (issues.length === 0) { console.log('✓ No polyfill issues detected'); return; } console.group('Polyfill Issues Detected (' + issues.length + ')'); issues.forEach(function(issue, index) { var icon = issue.severity === 'error' ? '❌' : '⚠️'; console.group(icon + ' Issue ' + (index + 1) + ': ' + issue.issue); console.log('Severity:', issue.severity); console.log('Solution:', issue.solution); if (issue.conflicts) { console.log('Conflicts:', issue.conflicts); } if (issue.details) { console.table(issue.details); } console.groupEnd(); }); console.groupEnd(); }, // Fix common issues automatically autoFix: function() { console.log('Attempting automatic fixes...'); // Fix 1: Remove duplicate polyfills this.removeDuplicatePolyfills(); // Fix 2: Optimize polyfill loading this.optimizeLoading(); console.log('Auto-fix complete. Run diagnose() to verify.'); }, // Remove duplicate polyfills removeDuplicatePolyfills: function() { var scripts = document.querySelectorAll('script[src*="polyfill"]'); var seen = {}; scripts.forEach(function(script) { var src = script.src; if (seen[src]) { console.log('Removing duplicate polyfill:', src); script.remove(); } else { seen[src] = true; } }); }, // Optimize polyfill loading optimizeLoading: function() { // Add async/defer to non-critical polyfills var scripts = document.querySelectorAll('script[src*="polyfill"]'); scripts.forEach(function(script) { if (!script.async && !script.defer) { // Check if polyfill is critical var isCritical = script.src.includes('critical') || script.src.includes('core'); if (!isCritical) { script.defer = true; console.log('Added defer to:', script.src); } } }); }};// Quick diagnostic commandwindow.polyfillCheck = function() { return PolyfillTroubleshooter.diagnose();};// Usage:// polyfillCheck(); // Run diagnostics// PolyfillTroubleshooter.autoFix(); // Attempt automatic fixes
Warning:Load polyfills before app code. Async loading can
cause race conditions where app code tries to use polyfilled features before they're available.
18.3 Performance Profiling with Polyfills
Metric
Tool
Target
Action
Parse Time
DevTools Performance
<50 ms
Minify, compress
Execution Time
Performance API
<100 ms
Lazy load
Method Overhead
Benchmarks
<2x native
Optimize implementation
Bundle Size
webpack-bundle-analyzer
<100 KB
Tree shake, split
FCP Impact
Lighthouse
<100 ms delay
Defer non-critical
TTI Impact
WebPageTest
<200 ms delay
Code splitting
Example: Performance profiling suite
// performance-profiler.jsvar PolyfillProfiler = { profiles: [], // Profile polyfill loading profileLoading: function(polyfillName, loadFn) { var startMark = 'polyfill-load-start-' + polyfillName; var endMark = 'polyfill-load-end-' + polyfillName; var measureName = 'polyfill-load-' + polyfillName; performance.mark(startMark); return Promise.resolve(loadFn()).then(function() { performance.mark(endMark); performance.measure(measureName, startMark, endMark); var measure = performance.getEntriesByName(measureName)[0]; var profile = { name: polyfillName, type: 'loading', duration: measure.duration, timestamp: Date.now() }; this.profiles.push(profile); console.log('Polyfill ' + polyfillName + ' loaded in ' + measure.duration.toFixed(2) + 'ms'); return profile; }.bind(this)); }, // Profile method execution profileMethod: function(obj, methodName, iterations) { iterations = iterations || 10000; var method = obj[methodName]; if (!method) { console.error('Method not found:', methodName); return; } // Warm up for (var i = 0; i < 100; i++) { method.call(obj, i); } // Profile var start = performance.now(); for (var i = 0; i < iterations; i++) { method.call(obj, i); } var end = performance.now(); var duration = end - start; var avgTime = duration / iterations; var profile = { name: methodName, type: 'execution', totalTime: duration, avgTime: avgTime, iterations: iterations, opsPerSecond: Math.round(1000 / avgTime) }; this.profiles.push(profile); console.log('Method: ' + methodName); console.log(' Total: ' + duration.toFixed(2) + 'ms'); console.log(' Average: ' + avgTime.toFixed(4) + 'ms'); console.log(' Ops/sec: ' + profile.opsPerSecond.toLocaleString()); return profile; }, // Compare native vs polyfill performance comparePerformance: function(nativeFn, polyfillFn, testName, iterations) { iterations = iterations || 10000; console.group('Performance Comparison: ' + testName); // Profile native var nativeStart = performance.now(); for (var i = 0; i < iterations; i++) { nativeFn(); } var nativeDuration = performance.now() - nativeStart; // Profile polyfill var polyfillStart = performance.now(); for (var i = 0; i < iterations; i++) { polyfillFn(); } var polyfillDuration = performance.now() - polyfillStart; var slowdown = polyfillDuration / nativeDuration; var acceptable = slowdown < 2; console.log('Native: ' + nativeDuration.toFixed(2) + 'ms'); console.log('Polyfill: ' + polyfillDuration.toFixed(2) + 'ms'); console.log('Slowdown: ' + slowdown.toFixed(2) + 'x'); console.log('Acceptable: ' + (acceptable ? '✓' : '✗')); console.groupEnd(); return { test: testName, native: nativeDuration, polyfill: polyfillDuration, slowdown: slowdown, acceptable: acceptable }; }, // Profile FCP impact profileFCPImpact: function() { if (!PerformanceObserver) { console.warn('PerformanceObserver not available'); return; } var observer = new PerformanceObserver(function(list) { list.getEntries().forEach(function(entry) { if (entry.name === 'first-contentful-paint') { var fcp = entry.startTime; // Calculate polyfill impact var polyfillMarks = performance.getEntriesByType('mark') .filter(function(m) { return m.name.includes('polyfill'); }); var polyfillTime = 0; polyfillMarks.forEach(function(mark) { var measure = performance.getEntriesByName(mark.name)[0]; if (measure && measure.startTime < fcp) { polyfillTime += measure.duration || 0; } }); var impact = (polyfillTime / fcp) * 100; console.log('FCP: ' + fcp.toFixed(2) + 'ms'); console.log('Polyfill Time: ' + polyfillTime.toFixed(2) + 'ms'); console.log('Impact: ' + impact.toFixed(1) + '%'); this.profiles.push({ type: 'fcp-impact', fcp: fcp, polyfillTime: polyfillTime, impact: impact }); } }.bind(this)); }.bind(this)); observer.observe({ entryTypes: ['paint'] }); }, // Generate comprehensive report generateReport: function() { console.group('📊 Polyfill Performance Report'); // Group by type var byType = {}; this.profiles.forEach(function(profile) { if (!byType[profile.type]) { byType[profile.type] = []; } byType[profile.type].push(profile); }); // Loading performance if (byType.loading) { console.group('Loading Performance'); var totalLoadTime = byType.loading.reduce(function(sum, p) { return sum + p.duration; }, 0); console.log('Total Load Time: ' + totalLoadTime.toFixed(2) + 'ms'); console.table(byType.loading); console.groupEnd(); } // Execution performance if (byType.execution) { console.group('Execution Performance'); console.table(byType.execution.map(function(p) { return { Method: p.name, 'Avg Time': p.avgTime.toFixed(4) + 'ms', 'Ops/sec': p.opsPerSecond.toLocaleString() }; })); console.groupEnd(); } // FCP impact if (byType['fcp-impact']) { console.group('FCP Impact'); byType['fcp-impact'].forEach(function(p) { console.log('FCP: ' + p.fcp.toFixed(2) + 'ms'); console.log('Polyfill Impact: ' + p.impact.toFixed(1) + '%'); }); console.groupEnd(); } // Recommendations console.group('Recommendations'); var recommendations = this.generateRecommendations(byType); recommendations.forEach(function(rec) { console.log(rec.icon + ' ' + rec.message); }); console.groupEnd(); console.groupEnd(); return { profiles: this.profiles, summary: byType, recommendations: recommendations }; }, // Generate recommendations generateRecommendations: function(byType) { var recommendations = []; // Check loading time if (byType.loading) { var totalLoadTime = byType.loading.reduce(function(sum, p) { return sum + p.duration; }, 0); if (totalLoadTime > 100) { recommendations.push({ icon: '⚠️', message: 'Total polyfill load time exceeds 100ms (' + totalLoadTime.toFixed(2) + 'ms). Consider lazy loading.' }); } } // Check execution performance if (byType.execution) { byType.execution.forEach(function(profile) { if (profile.avgTime > 1) { recommendations.push({ icon: '⚠️', message: profile.name + ' is slow (>1ms). Optimize implementation.' }); } }); } // Check FCP impact if (byType['fcp-impact']) { byType['fcp-impact'].forEach(function(profile) { if (profile.impact > 20) { recommendations.push({ icon: '❌', message: 'Polyfills delay FCP by ' + profile.impact.toFixed(1) + '%. Use differential serving.' }); } }); } if (recommendations.length === 0) { recommendations.push({ icon: '✓', message: 'All metrics within acceptable ranges' }); } return recommendations; }};// Usage examples:// Profile polyfill loadingPolyfillProfiler.profileLoading('Promise', function() { return import('es6-promise/auto');});// Profile method executionPolyfillProfiler.profileMethod(Array.prototype, 'find', 10000);// Compare native vs polyfillif (Array.prototype.map) { var nativeMap = Array.prototype.map; var polyfillMap = function() { /* polyfill implementation */ }; PolyfillProfiler.comparePerformance( function() { [1,2,3].map(function(x) { return x * 2; }); }, function() { [1,2,3].map(function(x) { return x * 2; }); }, 'Array.map', 10000 );}// Monitor FCP impactPolyfillProfiler.profileFCPImpact();// Generate report after page loadwindow.addEventListener('load', function() { setTimeout(function() { PolyfillProfiler.generateReport(); }, 2000);});
Note: Profile polyfills in target browsers (especially legacy
ones). Modern browser performance doesn't reflect real-world usage.
18.4 Feature Detection Debugging Techniques
Technique
Use Case
Example
Gotcha
typeof Check
Check if feature exists
typeof Promise !== 'undefined'
May exist but be broken
in Operator
Check method existence
'includes' in Array.prototype
Doesn't verify correctness
try-catch Test
Test feature behavior
Try executing, catch errors
Can hide other errors
Native Code Check
Verify if native
fn.toString() includes '[native code]'
Can be spoofed
Behavior Test
Test actual functionality
Execute and verify result
Can be slow
CSS.supports()
CSS feature detection
CSS.supports('display', 'grid')
Limited browser support
Example: Advanced feature detection utilities
// feature-detection-debugger.jsvar FeatureDetector = { // Comprehensive feature check detectFeature: function(featureName) { var detectors = { 'Promise': this.detectPromise.bind(this), 'fetch': this.detectFetch.bind(this), 'Map': this.detectMap.bind(this), 'Set': this.detectSet.bind(this), 'Symbol': this.detectSymbol.bind(this), 'Proxy': this.detectProxy.bind(this), 'IntersectionObserver': this.detectIntersectionObserver.bind(this), 'CustomElements': this.detectCustomElements.bind(this) }; var detector = detectors[featureName]; if (!detector) { console.error('No detector for feature:', featureName); return null; } var result = detector(); console.group('Feature Detection: ' + featureName); console.log('Exists:', result.exists); console.log('Native:', result.isNative); console.log('Working:', result.isWorking); console.log('Details:', result.details); console.groupEnd(); return result; }, // Detect Promise support detectPromise: function() { var exists = typeof Promise !== 'undefined'; var isNative = exists && Promise.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { var promise = new Promise(function(resolve) { resolve('test'); }); isWorking = promise instanceof Promise; // Test Promise.all details.hasAll = typeof Promise.all === 'function'; details.hasRace = typeof Promise.race === 'function'; details.hasAllSettled = typeof Promise.allSettled === 'function'; details.hasAny = typeof Promise.any === 'function'; } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Detect fetch support detectFetch: function() { var exists = typeof fetch !== 'undefined'; var isNative = exists && fetch.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { // Check if fetch returns a Promise var result = fetch('data:text/plain,test'); isWorking = result instanceof Promise; result.catch(function() {}); // Prevent unhandled rejection details.hasAbortController = typeof AbortController !== 'undefined'; details.hasHeaders = typeof Headers !== 'undefined'; details.hasRequest = typeof Request !== 'undefined'; details.hasResponse = typeof Response !== 'undefined'; } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Detect Map support detectMap: function() { var exists = typeof Map !== 'undefined'; var isNative = exists && Map.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { var map = new Map(); map.set('key', 'value'); isWorking = map.get('key') === 'value' && map.size === 1; // Test iterator details.hasIterator = typeof map[Symbol.iterator] === 'function'; details.hasForEach = typeof map.forEach === 'function'; } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Detect Set support detectSet: function() { var exists = typeof Set !== 'undefined'; var isNative = exists && Set.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { var set = new Set(); set.add('value'); isWorking = set.has('value') && set.size === 1; details.hasIterator = typeof set[Symbol.iterator] === 'function'; details.hasForEach = typeof set.forEach === 'function'; } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Detect Symbol support detectSymbol: function() { var exists = typeof Symbol !== 'undefined'; var isNative = exists && Symbol.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { var sym = Symbol('test'); isWorking = typeof sym === 'symbol'; details.hasFor = typeof Symbol.for === 'function'; details.hasKeyFor = typeof Symbol.keyFor === 'function'; details.hasIterator = typeof Symbol.iterator !== 'undefined'; } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Detect Proxy support detectProxy: function() { var exists = typeof Proxy !== 'undefined'; var isNative = exists && Proxy.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { var target = {}; var handler = { get: function(obj, prop) { return prop in obj ? obj[prop] : 'proxy'; } }; var proxy = new Proxy(target, handler); isWorking = proxy.test === 'proxy'; details.hasRevocable = typeof Proxy.revocable === 'function'; } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Detect IntersectionObserver support detectIntersectionObserver: function() { var exists = typeof IntersectionObserver !== 'undefined'; var isNative = exists && IntersectionObserver.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { var observer = new IntersectionObserver(function() {}); isWorking = typeof observer.observe === 'function'; observer.disconnect(); } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Detect Custom Elements support detectCustomElements: function() { var exists = typeof customElements !== 'undefined'; var isNative = exists && customElements.define.toString().indexOf('[native code]') !== -1; var isWorking = false; var details = {}; if (exists) { try { isWorking = typeof customElements.define === 'function'; details.hasGet = typeof customElements.get === 'function'; details.hasWhenDefined = typeof customElements.whenDefined === 'function'; } catch (e) { details.error = e.message; } } return { exists: exists, isNative: isNative, isWorking: isWorking, details: details }; }, // Batch detection detectAll: function() { var features = [ 'Promise', 'fetch', 'Map', 'Set', 'Symbol', 'Proxy', 'IntersectionObserver', 'CustomElements' ]; var results = {}; features.forEach(function(feature) { results[feature] = this.detectFeature(feature); }.bind(this)); console.table(Object.keys(results).map(function(name) { var result = results[name]; return { Feature: name, Exists: result.exists ? '✓' : '✗', Native: result.isNative ? '✓' : '✗', Working: result.isWorking ? '✓' : '✗' }; })); return results; }, // Debug why feature detection fails debugDetection: function(featureName, detectionCode) { console.group('Debugging Feature Detection: ' + featureName); try { var result = detectionCode(); console.log('Detection Result:', result); console.log('Type:', typeof result); console.log('Truthy:', !!result); } catch (e) { console.error('Detection Error:', e); console.log('Stack:', e.stack); } console.groupEnd(); }};// Global helperwindow.detectFeature = FeatureDetector.detectFeature.bind(FeatureDetector);window.detectAllFeatures = FeatureDetector.detectAll.bind(FeatureDetector);// Usage:// detectFeature('Promise');// detectAllFeatures();// FeatureDetector.debugDetection('CustomElements', function() {// return typeof customElements !== 'undefined';// });
Warning: Feature detection can give false positives. Always
test behavior, not just existence. Some browsers partially implement features.
18.5 Polyfill Conflict Resolution Strategies
Conflict Type
Cause
Detection
Resolution
Duplicate Polyfills
Multiple libraries include same polyfill
Check for multiple implementations
Deduplicate, use single source
Version Conflicts
Different polyfill versions
Compare implementations
Use latest compatible version
Override Conflicts
Polyfill overrides native incorrectly
Check for native code
Guard with feature detection
Load Order Issues
Polyfills loaded in wrong order
Check dependencies
Correct load order
Namespace Collisions
Global variable conflicts
Check window object
Use modules, avoid globals
Prototype Pollution
Unsafe prototype extensions
Check prototype chain
Use Object.defineProperty
Example: Conflict detection and resolution
// conflict-resolver.jsvar PolyfillConflictResolver = { conflicts: [], // Detect all conflicts detectConflicts: function() { this.conflicts = []; // Check for duplicate polyfills this.checkDuplicates(); // Check for override conflicts this.checkOverrides(); // Check for namespace collisions this.checkNamespaceCollisions(); // Report conflicts this.reportConflicts(); return this.conflicts; }, // Check for duplicate polyfills checkDuplicates: function() { var methods = [ { obj: Array.prototype, name: 'find', feature: 'Array.find' }, { obj: Array.prototype, name: 'includes', feature: 'Array.includes' }, { obj: Object, name: 'assign', feature: 'Object.assign' }, { obj: String.prototype, name: 'includes', feature: 'String.includes' }, { obj: window, name: 'Promise', feature: 'Promise' }, { obj: window, name: 'fetch', feature: 'fetch' } ]; methods.forEach(function(method) { var fn = method.obj[method.name]; if (!fn) return; // Check if method has been polyfilled multiple times var descriptor = Object.getOwnPropertyDescriptor(method.obj, method.name); if (descriptor && descriptor.writable === false && descriptor.configurable === false) { // Check if there are multiple definitions var source = fn.toString(); if (source.includes('polyfill') && source.length > 1000) { this.conflicts.push({ type: 'duplicate', feature: method.feature, severity: 'warning', message: 'Possible duplicate polyfill detected' }); } } }.bind(this)); }, // Check for override conflicts checkOverrides: function() { var methods = [ { obj: Array.prototype, name: 'map' }, { obj: Array.prototype, name: 'filter' }, { obj: Array.prototype, name: 'reduce' }, { obj: Object, name: 'keys' }, { obj: Object, name: 'values' } ]; methods.forEach(function(method) { var fn = method.obj[method.name]; if (!fn) return; var source = fn.toString(); var isNative = source.indexOf('[native code]') !== -1; // Check if a polyfill overrode a native method var descriptor = Object.getOwnPropertyDescriptor(method.obj, method.name); if (!isNative && descriptor && !descriptor.configurable) { this.conflicts.push({ type: 'override', feature: method.obj.constructor.name + '.' + method.name, severity: 'error', message: 'Polyfill incorrectly overrides native method' }); } }.bind(this)); }, // Check for namespace collisions checkNamespaceCollisions: function() { var globalVars = ['Promise', 'Map', 'Set', 'Symbol', 'Proxy', 'Reflect']; globalVars.forEach(function(varName) { var value = window[varName]; if (!value) return; // Check if variable is defined multiple times var descriptor = Object.getOwnPropertyDescriptor(window, varName); if (descriptor && descriptor.configurable === false) { var source = value.toString(); if (source.includes('polyfill')) { this.conflicts.push({ type: 'namespace', feature: varName, severity: 'warning', message: 'Global namespace collision detected' }); } } }.bind(this)); }, // Report all conflicts reportConflicts: function() { if (this.conflicts.length === 0) { console.log('✓ No polyfill conflicts detected'); return; } console.group('⚠️ Polyfill Conflicts (' + this.conflicts.length + ')'); this.conflicts.forEach(function(conflict, index) { var icon = conflict.severity === 'error' ? '❌' : '⚠️'; console.group(icon + ' Conflict ' + (index + 1) + ': ' + conflict.type); console.log('Feature:', conflict.feature); console.log('Severity:', conflict.severity); console.log('Message:', conflict.message); console.groupEnd(); }); console.groupEnd(); }, // Resolve conflicts automatically resolveConflicts: function() { console.log('Attempting to resolve conflicts...'); this.conflicts.forEach(function(conflict) { switch (conflict.type) { case 'duplicate': this.resolveDuplicate(conflict); break; case 'override': this.resolveOverride(conflict); break; case 'namespace': this.resolveNamespace(conflict); break; } }.bind(this)); console.log('Conflict resolution complete'); }, // Resolve duplicate polyfills resolveDuplicate: function(conflict) { console.log('Resolving duplicate:', conflict.feature); // Remove duplicate script tags var scripts = document.querySelectorAll('script[src*="' + conflict.feature.toLowerCase() + '"]'); if (scripts.length > 1) { for (var i = 1; i < scripts.length; i++) { console.log('Removing duplicate script:', scripts[i].src); scripts[i].remove(); } } }, // Resolve override conflicts resolveOverride: function(conflict) { console.error('Cannot auto-resolve override conflict:', conflict.feature); console.log('Manual intervention required:'); console.log('1. Check if native implementation exists'); console.log('2. Remove polyfill if native is available'); console.log('3. Or guard polyfill with feature detection'); }, // Resolve namespace collisions resolveNamespace: function(conflict) { console.log('Resolving namespace collision:', conflict.feature); console.log('Consider using ES modules instead of global variables'); }, // Prevent future conflicts preventConflicts: function() { // Freeze native methods to prevent override var methods = [ { obj: Array.prototype, names: ['map', 'filter', 'reduce', 'forEach'] }, { obj: Object, names: ['keys', 'values', 'entries', 'assign'] } ]; methods.forEach(function(group) { group.names.forEach(function(name) { if (group.obj[name]) { var descriptor = Object.getOwnPropertyDescriptor(group.obj, name); if (descriptor && descriptor.configurable) { Object.defineProperty(group.obj, name, { value: group.obj[name], writable: false, configurable: false, enumerable: false }); console.log('Protected:', group.obj.constructor.name + '.' + name); } } }); }); }};// Global helperwindow.checkPolyfillConflicts = function() { return PolyfillConflictResolver.detectConflicts();};window.resolvePolyfillConflicts = function() { PolyfillConflictResolver.resolveConflicts();};// Usage:// checkPolyfillConflicts();// resolvePolyfillConflicts();// PolyfillConflictResolver.preventConflicts();
Key Takeaways - Debugging & Troubleshooting
DevTools: Use Sources, Console, Network, Performance, and Coverage tabs
Common Issues: Load order, conflicts, CSP violations, performance