admin管理员组

文章数量:1386706

I wrote a small wrapper to return undefined in place of typeError when accessing properties that don't exist using a Proxy. Here is the code:

function proxify(event) {
    var proxy = new Proxy(event, {
        get: function (target, property) {
            if (property in target) {
                return target[property];
            } else {
                    return '';
            }
          }
        }
    });
    return proxy;
}

This works when a property is missing 1 level deep. For example, assuming obj.something does not exist:

obj.something.else

will return undefined

But if the object property is deep nested

obj.something.else.deeper

I receive a typeError

My question is how do I extend the function above to work on deep nested objects?

Thx

I wrote a small wrapper to return undefined in place of typeError when accessing properties that don't exist using a Proxy. Here is the code:

function proxify(event) {
    var proxy = new Proxy(event, {
        get: function (target, property) {
            if (property in target) {
                return target[property];
            } else {
                    return '';
            }
          }
        }
    });
    return proxy;
}

This works when a property is missing 1 level deep. For example, assuming obj.something does not exist:

obj.something.else

will return undefined

But if the object property is deep nested

obj.something.else.deeper

I receive a typeError

My question is how do I extend the function above to work on deep nested objects?

Thx

Share Improve this question asked Mar 22, 2017 at 21:16 CyphCyph 6411 gold badge7 silver badges25 bronze badges 6
  • You need to make obj.something return another such proxy, not a string. – Bergi Commented Mar 22, 2017 at 23:13
  • @Bergi it does not seem there is a way to determine how many levels deep the property is located. If I knew that the parent was non-existent I could call another proxy. Would love an example if I'm missing something. thx – Cyph Commented Mar 23, 2017 at 0:44
  • There is no way to determine that, it's just dynamic property access. You need to always return a proxy. – Bergi Commented Mar 23, 2017 at 1:02
  • @Bergi can you show how as an 'answer'? – Cyph Commented Mar 23, 2017 at 1:27
  • @Bergi i should add, i tried to implement what you suggested and I got some undesirable results so I'm assuming I misunderstood something – Cyph Commented Mar 23, 2017 at 1:29
 |  Show 1 more ment

3 Answers 3

Reset to default 4

You need to wrap the return value in your proxify function:

function proxify(event) {
  return isPrimitive(event) ? event : new Proxy(event, { get: getProp });
}
function isPrimitive(v) {
  return v == null || (typeof v !== 'function' && typeof v !== 'object');
}
function getProp (target, property) {
  if (property in target) {
    return proxify(target[property]);
  } else {
    return proxify({});
  }
}

In all honesty, you're probably better off using something like lodash's _.get() or ramda's R.path(). There's no way to know from the first getProp() call how many layers deep the evaluation is going to go, so it has to always return a primitive value or a "truthy" Proxy instance so that the next property access can be intercepted (if one happens). The _.get() method on the other hand takes a string so it can immediately know from the initial invocation that you're only trying to access down so many levels.

I published a library on GitHub (Observable Slim) that enables you to proxy an object and any nested children of that object. It also has a few extra features:

  • Reports back to a specified callback whenever changes occur.
  • Will prevent user from trying to Proxy a Proxy.
  • Keeps a store of which objects have been proxied and will re-use existing proxies instead of creating new ones (very significant performance implications).
  • Written in ES5 and employs a forked version of the Proxy polyfill so it can be deployed in older browsers fairly easily and support Array mutation methods.

It works like this:

var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
    console.log(JSON.stringify(changes));
});

p.testing.blah = 42; // console:  [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]

As idbehold said, you must return a primitive value or new proxy. I think you dont need loDash to do that, and Yes, you can manage the depth of the nested proxy ! (see my last point, deepProxy. But, since I'm not sure what you want to do, i would like focus your attention to those few points:

First usage (simulated chain):


function proxify(event){
    return isPrimitive(event) ? event : new Proxy(event,{ get: getProp });
}

function isPrimitive(v){
    return v == null || (typeof v !== 'function' && typeof v !== 'object');
}

function getProp(target, property){
    return (property in target)
        ? proxify(target[property])
        : proxify({});
}

this code as it stands, will simulate nested properties, but not confer any members dependency (because no assignment, then no preservation).

obj.something.else = 99;
console.log(obj.something.else) // returning a new Proxy()

IMO, it doesn't make sense, or reserved to very specific cases. Again, it depend of what is your purpose.

Second usage (deep assignment):


Basic not extensible assignment

function proxify(defprop={})
{
    return new Proxy(defprop, handler);
};
var handler = 
{
    get: function(target, property, receiver)
    {
        if(!(property in target))
            target[property] = proxify();
        return Reflect.get(target, property, receiver);
    },
};

obj.something.else = 99;
console.log(obj.something.else) // returning 99

Extensible to assignment

By using basic chainable proxy, if you try to set a new object (or nested object) you will only trap the root of this new property. There is no more possible chainning, this is not suitable and not safe.

Remember the following case:

Cyph: But if the object property is deep nested

obj.something.else.deeper

(Bad way) Assuming this assignment:

var deeper = {toto: "titi"};
obj.something.else = deeper;

console.log(obj.something.else.toto)
// will return "titi"

obj.something.else.toto.something.else = {tutu: "tata"};
// will return a typeError Again

(Good way) To propagate the chain ability You need to check for the type of value as a true object in the setter trap, and proxify each root properties. The rest of the depth will be converted naturally by the getter trap.

var handler = 
{
    get: function(target, property, receiver)
    {
        if(!(property in target))
            target[property] = proxify();
        return Reflect.get(target, property, receiver);
    },
    set: function(target, property, value, receiver)
    {
        // extend proxify to appended nested object
        if(({}).toString.call(value) === "[object Object]")
            value = deepApply(receiver, property, value);

        return Reflect.set(target, property, value, receiver);
    },
};
var deepApply = function(receiver,property, data)
{
    var proxy = proxify();
    var props = Object.keys(data);
    var size = props.length;

    for(var i = 0; i < size; i++)
    {
        property = props[i];
        proxy[property] = data[property];
    }
    return proxy;
};

Manage the depth


I invite you to read my actual solution : deepProxy

  • The level can be auto-defined via the proto declaration when the object structure is setted. Then you can easily know the current floor and add conditional instructions for each level.
  • The path of property can be reach from his accessor.

本文标签: javascriptes6 proxy safe deep objectStack Overflow