简体   繁体   English

如何观察通过setter-getter或Proxy公开的对象的array属性内容的更改

[英]How to observe changes to contents of an object's array property exposed through setter-getter or Proxy

Using getter/setter 使用getter / setter

I'm creating an IIFE like below. 我正在创建如下的IIFE。 It returns getters and setters to an array variable stored internally. 它将getter和setter返回到内部存储的数组变量。 I wish to intercept changes made to that array - the console.log is intended to indicate that in the setter below. 我希望拦截对该数组所做的更改console.log旨在在下面的设置器中表明这一点。

const a = (function() {
  let arr = [];

  return {
      get arr() {return arr},
      set arr(v) {
          console.log("new arr", v);
          arr = v;
      },
  }
})();

This works fine if I completely reassign arr , eg a.arr = [1, 2] . 如果我完全重新分配arr ,例如a.arr = [1, 2] ,则此方法很好。

But it doesn't intercept changes made to contents of the array, eg a.arr.push(3) or a.arr.shift() . 但是它不会拦截对数组内容所做的更改,例如a.arr.push(3)a.arr.shift()

Looking for any ideas on how to intercept these content changes. 寻找有关如何拦截这些内容更改的任何想法。

Using Proxy 使用代理

This is an alternate attempt using the new Proxy object: 这是使用新的Proxy对象的替代尝试:

a = (function() {

    let details = {
        arr: []
    }

    function onChangeProxy(object, onChange) {
        const handler = {
            apply: function (target, thisArg, argumentsList) {
                onChange(thisArg, argumentsList);
                return thisArg[target].apply(this, argumentsList);
            },
            defineProperty: function (target, property, descriptor) {
                Reflect.defineProperty(target, property, descriptor);
                onChange(property, descriptor);
                return true;
            },
            deleteProperty: function(target, property) {
                Reflect.deleteProperty(target, property);
                onChange(property, descriptor);
                return;
            }
        };

        return new Proxy(object, handler);
    };

    return onChangeProxy(details, (p, d) => console.log(p, d));

})();

The problem remains the same. 问题仍然存在。 Still unable to observe changes made to the contents of a.arr using anything from a.arr[0] = 1 to a.push(3) . 仍然无法使用从a.arr[0] = 1a.push(3)任何内容观察对a.arr内容所做的更改。

Update: The elegant solution (courtesy Kris Pruden and Sindre Sorhus) 更新:优雅的解决方案(由Kris Pruden和Sindre Sorhus提供)

The solution is based on this commit by Kris on Sindre's 'on-change' library . 解决方案基于Kris在Sindre的“ on-change”库中的 提交

Explanation of the solution , by Kris: 解决方案的解释 ,由Kris撰写:

In the set trap, my goal is to determine if the value provided is a Proxy produced by a previous call to the get trap. set陷阱中,我的目标是确定所提供的值是否是先前调用get陷阱所产生的Proxy If it is such a Proxy , any property access will be intercepted by our own get trap. 如果是这样的Proxy ,则任何属性访问都将被我们自己的get陷阱拦截。 So, when I access value[proxyTarget] our get trap will be invoked, which is coded to return target when property === proxyTarget (line 46). 因此,当我访问value[proxyTarget]将调用我们的get陷阱,当property === proxyTarget时,该陷阱将被编码为返回target (第46行)。 If the value passed to set is not an on-change-created Proxy, then value[proxyTarget] is undefined . 如果传递给set的值不是基于变更创建的代理,则value[proxyTarget]undefined

Full code of the solution: 解决方案的完整代码:

(object, onChange) => {
    let inApply = false;
    let changed = false;

    function handleChange() {
        if (!inApply) {
            onChange();
        } else if (!changed) {
            changed = true;
        }
    }

    const handler = {
        get(target, property, receiver) {
            const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
            const value = Reflect.get(target, property, receiver);

            // Preserve invariants
            if (descriptor && !descriptor.configurable) {
                if (descriptor.set && !descriptor.get) {
                    return undefined;
                }
                if (descriptor.writable === false) {
                    return value;
                }
            }

            try {
                return new Proxy(value, handler);
            } catch (_) {
                return value;
            }
        },
        set(target, property, value) {
            const result = Reflect.set(target, property, value);

            handleChange();

            return result;
        },
        defineProperty(target, property, descriptor) {
            const result = Reflect.defineProperty(target, property, descriptor);

            handleChange();

            return result;
        },
        deleteProperty(target, property) {
            const result = Reflect.deleteProperty(target, property);

            handleChange();

            return result;
        },
        apply(target, thisArg, argumentsList) {
            if (!inApply) {
                inApply = true;
                const result = Reflect.apply(target, thisArg, argumentsList);
                if (changed) {
                    onChange();
                }
                inApply = false;
                changed = false;
                return result;
            }

            return Reflect.apply(target, thisArg, argumentsList);
        }
    };

    return new Proxy(object, handler);
};

This has solved my problem, without resorting to the hack of checking for array modifying methods. 这解决了我的问题,而无需借助检查数组修改方法的技巧。


Original solution: 原始解决方案:

I've sorted this, for now, with help from David Walsh's post here . David Walsh在这里的帖子的帮助下,到目前为止,我已经对此进行了排序。 It's still ugly, but works for now. 它仍然很难看,但是现在可以使用。

Updated the onChanged Proxy maker with a recursive-ish get trap. 更新了具有递归式get陷阱的onChanged Proxy maker。

get: function (target, property, receiver) {
    let retval;

    try {
        retval = new Proxy(target[property], handler);
    } catch (err) {
        retval = Reflect.get(target, property, receiver);
    }

    if (mutators.includes(property))
        onChange(target, property, receiver);

    return retval;
},

Also added a list of functions to check the get trap against (the ugly, hacky bit): 还添加了用于检查获取陷阱的函数列表(丑陋,hacky):

const mutators = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "reverse",
    "fill",
    "sort"
]

This seems to be working in my testing so far. 到目前为止,这似乎在我的测试中起作用。

Thanks for pointing in the correct direction. 感谢您指出正确的方向。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM