区分代理获取处理程序中的直接访问和内部访问

Distinguish direct access from internal access in Proxy get handler

我有一个数组:

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

我使用 get 处理程序为该数组创建了一个代理,它将 return 属性 和每个数字 nn - 1 索引。例如,p[1] 将 return 'a'p[2]'b'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]
  }
})

它似乎工作正常。 p[2],例如,它应该给出 'b'。但是,还有另一个问题。 Array.prototype.indexOf() 不再正常工作。当我通过 'a''b' 时它确实有效 – 它 return 分别是 12,但是对于 'c' 它 returns -1。事实证明 indexOf() 也会触发 get 处理程序。通过将 console.log(property) 添加到 get 处理程序,我们得到 p.indexOf('c') 的以下输出:

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

似乎indexOf()在内部检查length属性,然后从索引0length - 1迭代数组。

如果我知道 属性 是直接访问(如 p[2])还是内部访问,那么解决这个问题就很简单了。然后我总是可以 return target[property] 进行内部访问(所以代理将是无操作的)。

如何区分 Proxy get 处理程序中的直接访问和内部访问?

我唯一想到的就是抛出一个错误,捕获它并分析它的堆栈。不过,这似乎是一种解决方法,而不是实际的解决方案,所以我想避免使用它,除非没有其他办法。

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

是的。这是所有内置数组方法的行为。

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

你不能。

It would be trivial to fix that if I knew whether the property is accessed directly or internally.

不,这是错误的做法。如果您期望 forEach 的行为与正常循环不同,那么您的语义就有问题了。也许您实际上也想拦截 .length 属性?或者环绕索引 0?在不知道您要解决的问题的情况下,我们几乎无法提出任何有用的建议。

一种极端的措施是编写您自己的所有数组方法版本来处理单索引数组。

正如 torazaburo 在评论中所建议的那样,一种可能的解决方案是 return get 处理程序中的 indexOf 属性 一个不同的函数并进行必要的操作那里。具体来说,我们可以让 indexOf() 方法作用于原始数组,而不是代理;并将 1 添加到结果中。

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

工作代码片段:

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'))

其他数组方法可以类推