How do I undo a Object.defineProperty call? - javascript

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

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.

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

Related

Local function declaration versus pointer to function

class Testone{
constructor(value){
Object.defineProperty(this, 'doSomething', {
enumerable: false,
configurable: false,
writable: false,
value: () => {
//do whatever
}
});
}
}
const doWhatever = () => {
//do whatever
}
class Testtwo{
constructor(value){
Object.defineProperty(this, 'doSomething', {
enumerable: false,
configurable: false,
writable: false,
value: doWhatever
});
}
}
My thought is that Testtwo should be more efficient, since doWhatever would only exist in memory once then get pointed to by every instance of Testtwo. Testone would instantiate a new anonymous function for each new instance.
Does Testone actually create a new anonymous function each time? Is Testtwo therefor more efficient or is it also creating separate instances of doWhatever?
Am I missing some major caveat to one over the other (other than variable scope)?
My benchmarking consistently shows ~2% difference in instantiation time, with Testtwo indeed being faster... but that's not exactly confirmation.
Yes. Testone create new doSomething function every time, but Testtwo uses existing one.
Proof:
class Testone{
constructor(value){
Object.defineProperty(this, 'doSomething', {
enumerable: false,
configurable: false,
writable: false,
value: () => {
//do whatever
}
});
}
}
const doWhatever = () => {
//do whatever
}
class Testtwo{
constructor(value){
Object.defineProperty(this, 'doSomething', {
enumerable: false,
configurable: false,
writable: false,
value: doWhatever
});
}
}
const f1 = new Testone()
const f2 = new Testone()
console.log('Testone', f1.doSomething === f2.doSomething)
const s1 = new Testtwo()
const s2 = new Testtwo()
console.log('Testtwo', s1.doSomething === s2.doSomething)

Reflect.get() using a proxy = error illegal invocation

I'm trying to log items inserted into localStorage via the dot accessor using Chromium.
But i'm getting this error when using Reflect.get() method.
Uncaught TypeError: Illegal invocation
here's my script
const localStorageProxy = new Proxy(localStorage, {
get(target, name, receiver) {
// error here
const value = Reflect.get(target, name, receiver);
if (!['setItem', 'getItem', 'removeItem'].includes(name)) {
console.log(value);
console.trace();
}
return typeof value === 'function' ?
value.bind(target) :
value;
},
set(target, name, value, receiver) {
if (!['setItem', 'getItem', 'removeItem'].includes(name)) {
console.log(value);
console.trace();
}
if (name === 'setItem') {
return false;
}
// probably an error here too
return Reflect.set(target, name, value, receiver);
},
});
Object.defineProperty(window, 'localStorage', {
value: localStorageProxy,
configurable: true,
enumerable: true,
writable: false,
});
Expected result: window.localStorage.myItem = 'some-value' should log the value and a stack trace
This script is used with Tampermonkey on Chromium v74 (should be fine, supported by Chrome 49 and above).
How can I resolve this ?

Assigning object properties (in a loop) and including a setter results in undefined values

This is my first attempt at using Javascript's Object.defineProperty and/or defineProperties and apparently I'm doing it completely incorrectly. I am passing a cfg object into a Javascript function constructor, then inside that constructor am looping over the config and
Calling Object.defineProperty on each cfg key
Assigning this[key] = cfg[key]
However every this[key] emerges with undefined as it's value. I've tried a few different things, such as setting the writable attribute to true, but got an error that this conflicts with the set/function. I've also tried using Object.defineProperties with the same result. I don't think it's a scoping issue with this and have even used .bind(this) on the setters to be sure, but to no avail. The code is can be run at https://repl.it/#dexygen/js-define-properties and I am including it below inline. Expected result of course rather than undefined is that this.foo e.g. will contain the string 'foo' from cfg.foo passed into the object constructor function
let myObj = new ObjInstance({
foo: 'foo',
bar: 'bar',
baz: 'baz'
});
function ObjInstance(cfg) {
for (let key in cfg) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
// writable: true, // throws error in conjunction with set function
set: (function(val) {
console.log('this[' + key + ']: ' + this[key]); //undefined :(
}).bind(this)
});
console.log('cfg[key]: ' + cfg[key]);
this[key] = cfg[key];
}
console.log(this);
console.log('this.foo: ' + this.foo);
/* // if you comment this in, comment out the above for loop
let objProperties = {}
for (let key in cfg) {
objProperties[key] = {
configurable: true,
enumerable: true,
set: (function(val) {
console.log('this[' + key + ']: ' + this[key]);
}).bind(this)
}
}
for (let key in cfg) {
console.log('cfg[key]: ' + cfg[key]);
this[key] = cfg[key];
}
Object.defineProperties(this, objProperties);
for (let key in cfg) {
console.log('cfg[key]: ' + cfg[key]);
this[key] = cfg[key];
}
*/
}
When you use setter or getter, then it cannot have the same property name as the returned property. It seems that you try to do something like this:
var obj = {
set a(param){
console.log(this.a); //setter try to set a setter
},
get a(){
return this.a; //RangeError: Maximum call stack
}
};
The property that is set or got by setter or getter must have different property name, eg _foo:
let myObj = new ObjInstance({
foo: 'foo',
bar: 'bar',
baz: 'baz'
});
console.log(myObj);
function ObjInstance(cfg) {
for (let key in cfg) {
Object.defineProperty(this,key, {
configurable: true,
enumerable: true,
set:(val)=>{
this[`_${key}`] = key;
},
get:()=>{
return this[`_${key}`];
}
});
console.log('cfg[key]: ' + cfg[key]);
this[key] = cfg[key];
}
console.log(this);
console.log('this.foo: ' + this.foo);
}
or you can just store the values in the getters like this:
let myObj = new ObjInstance({
foo: 'foo',
bar: 'bar',
baz: 'baz'
});
console.log(myObj);
function ObjInstance(cfg) {
for (let key in cfg) {
Object.defineProperty(this,key, {
configurable: true,
enumerable: true,
get:()=>{
return key;
}
});
console.log('cfg[key]: ' + cfg[key]);
this[key] = cfg[key];
}
console.log(this);
console.log('this.foo: ' + this.foo);
}
I want to thank the first person to comment but he's declined to answer so I am posting my own based not only on his comment (the fact that I had not providde a getter more or less being the reason I was getting back undefined from my setter), but also addressing issues brought up by some of the other answers, one being the questioning of my use case, which I address with a comment in the setter.
Working code here: https://repl.it/#dexygen/javascript-defineProperty-working
let myObj = new ObjInstance({
foo: 'foo',
bar: 'bar',
baz: 'baz'
});
function ObjInstance(cfg) {
for (let key in cfg) {
this[key] = cfg[key];
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
set: function(val) {
cfg[key] = val;
// additionally plan to emit an event
},
get: function() {
return cfg[key];
}
});
}
console.log('this.foo: ' + this.foo);
this.foo = 'foobarbaz';
console.log('this.foo: ' + this.foo);
}
Here is an alternative JavaScript backward compatible equivalent
function Handle( O ) {
var arr = [];
for( var p in O ) {
arr.push(
["get "+p+"(){ return O['"+p+"']; }, set "+p+"(x){ O['"+p+"'] = x; /*your emitter*/ }" ]
);
}
return Function("O", "return {"+arr.join()+"}" )(O);
}
Why is it that a nice and clean conventional JavaScript doesn't work for you?
var myObj = new ObjInstance
(
{
foo: 'foo',
bar: 'bar',
baz: 'baz'
}
);
function ObjInstance( cfg ) {
for ( var key in cfg ) {
this[ key ] = cfg[ key ];
}
};
console.log( JSON.stringify( myObj ) );
myObj.bar = 'bar2';
console.log( JSON.stringify( myObj ) );

JavaScript Watch removing watched value from object

i'm trying to watch an objects value, and if it changes, so some stuff..
So this is my object,
var list = [{
object: undefined,
index: 0,
src: 'url here',
active: { val: 0 }
}]
So I made active an additional object as I create a new object from the above, but make the active value the value from above, this keeps a reference of this object between the 2 objects.
var newList = [];
newList.push({
object: undefined,
index: 0,
src: list[i].src,
active: list[i].active // Use reference to old list
});
So i'm trying to watch the active value like so:
(list.active).watch('val', function() {
if (list.active.val === 1) {
console.log('active');
list.object.classList.add('active');
} else {
console.log('not active');
list.object.classList.remove('active');
}
});
However it appears that when I watch this value it is being removed, as if I console.log out the list, then the value is set to undefined! I'm changing the value of list.active.val after adding the watch events.
Here is the Polyfill i'm using for the watch functionality.
// object.watch
if (!Object.prototype.watch) {
Object.defineProperty(Object.prototype, "watch", {
enumerable: false,
configurable: true,
writable: false,
value: function (prop, handler) {
var oldval = this[prop],
newval = oldval,
getter = function () {
return newval;
},
setter = function (val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
};
if (delete this[prop]) { // can't watch constants
Object.defineProperty(this, prop, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
});
}
// object.unwatch
if (!Object.prototype.unwatch) {
Object.defineProperty(Object.prototype, "unwatch", {
enumerable: false,
configurable: true,
writable: false,
value: function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
}
});
}
Edit 1
Added the proxy-observe Polyfill however this doesn't appear to be watching still, I have added it like so:
list[0] = Object.observe(list[0], function(changeset) {
console.log('changed');
});
list[0].active contains the object { val: 0 }, so it should be observicing this object.
Not getting any errors, it's just doing nothing, ideas?
You don't need to implement a custom watch/unwatch features,
Ecmascript 2015 already provides a specific api:
Proxy
There are a plenty of polyfills to make it working on legacy browsers.
There was a proposal called Object.Observe to address what you need and you can find a Proxy porting here: https://github.com/anywhichway/proxy-observe
Follows a basic working example:
// Your Observable target
const target = Object.create(null);
const observer = {
set(target, key, value) {
console.log(`Should we set '${value}' as value of '${key}' property?`)
target[key] = value;
},
};
const observable = new Proxy(target, observer);
observable.someKindOfProperty = 'Hello World';

TypeScript class with property decorator acts as if static

I have written a class with a property decorator that sets a flag in the class when ever a decorated property is set. I also want to be able to copy from one instance of the class to another. The problem is that when I set the value of property on one object, the value of the property on another object changes too, as if the property were static. I am new to JavaScript and TypeScript. What did I miss?
Running the text code below will log:
Setting propNum from undefined to 0
testclass.ts:18 Setting propNum from 0 to 123
test.spec.ts:13 t1.propNum = 123
test.spec.ts:14 t2.propNum = 123
t1.propNum should still be zero
Decorator
//
// property decorator to set dirty flag automatically for any decorated property
//
function testProperty( target: any, key: string ) {
// property value
var _val = this[key];
// property getter
function getter() {
return _val;
};
// property setter
function setter( newVal ) {
if ( _val != newVal ) {
console.log( `Setting ${key} from ${_val} to ${newVal}` );
_val = newVal;
this._dirty = true;
}
};
//
// Delete original property and define new property with getter & setter
//
if ( delete this[key] ) {
// Create new property with getter and setter
Object.defineProperty( target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
Test Class
export class TestClass {
private _dirty: boolean;
#testProperty
public propNum: number = 0;
constructor() {
this._dirty = false;
}
public copyFrom( tc: TestClass ) {
this.propNum = tc.propNum;
}
}
Test Code
describe( 'Copy Class Test', () => {
it( 'Copy Test', () => {
var t1 = new TestClass();
var t2 = new TestClass();
t2.propNum = 123;
console.log( `t1.propNum = ${t1.propNum}` );
console.log( `t2.propNum = ${t2.propNum}` );
expect( t1.propNum ).toBe( 0 );
t1.copyFrom( t2 );
expect( t1.propNum ).toBe( 123 );
});
});
The main issue here is that the getter and setter are sharing the same variable instead of getting a value based on the instance.
It's basically the same as doing this:
function TestClass() {
}
var value;
Object.defineProperty(TestClass.prototype, "propNum", {
get: function() { return value; },
set: function(val) { value = val },
enumerable: true,
configurable: true
});
Which causes this to happen:
var a = new TestClass(), b = new TestClass();
a.propNum = 2;
a.propNum === b.propNum; // true, because they're both referencing the same variable
Second issue is that this[key] references a property on the global object.
What you probably want to do is something along these lines (untested code):
function testProperty( target: Object, key: string ) {
const privateKey = "_" + key;
function getter() {
return this[privateKey];
}
function setter( newVal: any ) {
if ( this[privateKey] != newVal ) {
console.log( `Setting ${key} from ${this[privateKey]} to ${newVal}` );
this[privateKey] = newVal;
this._dirty = true;
}
}
Object.defineProperty( target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}

Categories