Skip to content

Instantly share code, notes, and snippets.

@mmichlin66
Last active October 24, 2022 09:07
Show Gist options
  • Save mmichlin66/0763afcc8de7bbe4115266611793c3d0 to your computer and use it in GitHub Desktop.
Save mmichlin66/0763afcc8de7bbe4115266611793c3d0 to your computer and use it in GitHub Desktop.
Virtualizing TypeScript Properties with Proxy and Decorator - listing 6
// @virtual decorator function
function virtual( target: any, name: string)
{
// symbol to keep the proxy handler value
let sym = Symbol( name + "_proxy_handler");
Object.defineProperty( target, name, {
enumerable: true,
get()
{
// check whether we already have the handler and create it if we don't. In this
// case we also create a proxy for an empty object
let handler = this[sym] as VirtHandler;
if (!handler)
{
this[sym] = handler = new VirtHandler();
handler.proxy = new Proxy( {}, handler);
}
return handler.proxy;
},
set(v)
{
let newTarget: any;
let isPrimitive: boolean;
if (v == null)
{
newTarget = v;
isPrimitive = false;
}
else
{
// if v is of a primitive type, box it because proxies don't work with primitive types
isPrimitive = true;
if (typeof v === "string")
newTarget = new String(v);
else if (typeof v === "number")
newTarget = new Number(v);
else if (typeof v === "boolean")
newTarget = new Boolean(v);
else
isPrimitive = false;
}
// check whether we already have the handler and created it if we don't
let handler = this[sym] as VirtHandler;
if (!handler)
{
this[sym] = handler = new VirtHandler();
handler.proxy = newTarget == null ? {} : new Proxy( newTarget, handler);
}
// set the new vaule to the handler so that it will use it from now on
handler.target = newTarget;
handler.isPrimitive = isPrimitive;
}
});
}
// Handler for the proxies created by the `@virtual` decorator
class VirtHandler
{
public proxy: any;
public target: any;
public isPrimitive: boolean;
// interesting things happen in the get method
get( t: any, p: PropertyKey, r: any): any
{
// if our value is null or undefined and the requested property is a well-known symbol
// toPrimitive we return a function that returns either null or undefined. This will help
// if our proxy either participate in an arithmetic expression or or is combined with a
// string.
if (this.target == null && p === Symbol.toPrimitive)
return () => this.target;
// get the value of the request property; if the value is null or undefined, an exception
// will be thrown - which is expected.
let pv = Reflect.get( this.target, p, r);
// if the requested property is a function of a boxed primitive, bind the original method
// to the target object
return this.isPrimitive && typeof pv === "function" ? pv.bind( this.target) : pv;
}
// the rest of the methods mostly delegate the calls to the latest target instead of the
// original target. In some cases, we check whether the target is null or undefined so that
// we don'tthrow exceptions wher we can avoid it.
getPrototypeOf( t: any): object | null
{ return this.target == null ? null : Reflect.getPrototypeOf( this.target); }
setPrototypeOf(t: any, v: any): boolean
{ return Reflect.setPrototypeOf( this.target, v); }
isExtensible(t: any): boolean
{ return this.target == null ? false : Reflect.isExtensible( this.target); }
preventExtensions(t: any): boolean
{ return this.target == null ? false : Reflect.preventExtensions( this.target); }
getOwnPropertyDescriptor(t: any, p: PropertyKey): PropertyDescriptor | undefined
{ return Reflect.getOwnPropertyDescriptor( this.target, p); }
has(t: any, p: PropertyKey): boolean
{ return this.target == null ? false : Reflect.has( this.target, p); }
set( t: any, p: PropertyKey, v: any, r: any): boolean
{ return Reflect.set( this.target, p, v, r); }
deleteProperty(t: any, p: PropertyKey): boolean
{ return Reflect.deleteProperty( this.target, p); }
defineProperty(t: any, p: PropertyKey, attrs: PropertyDescriptor): boolean
{ return Reflect.defineProperty( this.target, p, attrs); }
enumerate(t: any): PropertyKey[]
{ return Array.from( Reflect.enumerate( this.target)); }
ownKeys(t: any): PropertyKey[]
{ return Reflect.ownKeys( this.target); }
apply(t: any, thisArg: any, args?: any): any
{ return this.target.apply( this, args); }
construct(t: any, args: any, newTarget?: any): object
{ return Reflect.construct( this.target, args, newTarget); }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment