Last active
October 24, 2022 09:07
-
-
Save mmichlin66/0763afcc8de7bbe4115266611793c3d0 to your computer and use it in GitHub Desktop.
Virtualizing TypeScript Properties with Proxy and Decorator - listing 6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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