Как отменить вызов 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;
}
}