Object.defineProperty() inside the getter - javascript

I've a scenario where I need to modify a property of a class using Object.defineProperty(). I know it can be done through the get() of the descriptor.
If you see the example below, I'm trying to return a function which when called will return a value from this.
My question is, do I need to define that property again for the this object? Is it necessary? If yes, why?
When I define a property to a class, this will also reflect the same right?
I came through this and I'm curious to know why he is doing this.
class A {
constructor() {
this.h = 'hello world'
}
hello() {}
}
const des = Object.getOwnPropertyDescriptor(A.prototype, 'hello')
Object.defineProperty(A.prototype, 'hello', {
configurable: true,
get: function(){
//is this necessary
Object.defineProperty(this, 'hello', {
configurable: true,
value: () => this.h,
})
//
return () => this.h
}
})
const n = new A()
console.log(n.hello())

I have no idea why that person is doing this. I probably wouldn't do it. But if your question is whether there is a difference in doing it or not, the answer is yes.
class A {
constructor() {
this.h = 'hello world'
}
hello() {}
}
const des = Object.getOwnPropertyDescriptor(A.prototype, 'hello')
Object.defineProperty(A.prototype, 'hello1', {
configurable: true,
get: function() {
Object.defineProperty(this, 'hello1', {
configurable: true,
value: () => this.h,
});
return () => this.h;
}
});
Object.defineProperty(A.prototype, 'hello2', {
configurable: true,
get: function() {
return () => this.h;
}
});
const n = new A()
console.log(n.hello1); // () => this.h OK
console.log(n.hello2); // () => this.h OK
Object.setPrototypeOf(n, null);
console.log(n.hello1); // () => this.h Still OK
console.log(n.hello2); // undefined Oops!
It's also possible that calling a getter may be more expensive than reading a data property.

Related

creating default parameters when making a object using object.create

Is there a way to optionally declare dots: {} from inside Object.create?
As you can see I have tried
standard default parameter assignment
tried using a nullish coalescing
Neither work.
Any guidance would be appreciated.
'use strict'
let Carousel = function(name, dots=true) {
this.name = name
this.dots = dots ?? true
}
Carousel.prototype.show = function() {
console.log(`${this.name} and ${this.dots}`)
}
window.addEventListener('DOMContentLoaded', () => {
let usps = Object.create(Carousel.prototype, {
name: { value: 'element name', writeable: false}
// dots: { value: true, writeable: false }
})
usps.show()
})
Not sure why you don't use new keyword and instead create object from prototype, but in either case, if you set default value to prototype of your object, you will get dots on it.
let Carousel = function(name, dots) {
this.name = name
this.dots = dots
}
Carousel.prototype.show = function() {
console.log(`${this.name} and ${this.dots}`)
}
Carousel.prototype.dots = true;
window.addEventListener('DOMContentLoaded', () => {
let usps = Object.create(Carousel.prototype, {
name: { value: 'element name', writeable: false},
})
usps.show() // will show that dots equals true
let usps2 = Object.create(Carousel.prototype, {
name: { value: 'element name', writeable: false},
dots: { value: false, writeable: false }
});
usps2.show(); // will show that dots equals false
})
It is important, that if you will want to use new operator, you will have to modify your constructor, so if there is no dots parameter set, you will not override one with undefined:
let Carousel = function (name, dots) {
this.name = name;
if (dots !== undefined) {
this.dots = dots;
}
};
Carousel.prototype.show = function () {
console.log(`${this.name} and ${this.dots}`);
};
Carousel.prototype.dots = true;
window.addEventListener("DOMContentLoaded", () => {
let usps = new Carousel("element name");
usps.show();
let usps2 = new Carousel("element name 2", false);
usps2.show();
});

Applying proxy to an object

Hi, I am trying to transform an object into a proxy, without changing the reference to the object:
Here is a simple class:
class Foo {
constructor () {
this.a = 'bar'
}
}
const foo = new Foo()
Here is my Proxy:
const proxy = new Proxy(foo, {
get () {
return 'proxy-bar'
},
set () {
// Don't do anything
return true
}
})
Here is what a normal use of this proxy does:
console.log(foo.a) // bar
console.log(proxy.a) // proxy-bar
Now, I want to keep using foo, but with the capabilities of proxy. So I tried to use Object.assign which seems to work only for the getter, but not for the setter. Is their something I miss?
Object.assign(foo, proxy)
console.log(foo.a) // Getter works: proxy-bar
foo.a = 'proxy-proxy-bar'
console.log(foo.a) // Setter does not work: proxy-proxy-bar
You can apply the proxy in your constructor and return it :
class Foo {
constructor() {
this.a = 'bar';
return new Proxy(this, {
get() {
return 'proxy-bar'
},
set() {
// Don't do anything
return true
}
});
}
}
const foo = new Foo();
foo.a = 'proxy-proxy-bar';
console.log(foo.a); // proxy-bar

How do I use JavaScript decorators on a class instance via Babel?

Given the following .babelrc config:
{
"plugins": [
["#babel/plugin-proposal-decorators", {
"legacy": false,
"decoratorsBeforeExport": false
}]
]
}
I can't get the class decorators to work:
#annotation
class MyClass { }
function annotation(target) {
target.annotated = true;
}
const c = new MyClass();
console.log(c);
Also, console.log(target) yields the following:
Object [Descriptor] { kind: 'class', elements: [] }
For the console.log(c) statement I'd expect to see the annotated property added, however what I get is just MyClass {}.
Some additional clarification - I know of the legacy: true flag but I wish to use the spec as it is now, without the legacy fallback. I did some additional research and I think I'm on the right path here, here's the updated code:
#annotation
class MyClass { }
function annotation(descriptor) {
const {
kind,
elements
} = descriptor;
const newElements = elements.concat([{
kind: 'field',
placement: 'own',
descriptor: {
annotated: true
}
}]);
return {
kind,
elements: newElements
}
}
const c = new MyClass();
console.log(c);
The above still doesn't work but at least I am no longer getting weird errors :)
Please read the comments on the accepted answer to see some potential solutions to this, if interested.
** UPDATE **
I actually managed to figure it out - using the legacy: false option:
#annotation
class MyClass { }
function annotation(descriptor) {
const {
kind,
elements
} = descriptor;
const newElements = elements.concat([{
kind: 'field',
placement: 'own',
key: 'annotated',
initializer: () => true,
descriptor: {
configurable: true,
writable: true,
enumerable: true
}
}]);
return {
kind,
elements: newElements
}
}
const c = new MyClass();
console.log(c); // MyClass { annotated: true }
Return a new class that sets the property in the constructor.
#annotation
class MyClass { }
function annotation(target) {
return class extends target {
constructor(...args) {
super(...args);
this.annotated = true;
}
}
}
const c = new MyClass();
console.log(c);

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

How do I undo a Object.defineProperty call?

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

Categories