blog
javascript

Built-in-like Range in JavaScript

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


Written on October 6, 2021. Originally posted on dev.to .

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


Basic functionality

You start by overriding the prototype of Number with itself, but proxed.

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.

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.

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.

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

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

(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...

[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.

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

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.