正确构建数组的Javascript代理集处理程序

TCo*_*ert 7 javascript arrays proxy

为数组构建Javascript代理的正确方法是什么,以便对单个更改数组不会多次调用"set"处理程序?

这就是我的意思:

我想在Proxy对象中包装一个简单的数组.我希望在我希望将一个新值推送到此Proxy对象时运行'set'处理程序.

麻烦的是,代理处理程序像"设置"被调用多次一个操作为数组.在某些情况下,处理问题似乎相当容易,但在其他情况下,修改包含在Proxy对象中的数组的一次调用会使每个元素至少调用一次set处理程序.

假设我创建了最简单的代理处理程序对象和代理,如下所示:

let proxyHandlerObj = {
  set:function(target,property,value,receiver) {
    console.log("Set handler is invoked on property:",property,"with value:",value);
    /*
     * Important and interesting things done here
     */
    return (target[property] = value);
  }
};

let proxyArray = new Proxy(["zero","one","two"],proxyHandlerObj);
Run Code Online (Sandbox Code Playgroud)

这个代理只是拦截对我的代理数组的'set-like'调用并向控制台写一条消息.现在,当我在proxyArray对象的末尾添加一个新元素时:

proxyArray.push("three")

我会得到这样的东西:

Set handler is invoked on property: 3 with value: three
Set handler is invoked on property: length with value: 4
Run Code Online (Sandbox Code Playgroud)

我看到set处理程序被调用了两次:一次用于在数组中创建一个新元素,另一次用于设置数组的新长度属性.

好的,可以通过检查被操作的属性来处理此问题.我已经看到设置属性完成了这样的事情:

set:function(target,property,value,receiver) {
  if(property!="length") {
    console.log("Set handler is invoked on property:",property,"with value:",value);
    /*
     * Important and interesting things done here
     */
  }
  return (target[property] = value);
}
Run Code Online (Sandbox Code Playgroud)

proxyArray.push("three")除了长度属性之外,同样的调用将执行所有重要的事情.这是因为我正在检查是否正在设置length属性.这对我来说似乎没问题.

但是,假设我只想splice()从我的数组中得到一些东西?:

proxyArray.splice(0,1);
Run Code Online (Sandbox Code Playgroud)

这会为数组中的每个元素生成一个"set"调用:

Set handler is invoked on property: 0 with value: one
Set handler is invoked on property: 1 with value: two
Set handler is invoked on property: 2 with value: three
Run Code Online (Sandbox Code Playgroud)

这当然不是我想要的.我希望我的set处理程序在'splice()'上运行一次,而不是三次.

更重要的是,对于同一splice()操作,多次触发"设置"方法会产生非常令人讨厌的副作用.通过将'set'处理程序更改为此来查看数组的内容:

set:function(target,property,value,receiver) {
  if(property!="length") {
    console.log("Set handler is invoked on property:",property,"with value:",value);
    /*
     * Important and interesting things done here
     */
  }
  let result = (target[property] = value);
  console.log(JSON.stringify(target));
  return result;
}
Run Code Online (Sandbox Code Playgroud)

会产生这个:

Set handler is invoked on property: 0 with value: one
["one","one","two","three"]
Set handler is invoked on property: 1 with value: two
["one","two","two","three"]
Set handler is invoked on property: 2 with value: three
["one","two","three","three"]
["one","two","three"]
Run Code Online (Sandbox Code Playgroud)

因此,Javascript将每个值向下移动数组,一次一个,然后弹出最后一个重复元素作为最后一步.必须构建您的set处理程序来处理这种中间复制.

这似乎会产生令人讨厌和复杂的"设置"处理程序.

那么,构建围绕数组的Javascript代理的正确方法是什么,以便"set"处理程序不会被多次调用并且目标对象在此过程中是可靠的?

小智 5

正在发生的事情的最终问题是代理显式地处理.splice()调用(意思是,所有涉及从数组中拉出元素、重新索引该数组和修改length属性的操作都将通过代理——这是如果您调用预期的行为,proxyArray.splice()因为 的上下文splice将是代理而不是底层数组)。

问题的解决方案是允许splice“落入”底层数组,从而绕过代理。为了做到这一点,我们需要向get代理添加一个陷阱并监听我们何时调用,splice以便我们可以确保它发生在数组本身上。

const proxyHandlerObj = {
    set(tgt, prop, val, rcvr) {
        if (prop !== 'length') {
            /*
             * Important and interesting things done here
             */
        }
        return (tgt[prop] = val);
    },

    get(tgt, prop, rcvr) {
        if (prop === 'splice') {
            const origMethod = tgt[prop];

            return function (...args) {
                /*
                 * Do anything special here
                 */
                origMethod.apply(tgt, args);
            }
        }
        return tgt[prop];
    }
};
Run Code Online (Sandbox Code Playgroud)

所以有几件事关于这个:

  • proxyArray.splice()使用任何参数调用将返回一个函数,该函数调用我们从原始数组中提取的方法(我们必须将其绑定到数组,因为我们通过代理调用此方法,这意味着this将指向该代理和我们的行为将不同)通过所有参数
  • 我建议您将您希望set陷阱执行的任何特殊逻辑放在这里;从技术上讲,您可以做一些事情来手动调用set陷阱并触发您的逻辑,但最好在此处调用该逻辑以保持代码不那么复杂且更加模块化
  • 这将完全绕过set陷阱,但代理将反映您对底层数组的所有更改
  • 您可以prop === 'splice'通过检查要“渗透”到原始对象的任何属性来进行更改,因此push()/ shift()/etc. 可以用同样的方式处理