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. 可以用同样的方式处理