Built-in-like Range in JavaScript
Tags: javascript, webdev, programming, tutorial
Published: 2021-10-06 23:49:21


## Premise

Make it possible to generate any range of integers with built-in-like syntax.

> **Motivation?**
Honestly, none. Zero. Except for fun & study.

***

## Basic functionality

You start by overriding the **prototype** of `Number` with itself, but _proxed_.

```js
Object.setPrototypeOf(
  Number.prototype,
  new Proxy(Number.prototype, {
    // ...
  })
)
```

In this way, any normal operations related to the prototype are not lost.

In the proxy you listen for access to any property via a **getter**. The third argument (`receiver`) is the "object", in this case the number itself - you call it `start`. It's already the right type, **number**.

The second argument corresponds to the _name of the property_, its `typeof` is indeed `string`.

```js
Object.setPrototypeOf(
  Number.prototype,
  new Proxy(Number.prototype, {
    get(_, _end, start) {
      // _end -> '182' (typeof string)
      // start -> 42 (typeof number)
    },
  })
)

(42)[182]
```

It is sufficient to use `parseInt` and, if it still `isNaN` just throw an error/warning. Or just ignore it silently and fallback by returning `start`.

```js
let end = parseInt(_end)
if (isNaN(end)) {
  // warning or error
  // eventually, fallback
  return start
}
```

Assured that the `typeof end` is also `number`, you can proceed to generate the **range**.

```js
return Array(end - start + 1)
  .fill()
  .map((_, i) => start + i)
```

Basic functionality is complete. Now the following code is perfectly valid.

```js
(0)[5] // [0, 1, 2, 3, 4, 5]
```

> To make it not-end-inclusive, use
`Array(end - start)` instead of `Array(end - start + 1)`.

***

## Reverse range

To be able to do something like the following...

```js
[5](0) // [5, 4, 3, 2, 1, 0]
```

Check if `start > end` and if so **swap** both. Don't forget to sort the result in descending order.

The code is self-explanatory.

```js
Object.setPrototypeOf(
  Number.prototype,
  new Proxy(Number.prototype, {
    get(_, _end, start) {
      // where (start)[_end]

      let end = parseInt(_end)
      if (isNaN(end)) {
        // warning or error
        // eventually, fallback
        return start
      }

      // sort behaviour - default ASC
      let s = +1

      if (start > end) {
        // swap
        let tmp = start
        start = end
        end = tmp

        // sort behaviour - DESC
        s = -1
      }

      // generate range
      return Array(end - start + 1)
        .fill()
        .map((_, i) => start + i)
        .sort(() => s)
    },
  })
)
```

## Result

```js
42             // 42
(0)[5]         // [0, 1, 2, 3, 4, 5]
(0)['foo']     // #fallback -> 0
(3)[7]         // [3, 4, 5, 6, 7]
(8)[3]         // [8, 7, 6, 5, 4, 3]
```

***

> **Couldn't I have done the same thing with a range function?**
Yes, probably _you should_ do it with a function.

Let this be a mental exercise and a way of making friends with the concept of prototype and proxy.

If you want to chat about nerdy things or just say hi, you can find me here:
* [GitHub](https://github.com/didof)
* [Twitter](https://twitter.com/did0f)
* [LinkedIn](https://www.linkedin.com/in/didof/)