-
-
Save fostyfost/970298aec2792dd4cd589b407d388189 to your computer and use it in GitHub Desktop.
Change property descriptors on an object with an undo stack
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
interface IPropertyDescriptorStack { | |
/** | |
* set the property descriptor of the member prop on the target object | |
* Fails silently, returning false | |
* @param props | |
*/ | |
push(props: Partial<PropertyDescriptor>): boolean; | |
/** | |
* reset the property descriptor of the member prop on the target object, either to | |
* the state it was before the last set, or to the initial state, resetting the state stack. | |
* Does nothing if the stack is empty | |
* Fails silently, returning false | |
* @param toStart | |
*/ | |
pop(toStart?: boolean): boolean; | |
} | |
class PropertyDescriptorStack implements IPropertyDescriptorStack { | |
private readonly descriptors: PropertyDescriptor[] = []; | |
constructor(private readonly target: Object, private readonly prop: string) { | |
if (!target || typeof prop !== "string") { // your choice to define "" | |
throw new Error("PropertySaver: no object or property"); | |
} | |
} | |
public push(props: Partial<PropertyDescriptor>): boolean { | |
this.saveDescriptor(this.target, this.prop); | |
try { | |
Object.defineProperty(this.target, this.prop, { | |
...props, | |
configurable: true, | |
}); | |
return true; | |
} | |
catch (e) { | |
console.error(`Error setting property ${this.prop} on ${this.target}`); | |
return false; | |
} | |
} | |
public pop(toStart?: boolean): boolean { | |
const ind = toStart ? 0 : this.descriptors.length - 1; | |
const descriptor = this.descriptors[ind]; | |
if (!descriptor) { | |
return false; | |
} | |
this.descriptors.splice(ind, this.descriptors.length - ind); | |
try { | |
Object.defineProperty(this.target, this.prop, descriptor); | |
return true; | |
} | |
catch (e) { | |
console.error(`Error resetting property ${this.prop} on ${this.target}`); | |
return false; | |
} | |
} | |
/** | |
* Saves the current descriptor of the property in the object in the descriptors stack. | |
* The descriptor is taken either from the object or from the closest prototype that has this prop. | |
* If none is found, a new descriptor is generated with the current value. | |
* @param target | |
* @param prop | |
* @returns The found descriptor | |
*/ | |
private saveDescriptor(target: object, prop: string): PropertyDescriptor { | |
let ds: PropertyDescriptor | null = null; | |
for (let o: any = target, ds: PropertyDescriptor = null; o; o = Object.getPrototypeOf(o)) { | |
ds = Object.getOwnPropertyDescriptor(o, prop); | |
if (ds) { | |
break; | |
} | |
} | |
ds = ds || { | |
configurable: true, | |
writable: true, | |
value: target[prop], | |
enumerable: true | |
} | |
this.descriptors.push(ds); | |
return ds; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://stackoverflow.com/questions/7141210/how-do-i-undo-a-object-defineproperty-call