DEV Community

Discussion on: PolyFull - enhance js capabilities

Collapse
 
lionelrowe profile image
lionel-rowe • Edited

This isn't polyfilling, it's monkey patching. Polyfills are to add support for features that are in the spec but not yet supported by all environments. Polyfills are considered safe, because all they're doing is improving consistency between the different environments.

Monkey patching, however, is stuff that will almost certainly never be in the spec, but might clash with other things added to the spec later on, or with other rogue libraries that decide to monkey patch stuff, because it's all global.

There is one way and only one way to monkey patch safely: by using unique symbols:

// my-fancy-lib/index.js

export const remove = Symbol('remove')

Array.prototype[remove] = function(val) {
    const idx = this.indexOf(val)

    if (idx !== -1) this.splice(idx, 1)

    return this
}

// my-app/src/my-lib-consumer.js

import { remove } from 'my-fancy-lib'

const arr = [1, 2, 3]

arr[remove](2)[remove](3) // [1]
Enter fullscreen mode Exit fullscreen mode

That works fine and won't pollute any global namespaces, because the symbol used to access the new method is only accessible by importing it directly from the library (or by doing something convoluted like Object.getOwnPropertySymbols(Array.prototype).find(({ description }) => description === 'remove'), which a consumer of your library wouldn't plausibly do by mistake).

As shiny and cool as symbols are, however, in my opinion it's better not to monkey patch at all, and instead to just use plain old functions that take the target array as a parameter. If you want to keep the nice left-to-right syntax you get from method chaining, you can implement the API with higher-order functions and use a pipe function to compose them:

// my-fancy-lib/index.js

export const pipe = (initialVal, ...fns) => fns.reduce((val, fn) => fn(val), initialVal)

export const remove = (val) => (arr) => {
    const idx = arr.indexOf(val)

    return [
        ...arr.slice(0, Math.max(idx, 0)),
        ...arr.slice(idx + 1),
    ]
}

// my-app/src/my-lib-consumer.js

import { pipe, remove } from 'my-fancy-lib'

const arr = [1, 2, 3]

pipe(arr, remove(2), remove(3)) // [1]
Enter fullscreen mode Exit fullscreen mode

This version also has the added advantage of not mutating the original array.