How do I undo a Object.defineProperty call?
Asked Answered
U

2

53

Fiddle

var Assertion = function() {
    return { "dummy": "data" };    
}

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(this);
  }
});

// Insert magic here.

// This needs to be false
console.log(({}).should === undefined);

What options do I have in ES5 to undo a defineProperty call ?

No silly suggestions like Object.defineProperty = function() { } please.

The following Object.defineProperty(Object.prototype, 'should', {})

does not work

and Object.defineProperty(Object.prototype, 'should', { value: undefined })

Throws a Uncaught TypeError: Cannot redefine property: defineProperty in V8

Object.defineProperty(Object.prototype, 'should', { 
    set: function() {},
    get: function() { return undefined; }
});

Throws the same error

delete Object.prototype.should also does not work

Undersexed answered 21/8, 2011 at 21:11 Comment(0)
A
55

In general, you can't undo a defineProperty call, since there's no undo stack or something. The JS engine does not keep track of previous attribute descriptors.

For example,

Object.defineProperty(Object.prototype, 'foo', {
    configurable: true,
    value: 1,
    enumerable: false
});
Object.defineProperty(Object.prototype, 'foo', {
    get: function () {
        alert('You cannot revert me');
        return 2;
    },
    enumerable: true
});

What you can do is remove or reconfigure an attribute, or overwrite its value. As mentioned in the other answer, the configurable flag is required to be true if you want to remove or reconfigure. Once a property is defined with configurable:false, you cannot change the configurable flag.


To remove an attribute (this is supposedly what you want to do), use delete:

Object.defineProperty(Object.prototype, 'foo', {
    configurable: true, // defaults to false
    writable: false,
    value: 1
});
delete Object.prototype.foo;
console.log(Object.prototype.hasOwnProperty('foo')); // false

To reconfigure, use defineProperty again and pass a different descriptor:

Object.defineProperty(Object.prototype, 'foo', {
    configurable: true,
    get: ...
    set: ...
});
Object.defineProperty(Object.prototype, 'foo', {
    value: undefined
});
console.log({}.foo); // undefined
console.log(Object.prototype.hasOwnProperty('foo')); // true

As shown in this sample, you can use defineProperty to switch between accessor (get/set) and data (value) properties.


To overwrite, use simple assignment. In this case, you need the writable flag to be true. Obviously this does not work with accessor properties. It even throws an exception:

Object.defineProperty(Object.prototype, 'foo', {
    configurable: true,
    value: 1,
    writable: true // defaults to false
});
Object.prototype.foo = undefined;
console.log(Object.prototype.foo); // undefined
console.log(Object.prototype.hasOwnProperty('foo')); // true

Object.defineProperty(Object.prototype, 'foo', {
    get: function () {
        return 1;
    },
    writable: true // JS error!
});

Note that writable defaults to false when you use defineProperty, but true when you use the simple syntax o.attr = val; to define a (previously not existing) property.

Aiello answered 22/8, 2011 at 7:20 Comment(2)
can anybody explain why can't we override or remove an accessor by assigning a new value to the property like this obj.foo = 'new value';? in other word why we have to either delete the property first or reconfigure it to get rid of that accessorMathamathe
@Mathamathe Because JavaScript accessor properties do not work in a way that allows reassignment of the property itself: when you attempt to assign a value to an accessor, a correctly programmed JavaScript engine will "trap" the call and pass the assigned value to the accessor's setter function if one has been defined, else it assume the property is not supposed to be written to at all and will throw an error, as according to the ECMAScript specification. Defining a property as an accessor at all will remove the default [[Get]] and [[Set]] behaviour in favour of user-provided behaviour.Drucill
D
0

If you want to undo your last defineProperty or all of them, you can use this class:

(gist here)

class PropertyDescriptorStack {
    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}: ${e}`);
            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}: ${e}`);
            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;

    }
}
Dovetail answered 13/2, 2023 at 0:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.