Search code examples
javascriptecmascript-6es6-proxy

Distinguish direct access from internal access in Proxy get handler


I have an array:

const arr = ['a', 'b', 'c']

I create a proxy for that array with a get handler, which will return property with n - 1 index for every number n. For example, p[1] will return 'a', p[2]'b' and p[3]'c'.

const p = new Proxy(arr, {
  get: (target, property, receiver) => {
    const parsed = parseInt(property, 10)
    if (!Number.isNaN(parsed)) return target[parsed - 1]
    return target[property]
  }
})

It seems to be working fine. p[2], for instance, gives 'b' as it should. However, there's another problem. Array.prototype.indexOf() is not working correctly anymore. It does work when I pass 'a' or 'b' – it returns respectively 1 and 2, but for 'c' it returns -1. It turns out that indexOf() also triggers the get handler. By adding console.log(property) to the get handler we get the following output for p.indexOf('c'):

'indexOf'
'length'
'0'
'1'
'2'

It seems that indexOf() internally checks the length property, and then iterates the array from index 0 to length - 1.

It would be trivial to fix that if I knew whether the property is accessed directly (like p[2]) or internally. Then I could always return target[property] for internal access (so the proxy would be no-op).

How to distinguish direct access from internal access in Proxy get handler?

The only thing that comes to my mind is throwing an error, catching it and analyzing its stack. Still, this seems to be rather a workaround than an actual solution, so I would like to avoid it, unless there's no other way.


Solution

  • As suggested by torazaburo in comments, one possible solution is to return a different function for the indexOf property in the get handler and do the necessary manipulation there. Specifically, we can make the indexOf() method work on the original array, instead of the proxy; and add 1 to the result.

    if (property === 'indexOf') {
      return (...args) => {
        const result = Reflect.apply(target.indexOf, target, args)
        return result === -1 ? result : result + 1
      }
    }
    

    Working code snippet:

    const arr = ['a', 'b', 'c']
    const p = new Proxy(arr, {
      get: function(target, property, receiver) {
        if (property === 'indexOf') {
          return (...args) => {
            const result = Reflect.apply(target.indexOf, target, args)
            return result === -1 ? result : result + 1
          }
        }
        const parsed = parseInt(property, 10)
        if (!Number.isNaN(parsed)) return target[parsed - 1]
        return target[property]
      }
    })
    console.log(p.indexOf('c'))

    Other array methods can be fixed analogically.