Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to control property enumeration (for...in) with Proxy objects?

Tags:

I'm wrapping an object in a Proxy and then iterate through it. How can I control the keys it iterates through?

The proxy works if I don't override the keys:

var obj = {"hello": "world"}
var proxy = new Proxy(obj, {})
for (var key in proxy){
    console.log(key)
}
// logs "Hello"

However, nothing is logged if I change the keys in the ownKeys handler.

var obj = {"hello": "world"}
var proxy = new Proxy(obj, {
    ownKeys: function(){
        return ["a", "b"]
    }
})
for (var key in proxy){
    console.log(key)
}
// Logs nothing

If I return "hello" as part of the ownKeys only "hello" is logged.

Apparently there was an enumerate trap in ES6, but it has been removed from ES7.

Is it still possible to control the for...in loop with a Proxy? Why was enumerate removed from the spec?

like image 482
Matt Zeunert Avatar asked Jul 26 '16 09:07

Matt Zeunert


People also ask

How do you use a proxy object?

Proxy objects are commonly used to log property accesses, validate, format, or sanitize inputs, and so on. You create a Proxy with two parameters: target : the original object which you want to proxy. handler : an object that defines which operations will be intercepted and how to redefine intercepted operations.

What are proxy objects?

A proxy object acts as an intermediary between the client and an accessible object. The purpose of the proxy object is to monitor the life span of the accessible object and to forward calls to the accessible object only if it is not destroyed.

What does a proxy do to the target object Ecmascript?

A proxy allows you to perform meta-programming operations such as intercepting a call to inspect or change an object's property. The original object the proxy will virtualize.

How does JavaScript proxy work?

Proxy is an object in javascript which wraps an object or a function and monitors it via something called target. Irrespective of the wrapped object or function existence. Proxy are similar to meta programming in other languages.


2 Answers

Unfortunately, it isn't possible to do this anymore.

As Brian Terlson (the editor of the EcmaScript Specification) wrote:

issue with proxy enumerate trap and for-in, where iimplementations are prevented from pre-populating the list of keys in the object, because the iterator causes observable affects. Which means the iterate must be pulled for every iteration. Last meeting we thought it would be ok if the enumerate trap exhausts the iterator, we thought that would solve the problem. The issue was, now their is an observable difference between an object and proxy of that object, mainly due to delete.

(Source: https://github.com/rwaldron/tc39-notes/blob/master/es7/2016-01/2016-01-28.md#5xix-proxy-enumerate---revisit-decision-to-exhaust-iterator via https://ecmascript-daily.github.io/2016/02/10/why-remove-enumerate-and-reflect-enumerate)

So it was removed due to technical challenges that could not be solved in a satisfactory manner.

has proxy trap

The in operator as such can still be captured using the has proxy trap:

var p = new Proxy({}, {
  has: function(target, prop) {
    if (prop === 'a') { return true; }
    return false;
  }
});
'a' in p; // true
'b' in p; // false

Alternative

As for (let key in proxy) loops are more of a legacy feature these days, you could use one of the following with the ownKeys proxy trap:

  • Object.keys() (own enumerable properties only)
  • Object.getOwnPropertyNames() (own properties)
  • Reflect.ownKeys() (own properties and Symbols)

enter image description here (Source: https://twitter.com/nilssolanki/status/659839340592422912)

(but you probably already knew that, seeing that you are working with proxies in the first place)

like image 149
nils Avatar answered Sep 20 '22 16:09

nils


User user2106769 suggestion and yeerk's answer of overriding the getOwnPropertyDescriptor to allow enumeration of Proxy attributes has a flaw that one should be aware of when using it, it doesn't set the value attribute when trapping getOwnPropertyDescriptor, thus some other code that depends on that behavior will not function correctly.

The code demonstrating the flaw and the solution to it is below:

var obj = { "hello": "world" };
var flawedProxy = new Proxy(obj, {
    ownKeys: function() {
        return ["a", "b"];
    },
    getOwnPropertyDescriptor: function(target, key) {
         return { enumerable: true, configurable: true };
    }
});

var goodProxy = new Proxy(obj, {
    get: function(target, key) {
      // modify something here if you want to
      return target[key];
    },
    ownKeys: function() {
        return ["a", "b"];
    },
    getOwnPropertyDescriptor: function(target, key) {
         return { value: this.get(target, key), enumerable: true, configurable: true };
    }
});

// value is accessible, getOwnPropertyDescriptor not trapped
console.log(Object.getOwnPropertyDescriptor(obj, 'hello').value);

// value is undefined, getOwnPropertyDescriptor not trapped correctly
console.log(Object.getOwnPropertyDescriptor(flawedProxy, 'hello').value);

// value is accessible, getOwnPropertyDescriptor trapped correctly
console.log(Object.getOwnPropertyDescriptor(goodProxy, 'hello').value);
like image 28
Pärt Johanson Avatar answered Sep 19 '22 16:09

Pärt Johanson