Как отменить вызов Object.defineProperty?

скрипка

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);

Какие варианты у меня есть в ES5, чтобы отменить defineProperty вызов?

Никаких глупых предложений вроде Object.defineProperty = function() { } Пожалуйста.

Следующие Object.defineProperty(Object.prototype, 'should', {})

не работает

а также Object.defineProperty(Object.prototype, 'should', { value: undefined })

Бросает Uncaught TypeError: Cannot redefine property: defineProperty в V8

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

Выдает ту же ошибку

delete Object.prototype.should тоже не работает

2 ответа

Решение

В общем, вы не можете отменить defineProperty вызов, так как нет стека отмены или что-то. Механизм JS не отслеживает предыдущие дескрипторы атрибутов.

Например,

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
});

Что вы можете сделать, это удалить или перенастроить атрибут или перезаписать его значение. Как упоминалось в другом ответе, configurable флаг должен быть true если вы хотите удалить или перенастроить. Как только свойство определено с configurable:falseВы не можете изменить configurable флаг.


Чтобы удалить атрибут (это, вероятно, то, что вы хотите сделать), используйте 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

Для перенастройки используйте defineProperty еще раз и передайте другой дескриптор:

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

Как показано в этом примере, вы можете использовать defineProperty переключаться между аксессором (get/set) и данные (value) свойства.


Чтобы перезаписать, используйте простое назначение. В этом случае вам нужно writable флаг быть true, Очевидно, что это не работает со свойствами доступа. Это даже бросает исключение:

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!
});

Обратите внимание, что writable по умолчанию false когда вы используете defineProperty, но true когда вы используете простой синтаксис o.attr = val; определить (ранее не существующее) свойство.

Если вы хотите отменить свой последнийdefinePropertyили все из них, вы можете использовать этот класс:

(суть здесь )

      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;

    }
}
Другие вопросы по тегам