JS Magic: Making a Range Syntax for Numbers
Tags: showdev, webdev, javascript, programming
Published: 2023-03-24 10:02:40

### **WARNING**: using this technique in production code may result in injury or death during code reviews. Use at your own risk.

I've seen the following code [around](https://davidltran.com/blog/generate-range-numbers-js/#a-cool-trick) a few times to 'spread' a number in JS:
```js
Number.prototype[Symbol.iterator] = function *range() {
  for (let i = 0; i <= this; i++) {
    yield i;
  }
}
const numbers = [...3]
console.log(numbers); // [0, 1, 2, 3]
```
(If you don't understand what's going on here, we're making an object into an iterable by giving it an iterator method - [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator) explains it pretty well)
 
This is kind of cool I guess, but the range always starts from a fixed value (0 in this case) - which is kind of a pain. Wouldn't it be great if we could take it up a notch and do this:
```js
console.log([3...7])
```
Well, tough s**t, we can't. So, how about this?
```js
console.log(3[...7])
```
Again, nope - `Uncaught SyntaxError: expected expression, got '...'`.

Not quite so pretty, but how about this?
```js
console.log(3[[...7]])
```
This gives us another error: `Uncaught TypeError: 7 is not iterable` - but, as we saw above we CAN make numbers iterable, and running the above code after first doing that results in... no errors, and 'undefined' logged in the console. It appears we may have a route to a solution...

Long story short - I dreamed up the following solution (yes, I'm not normal). It combines making a number iterable (but not in the same way as above), with some ideas from [Metho](https://dev.to/jonrandy/introducing-metho-safely-adding-superpowers-to-js-1lj), [Metho-Number](https://dev.to/jonrandy/js-number-extensions-with-metho-2fn6) and [Turboprop](https://dev.to/jonrandy/turboprop-js-arrays-as-property-accessors-52h). Gold stars to everyone who follows how it works ⭐:
```js
// When we 'spread' a number, set up a temporary method on the Number
// prototype that will return our final result, and get the Symbol
// that is the 'name' of this method
Number.prototype[Symbol.iterator] = function *range() {
  yield attachTempNumRangeMethod(+this)
}

// Create a preconfigured range method, and attach it to the Number
// prototype (idea from Metho)
function attachTempNumRangeMethod(rangeEnd) {
  const s = Symbol()
  Object.defineProperty(
    Number.prototype, s,
    { configurable: true, get: makeRangeMethod(rangeEnd, s) }
  )
  return s
}

// Create a function to count from or to the given end value, then
// delete itself from the Number prototype (idea from Metho-number)
function makeRangeMethod(end, sym) {
  return function range() {
    const step = this<=end ? 1 : -1
    let arr = [], i, d = end>this
    for (i=+this; d ? i<=end : i>=end; i+=step) arr.push(i)
    delete Number.prototype[sym]
    return arr
  }
}

// Set up a 'toPrimitive' method on the Array prototype so we can
// hijack it if we spot an array containing a Number method symbol
// we previously set up (idea from Turboprop)
Array.prototype[Symbol.toPrimitive] = function (hint) {
  // this is dangerous, so try to and leave all default behaviour alone:
  if (hint === 'default') return this.toString()
  if (hint === 'number') return Number(this.toString())
  if (this.length != 1 && !Number.prototype.hasOwnProperty(this[0]))
    return this.toString()
  // return our symbol that names the Number method
  return this[0]
}
```
Still with me ? Let's test it:
```js
37[[...42]].forEach(n => console.log(n))
// 37
// 38
// 39
// 40
// 41
// 42

console.log(10[[...1]])
// Array(10) [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ]
```
## TA-DA 🎉
Tune in again next time for more JavaScript syntax abuse!

![written by human](https://notbyai.fyi/img/written-by-human-not-by-ai-black.svg)

---

## Further reading
{% embed https://dev.to/jonrandy/introducing-metho-safely-adding-superpowers-to-js-1lj %}
{% embed https://dev.to/jonrandy/js-number-extensions-with-metho-2fn6 %}
{% embed https://dev.to/jonrandy/turboprop-js-arrays-as-property-accessors-52h %}