ECMAScript Language Feature Polyfills

1. Array Methods (map, filter, find, findIndex, includes)

Method Syntax Returns ES Version
map arr.map(callback(el, i, arr)) New array with transformed elements ES5
filter arr.filter(callback(el, i, arr)) New array with elements that pass test ES5
find arr.find(callback(el, i, arr)) First element that passes test or undefined ES6/ES2015
findIndex arr.findIndex(callback(el, i, arr)) Index of first matching element or -1 ES6/ES2015
includes arr.includes(searchElement, fromIndex) Boolean - true if element found ES7/ES2016
forEach arr.forEach(callback(el, i, arr)) undefined (executes function for each) ES5

Example: Array.prototype.map polyfill

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 polyfill
if (!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 polyfill
if (!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;
    };
}

Example: Array.prototype.includes polyfill (handles NaN)

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;
    };
}

2. Object Methods (assign, keys, values, entries, fromEntries)

Method Syntax Returns ES Version
Object.assign Object.assign(target, ...sources) Target object with merged properties ES6/ES2015
Object.keys Object.keys(obj) Array of object's own enumerable keys ES5
Object.values Object.values(obj) Array of object's own enumerable values ES8/ES2017
Object.entries Object.entries(obj) Array of [key, value] pairs ES8/ES2017
Object.fromEntries Object.fromEntries(iterable) Object from [key, value] pairs ES10/ES2019
Object.create Object.create(proto, props) New object with specified prototype ES5

Example: Object.assign polyfill

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 polyfill
if (!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 polyfill
if (!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 polyfill
if (!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 polyfill
if (!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;
    };
}

3. String Methods (includes, startsWith, endsWith, padStart, padEnd)

Method Syntax Returns ES Version
includes str.includes(search, position) Boolean - true if substring found ES6/ES2015
startsWith str.startsWith(search, position) Boolean - true if starts with substring ES6/ES2015
endsWith str.endsWith(search, length) Boolean - true if ends with substring ES6/ES2015
padStart str.padStart(targetLength, padString) String padded from start to target length ES8/ES2017
padEnd str.padEnd(targetLength, padString) String padded from end to target length ES8/ES2017
trim str.trim() String with whitespace removed from both ends ES5

Example: String search method polyfills

// String.prototype.includes
if (!String.prototype.includes) {
    String.prototype.includes = function(search, start) {
        'use strict';
        if (search instanceof RegExp) {
            throw new TypeError('first argument must not be a RegExp');
        }
        if (start === undefined) start = 0;
        return this.indexOf(search, start) !== -1;
    };
}

// String.prototype.startsWith
if (!String.prototype.startsWith) {
    String.prototype.startsWith = function(search, pos) {
        pos = !pos || pos < 0 ? 0 : +pos;
        return this.substring(pos, pos + search.length) === search;
    };
}

// String.prototype.endsWith
if (!String.prototype.endsWith) {
    String.prototype.endsWith = function(search, this_len) {
        if (this_len === undefined || this_len > this.length) {
            this_len = this.length;
        }
        return this.substring(this_len - search.length, this_len) === search;
    };
}

Example: String padding polyfills

// String.prototype.padStart
if (!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.padEnd
if (!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;
    };
}

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 isNaN
if (!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 isFinite
if (!Number.isFinite) {
    Number.isFinite = function(value) {
        return typeof value === 'number' && isFinite(value);
    };
}

// Number.isInteger
if (!Number.isInteger) {
    Number.isInteger = function(value) {
        return typeof value === 'number' &&
               isFinite(value) &&
               Math.floor(value) === value;
    };
}

// Number.isSafeInteger
if (!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 polyfills
if (!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.

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 example
var sym1 = Symbol('description');
var sym2 = Symbol.for('global');
var sym3 = Symbol.for('global');
console.log(sym2 === sym3);  // true
console.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.

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 object
var 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 objects
function 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