80

How to access the target (which is myArray) of myProxy here?

function createProxy() {
  const myArray = [Math.random(), Math.random()];
  return new Proxy(myArray, {});
}

const myProxy = createProxy();

I'd like to have a getProxyTarget function that would return the original object without modifying any other part of the code:

let original;
function createProxy() {
  const myArray = [Math.random(), Math.random()];
  original = myArray;
  return new Proxy(myArray, {});
}

const myProxy = createProxy();

function getProxyTarget(proxy){ /* ... your code */ }

console.log(getProxyTarget(myProxy) === original) // should be true
user3840170
  • 26,597
  • 4
  • 30
  • 62
Adam
  • 4,985
  • 2
  • 29
  • 61
  • 3
    You can't. That's why it's a proxy. What are you trying to do, why do you need this? – Bergi Jun 29 '18 at 07:54
  • @Bergi Because if I have an object with circular references, and these circular references are in proxies, there is no way to *safe stringify* my object. I'll get stack size exceeded error. :( – Adam Jun 29 '18 at 07:59
  • 2
    Can you make an example of that, please? Circular references (I think you mean those, not *dependencies*) should work just fine with proxies, as `myProxy === myProxy` still holds. Nothing needs to get its hands on the target. – Bergi Jun 29 '18 at 08:02

12 Answers12

63

If you are using Vue 3 and dealing with Proxies, Vue provides some methods to help with this:

import { isProxy, toRaw } from 'vue';

Using these, you can check if an object is a proxy with isProxy, for example:

isProxy(reactiveObjectOrArray) ? 'yup' : 'nope'

And you can extract the raw data using toRaw:

const rawObjectOrArray = toRaw(reactiveObjectOrArray)

More info on toRaw and isProxy

Caleb Waldner
  • 889
  • 8
  • 11
  • 2
    Bizarrely, `isProxy` and `toRaw` only seem to work with reactive objects created with the `reactive` function, not the more common `ref`. How on earth are you meant to get the original object for a `ref` reactive value? – Jez Nov 07 '22 at 12:04
  • Interesting, I never needed to convert a `ref` so I never noticed this, but upon testing myself I see that you're correct; a `ref` doesn't work with those two functions. If someone really needed to convert a `ref`, they could use `toReactive` from VueUse, which allowed me to convert my `ref` into a reactive object. This worked for me, but I believe it only works for objects; string, booleans, and the like might not work using this method. – Caleb Waldner Nov 08 '22 at 04:27
  • Did you try that with a template ref, which is what I was trying to figure out the underlying type of? `const playfieldRef = ref(null); const pref = playfieldRef; const prefValue = playfieldRef.value; const prefReactive = toReactive(pref); const prIsProxy = isProxy(prefReactive); /* false */ const prRaw = toRaw(prefReactive); /* Proxy {} */` – Jez Nov 08 '22 at 11:44
  • Try `const prefReactive = toReactive(pref.value);`. Does that work? – Caleb Waldner Nov 09 '22 at 02:48
  • Yeah, I tried that one too, it's weird, it gives me back a slightly different Proxy that shows me this in the console when evaluated: `Proxy {__v_skip: true, getStage: ƒ, getNode: ƒ}`. When I open the object it has `[[Handler]]: Object`, `[[Target]]: Proxy`, and `[[IsRevoked]]: false` on it, but not `getStage` or `getNode` functions. Still doesn't seem like an underlying object; unless I'm being dumb and the original library actually exposes a Proxy itself. Hmm. – Jez Nov 10 '22 at 11:01
  • For common ref one can use something like this: `export const deRef = (ref: Ref): T => { return (ref as any)._rawValue }` – Andrei O. Mar 10 '23 at 00:38
44

You can make a copy of the data returned by the proxy using Object.assign():

const target_copy = Object.assign({}, my_proxy);

This will work for all enumerable own properties existing on the proxy/target.

Rashad Saleh
  • 2,686
  • 1
  • 23
  • 28
  • My example scenario required handling a proxy returned as a promise reject from a web API call. The full line looked like this: `var m = Object.assign({}, rejected)[0].message;` . Thanks Rashad! – Shanerk Jan 23 '19 at 19:54
  • 5
    This doesn't answer the question. The data of `target_copy` will still be affected by proxy accessors; and you won't have the reference to the original object. – Leonardo Raele Oct 04 '21 at 06:12
  • 5
    This doesn't work if target is an array and you want to get the array back. – chetan Nov 17 '21 at 07:30
24

I find that (using Vue.js where sometimes Proxy objects are involved, e.g. when watching a component prop) I can obtain the target both if it is an Object and if it is an Array using JSON.stringify:

let myTarget = JSON.parse(JSON.stringify(myProxy))

This approach works also with Array targets, whereas Object.assign({}, myProxy) works only if the target is an Object.

But I am very new to JavaScript proxies and my knowledge is limited. I may not understand the limitations and caveats of this approach. Nevertheless, maybe it helps someone!

sgrubsmyon
  • 1,077
  • 14
  • 22
13

Like the other answers already said a proxy get trap can be an elegant solution.

const IDENTITY = Symbol('proxy_target_identity')
const handler = {
  get: (target, property, receiver) => {
    if (property === IDENTITY) {
      return target
    }
    return Reflect.get(target, property, receiver)
  }
}
function createProxy() {
    const myArray = [Math.random(), Math.random()];
    return new Proxy(myArray, handler);
}
const myProxy = createProxy();
const orignal_target = myProxy[IDENTITY]

This code sample should be quite robust as it:

  • avoids property name conflicts by using a Symbol
  • covers all get-scenarios by using Reflect.get instead of target[property]
  • WARNING: be careful about prototype-inheritance - in case your proxy ends up being used as a prototype the not_itself_a_proxy[IDENTITY] call will not return not_itself_a_proxy but the "identity" of the prototype!
Davidiusdadi
  • 511
  • 1
  • 7
  • 14
8

There is a clever way to do this - You can add a get trap to the proxy and have it return the target conditionally. Like so..

let resolveMode = false;  // Switch that controls if getter returns target or prop. 

function resolve(obj) {
    resolveMode = true;  // Turn on our switch
    let target = obj.anything;  // This gets the target not the prop!
    resolveMode = false;  // Turn off the switch for the getter to behave normally
    return target;  // Return what we got!
}

function createProxy() {
    const myArray = [Math.random(), Math.random()];
    return new Proxy(myArray, {
        get: function(target, prop) {
            if (resolveMode) return target;  // This is where the magic happens!
            else return target[prop];        // This is normal behavior..
        }
    });
}

const myProxy = createProxy();
let target = resolve(myProxy);

Remember that the more lines of code you add to traps, the slower the object's performance gets. Hope this helps.

4

The other answers gave some good solutions. Here is @Yuci's answer distilled down for classes, in which case, it's as simple as defining an instance variable of some special name. The Proxy get function returns it, and so does the underlying target.

class Foo {
    constructor() {
        this.__target__ = this;
        return new Proxy(this, {
            get: function (target, name) {
                if (name in target) return target[name];
                // your code here
            }
        });
    }
}

let foo = new Foo();
let target = foo.__target__;
console.log('proxied Foo', foo);
console.log('recovered target', target, target.__target__.__target__);
timkay
  • 656
  • 9
  • 15
3

How about adding the following get trap:

const handler = {
  get: (target, property, receiver) => {
    if (property === 'myTarget') {
      return target
    }
    return target[property]
  }
}

const myArray = [Math.random(), Math.random()];

function createProxy() {
//     const myArray = [Math.random(), Math.random()];
    return new Proxy(myArray, handler);
}

const myProxy = createProxy();

And you can get the target of the proxy by myProxy.myTarget:

console.log(myProxy.myTarget) // [0.22089416118932403, 0.08429264462405173]
console.log(myArray === myProxy.myTarget) // true
Yuci
  • 27,235
  • 10
  • 114
  • 113
3

I found a great way to get the original target by defining the getPrototypeOf handler:

function createProxy() {
  const myArray = [Math.random(), Math.random()];
  return new Proxy(myArray, {
    getPrototypeOf(target) {
      return target;
    }
  });
}

const myProxy = createProxy();
const target = Object.getPrototypeOf(myProxy); // will get the original array
Eduardo Rosostolato
  • 742
  • 1
  • 8
  • 21
2

You can if the target is an object.

You will have to create a function in target to retrieve it, that's all.

Example:

class AnyClass {
   constructor() {
      this.target = this;

      return new Proxy(this, this);
   }

   get(obj, prop) {
      if (prop in obj)
          return this[prop];

      // your stuff here
   }

   getTarget() {
      return this.target;
   }
}

And then when you call:

let sample = new AnyClass;
console.log(sample.getTarget());

Will return you the target as you expect :)

ragnar
  • 1,252
  • 11
  • 18
-1

Use a private Symbol to trap a property. Then give a public function to test if the object returns true for this Symbol.

var sym_Test = Symbol('test')
let proxyHandler = {        
    get: function(pValues:any, prop:any, receiver:any){
        if(prop == sym_Test)
            return true;
        
        return getValue(pValues, prop)
    },    
}

export function IsMyProxy(some:any){
    return some[sym_Test]
}
tdy
  • 36,675
  • 19
  • 86
  • 83
-1

I found Array.from seems work.

function createProxy() {
    const myArray = [Math.random(), Math.random()];
    return new Proxy(myArray, {});
}

const myProxy = createProxy();
const l=Array.from(myProxy)
console.assert(l.every(it=>!isNaN(it)));
Youth overturn
  • 341
  • 5
  • 7
-2

As the proxy contain an object you can also do

Object.keys( my_proxy )

And then it become easy to retrieve thing such as Object.keys( my_proxy ).length

Atnaize
  • 1,766
  • 5
  • 25
  • 54