【问题标题】:Properly building Javascript proxy set handlers for arrays为数组正确构建 Javascript 代理集处理程序
【发布时间】:2017-08-06 04:15:25
【问题描述】:

为数组构建 Javascript 代理的正确方法是什么,这样“set”处理程序就不会被多次调用以对数组进行一次更改?

这就是我的意思:

我想在代理对象中包装一个简单的数组。当我希望 push() 一个新值到这个 Proxy 对象时,我希望运行一个“设置”处理程序。

问题在于,对于数组的一次操作,像“set”这样的代理处理程序会被多次调用。在某些情况下,处理该问题似乎相当容易,但在其他情况下,一次调用 来修改包装在 Proxy 对象中的数组会导致 set handler 至少被调用一次 every元素

假设我像这样创建了最简单的代理处理程序对象和代理:

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 对象的末尾添加一个新元素时:

proxyArray.push("three")

我会得到这样的东西:

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

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

好的,这个问题可以通过检查被操纵的属性来解决。我见过 set properties 做了这样的事情:

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 属性。这对我来说似乎没问题。

但是,假设我想从我的数组中简单地splice() 一些东西?:

proxyArray.splice(0,1);

这会为数组中的每个元素生成一个“集合”调用:

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()”上运行一次,而不是三次。

更重要的是,对于同一个splice() 操作多次触发“set”方法会产生非常讨厌的副作用。通过将“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;
}

会产生这个:

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 将每个值向下移动到数组中,一次一个,然后弹出最后一个重复元素作为最后一步。必须构建您的集合处理程序来处理这种中间重复。

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

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

【问题讨论】:

  • “这样 'set' 处理程序不会被多次调用并且目标对象在此过程中是可靠的?” set 有什么问题处理程序被多次调用?具体来说,“过程中的可靠”是要求的一部分?
  • 假设您有一组电子邮件地址。您想构建一个“设置”处理程序,当电子邮件地址数组更改时触发一些操作。也许,当添加电子邮件地址时。也许您不希望将单个 push() 多次触发到电子邮件地址数组中。更糟糕的是,如果您从数组中拼接出一个电子邮件地址,那么该集合处理程序将为数组中的每个元素运行。此外,“目标”对象(处理程序方法中的第一个参数)不准确,因为在拼接完成之前它有一个重复的元素。
  • 如果每次索引更改都调用处理程序时出现问题,使用set 处理程序的目的是什么?是否需要创建一个在数组内更改索引时不验证数组的.length 的函数?
  • 了解代理提供了一种方便的方法来拦截对象的基本操作并执行一些自定义行为。所以,假设我想创建一个自定义行为,每次添加地址时验证电子邮件地址。如果我 push() 一个新的电子邮件地址到我的电子邮件列表,我的小特殊验证功能会自动运行。但是,对于数组中的每个元素,一些基本功能每次都会触发我的验证功能——即使它已经过验证。那不是很好。
  • 您是否尝试过使用 JavaScript 普通对象替换 targetProxy() 调用中的数组?

标签: javascript arrays proxy


【解决方案1】:

发生的最终问题是代理正在显式处理.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];
    }
};

关于这件事有几点:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    • 1970-01-01
    • 2011-03-03
    • 1970-01-01
    • 1970-01-01
    • 2012-05-15
    相关资源
    最近更新 更多