正确构建数组的Javascript代理集处理程序 [英] Properly building Javascript proxy set handlers for arrays

查看:60
本文介绍了正确构建数组的Javascript代理集处理程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

What is the correct way to build Javascript proxies for arrays so that 'set' handlers do not get invoked multiple times for a single change to the array?

这是我的意思:

我想在Proxy对象中包装一个简单的数组.当我希望将新值push()到此Proxy对象时,我希望运行一个设置"处理程序.

I want to wrap a simple array in a Proxy object. I want a 'set' handler to run when I wish to push() a new value to this Proxy object.

麻烦的是,对于数组的一项操作,像'set'这样的代理处理程序被多次调用.在某些情况下,似乎很容易解决该问题,但在其他情况下,一个调用来修改包装在Proxy对象中的数组,则每次至少都要调用一次设置处理程序元素.

The trouble is that proxy handlers like 'set' get called multiple times for one operation to an array. In some cases it seems fairly easy to deal with the problem but in other cases one call to modify an array wrapped in a Proxy object cases the set handler to be called at least one time for every element.

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

Suppose I created the simplest Proxy handler object and Proxy like this:

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

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

This proxy is just intercepting 'set-like' calls to my proxied array and writing a message to console. Now, when I add a new element to the end of my proxyArray object:

proxyArray.push("three")

我会得到这样的东西:

Set handler is invoked on property: 3 with value: three
Set handler is invoked on property: length with value: 4

我看到设置处理程序被称为两次:一次用于在数组中创建新元素,再一次用于设置数组的新length属性.

I see that the set handler got called twice: once for the creation of a new element in the array and once more for setting the new length property of the array.

好的,可以通过检查被操纵的属性来解决此问题.我已经看到set属性做了这样的事情:

Ok, this issue can be handled by checking for the property being manipulated. I've seen set properties done something like this:

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);
}

proxyArray.push("three")的相同调用将对除 length 属性之外的所有内容执行重要的操作.这是因为我正在检查是否设置了 length 属性.对我来说这没关系.

The same call to proxyArray.push("three") will perform the important things on all but the length property. This is because I'm checking if the length property is being set. This seems ok to me.

但是,假设我只是想简单地splice()数组中的某些内容?:

But, suppose I want to simply splice() something out of my array?:

proxyArray.splice(0,1);

这会对数组中的每个元素产生一个设置"调用:

That produces one 'set' invocation for every element in the array:

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

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

This is certainly not what I wanted. I wanted my set handler to run once on the 'splice()', not three times.

此外,对于相同的splice()操作,多次触发设置"方法会带来非常讨厌的副作用.通过将"set"处理程序更改为该数组来查看数组的内容:

What is more, there's a very nasty side-effect of having 'set' methods triggered multiple times for the same splice() operation. Looking at the contents of the array by changing the 'set' handler to this:

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;
}

会产生这个:

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"]

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

So, Javascript is shifting each value down the array, one at a time, then popping off the last, duplicate element as the last step. Your set handler would have to be built to handle that sort of intermediate duplication.

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

That would seem to yield nasty and complicated 'set' handlers.

那么,构建包裹在数组周围的Javascript代理的正确方法是什么,以使设置"处理程序不会多次被不必要地调用,并且目标对象在此过程中是可靠的?

So, what is the proper way to build Javascript proxies wrapped around arrays so that 'set' handlers do not get invoked an unwanted multiple of times and the target object is reliable in the process?

推荐答案

发生的最终问题是代理服务器显式地处理了.splice()调用(意味着,所有与从元素中拉出元素有关的操作)数组,对该数组重新索引以及修改length属性都将通过代理进行-如果您调用proxyArray.splice()则这是预期的行为,因为splice的上下文将是代理而不是基础数组).

The ultimate problem with what's going on is that the proxy is explicitly handling the .splice() call (meaning, all the operations that involve pulling an element out of the array, reindexing that array, and modifying the length property will all go through the proxy--which is intended behavior if you call proxyArray.splice() because the context of splice will be the proxy and not the underlying array).

该问题的解决方案是允许splice掉入"到基础阵列,从而绕过代理.为此,我们需要向代理添加get陷阱,并在调用splice时进行侦听,以确保它在数组本身上发生.

The solution to the problem is to allow splice to "fall through" to the underlying array, thereby bypassing the proxy. In order to do this we need to add a get trap to the proxy and listen for when we are calling splice so that we can make sure it happens on the array itself.

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];
    }
};

关于这件事的几件事:

  • 使用任何参数调用proxyArray.splice()都将返回一个函数,该函数调用我们从原始数组中拔出的方法(我们必须将其绑定到该数组,因为我们正在通过代理调用此方法,这意味着this将指向该代理,并且我们的行为会有所不同),并且所有参数都通过
  • 我建议您在此处放置您希望set陷阱执行的任何特殊逻辑;从技术上讲,您可以执行一些操作来手动调用set陷阱并触发您的逻辑,但是最好在这里调用该逻辑,以使代码的复杂度和模块化程度降低
  • 这将完全绕过set陷阱,但是代理将反映您对基础数组的所有更改
  • 您可以通过检查要渗入"原始对象的任何属性来更改prop === 'splice',因此push()/shift()/etc.可以用同样的方式处理
  • calling proxyArray.splice() with whatever your arguments are will return a function that calls the method which we plucked off the original array (we have to bind it to the array because we are calling this method through the proxy meaning the this will be pointing to that proxy and our behavior will be different) with all the arguments passed through
  • I recommend you place here any of the special logic you wanted your set trap to do; you could technically do something to manually invoke the set trap and trigger your logic, but it would be better to call that logic here to keep code less convoluted and more modular
  • this will completely bypass the set trap, but the proxy will reflect all your changes to the underlying array
  • you can change prop === 'splice' with a check for any of the properties you want to "bleed" through to the original object, so push()/shift()/etc. can be handled in this same manner

这篇关于正确构建数组的Javascript代理集处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆