BFE.dev challenge #88 support negative Array index in JavaScript
Tags: javascript, challenge, 100daysofcode
Published: 2020-08-24 14:29:37

> https://bfe.dev is like a LeetCode for FrontEnd developers. I’m using it to practice my skills.

This article is about the coding problem [BFE.dev#88. support negative Array index in JavaScript](https://bigfrontend.dev/problem/support-negative-Array-index)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ty67kfyx5ny3zk8xuifp.png)

## Look at the example

```js
const originalArr = [1,2,3]
const arr = wrap(originalArr)

arr[0] // 1
arr[1] // 2
arr[2] // 3
arr[3] // undefined
arr[-1] // 3
arr[-2] // 2
arr[-3] // 1
arr[-4] // undefined
```

We could return a new object and hard set the properties with values, like `{0:1, 1:2, 2:3, -1:3, -2:2, -3:1}`.

But it looks impossible to sync the data to the original array as follows.

```js
arr[-1] = 5
arr // [2,3,5]
originalArr // [2,3,5]
```

We could use getter/setter to do the sync, like:

```js
Object.defineProperty(arr, '-1', {
  get() {
     return originalArr[2]
  },
  set(value) {
     originalArr[2] = value
  }
})

```

But there are not only `-1`, but also `-2` ...`-originalArr.length`. It is to maintain the hardcoded indices when length changes.

## Proxy comes the rescue.

From the problem, we could notice that what we want is actually a `Proxy`

1. we apply everything to the original array
2. only for negative indices, we tweak a little

First let's write the basic Proxy:

```js
function wrap(arr) {
  return new Proxy(arr, {
    get(target, prop) {
      return target[prop]
    },
    
    set(target, prop, value) {
      target[prop] = value
      return true
    }
  })
}     
```

This returns a proxy that proxies everything. So test cases for non-negative index actually passes, but not the negative case, of course.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hbk3pdrp7e8upnri7gc0.jpg)

## Tweak the index

Notice the prop is not number, we convert it to the non-negative index first before we apply to the original array.

```js

get(target, prop) {
  let index = parseInt(prop, 10)
  // negative index
  if (index < 0) {
    index += target.length;
    return target[index]
  }

  return target[prop]
},

set(target, prop, value) {
  let index = parseInt(prop, 10)
  // negative index
  if (index < 0) {
    index += target.length;
    // negative index overflowed, too small
    if (index < 0) {
      throw new Error('not ok')
    }
    target[index] = value
    return true
  }

  target[prop] = value
  // don't forget to return true to indicate success
  return true
}                   
```

## not iterable!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8gchbu7gljbg74f22yi9.jpg)

Oops, `[...arr]` doesn't work on our proxy.

`[...arr]` actually calls the `Symbol.Iterator` on the target, since our proxy doesn't hold the data, it needs to run the iterator on the original array. `Function.prototype.bind()` is a good choice.

```js
get(target, prop) {
  if (prop === Symbol.iterator) {
    return target[Symbol.iterator].bind(target)
  }
  ...
}
```

## Passed!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/81tfmgog3soekjw00nvj.gif)

This is pretty interesting problem, maybe you can also have a try at https://bfe.dev