I want to know how do keep initialed values of object properties
class MyClass{
constructor(){
this.settings = {
theme: 'dark',
confirmBtn: 'ok',
cancelBtn: 'cancel',
}
}
}
let obj= new MyClass();
obj.settings = {
theme: 'white'
}
and other properties like confirmBtn keep their values that assigned in the class declaration.
You have a couple of options.
Only assign to the one property:
obj.settings.theme = "white";
Use Object.assign if you have an object of properties you want to assign (here I have just the one property, but you could have more):
Object.assign(obj.settings, {
theme: "white",
});
Object.assign copies all of the own, enumerable properties from the objects provided as the second argument onward into the object provided as the first argument.
Use object spread syntax ... on both the existing obj.settings object and the new settings object into an anonymous, new object and assign that to obj.settings:
obj.settings = { ...obj.settings, ...{ theme: "white" } };
Though there's no reason to do that here, just update the settings object you have. This option can be very useful in situations where you want to avoid ever modifying the existing object (state updates in various MVC libs, etc.).
In a self-answer it appears you wanted to move the assignment into the constructor, which you didn't mention in the question. I'd handle that by using parameter destructuring combined with destructuring default values and a parameter default (just a blank object):
class MyClass {
constructor({theme = "dark", confirmBtn = "ok", cancelBtn = "cancel"} = {}) {
this.settings = {
theme,
confirmBtn,
cancelBtn,
};
}
}
let obj= new MyClass({
theme: "white",
});
Live Example:
class MyClass {
constructor({theme = "dark", confirmBtn = "ok", cancelBtn = "cancel"} = {}) {
this.settings = {
theme,
confirmBtn,
cancelBtn,
};
}
}
// Settings not specified get defaulted
const obj1 = new MyClass({
theme: "white",
});
console.log(obj1.settings);
// No parameter at all gets all defaults
const obj2 = new MyClass();
console.log(obj2.settings);
You can also do it with Object.assign, though I like the expressiveness of the defaults all being in the constructor signature:
class MyClass {
constructor(settings = {}) {
this.settings = Object.assign(
{},
{
theme: "dark",
confirmBtn: "ok",
cancelBtn: "cancel",
},
settings
);
}
}
let obj= new MyClass({
theme: "white",
});
Live Example:
class MyClass {
constructor(settings = {}) {
this.settings = Object.assign(
{},
{
theme: "dark",
confirmBtn: "ok",
cancelBtn: "cancel",
},
settings
);
}
}
// Settings not specified get defaulted
const obj1 = new MyClass({
theme: "white",
});
console.log(obj1.settings);
// No parameter at all gets all defaults
const obj2 = new MyClass();
console.log(obj2.settings);
You could use the private class feature and use getters and setters.
class MyClass{
#settings;
constractor(){
this.#settings={
theme:'dark',
confirmBtn:'ok',
cancelBtn:'cancel'
}
}
getSettings(){
return this.#settings;
}
}
trying to access #setting swill yield an error
I have solved my issue by JQuery $.extend:
I defined a deafult object and get options as another object and merged them.
class MyClass{
constructor(options){
this.default = {
theme: 'dark',
confirmBtn: 'ok',
cancelBtn: 'cancel',
}
this.settings=$.extend({},this.default,options);
}
}
let obj= new MyClass({
theme:'dark
});
Related
I want to create a class that takes an options object as it's argument in its constructor. Each of the member of option object should become member of the class instance.
const __defaults = {
isDisabled = false,
caption: undefined
}
class Button {
constructor(options) {
this.caption = options.caption || __defaults.caption;
this.isDisabled = options.isDisabled || __defaults.disabled;
}
}
Is there a better way to handle this like spread operator?
this.config = { ...options, ...__defaultOptions };
The only problem is I can't assign directly to this using spread operator. That would be an invalid assignment.
this = { ...options, ...__defaultOptions };
I want to create all properties directly inside class and not within a config property in the instance. That way when I initialize my button in following manner...
const registerButton = new Button({ isDisabled: true });
I can read property just like this:
console.log(registerButton.isDisabled);
and not like this...
console.log(registerButton.config.isDisabled);
Former approach is more verbose and readable.
Use Object.assign()
class Foo{
constructor(options)
{
Object.assign(this,options);
}
}
let foo = new Foo({a:1,b:2,c:3});
console.log(foo);
Object.assign can assign the merged properties onto the instance.
constructor(options) {
Object.assign(
this,
__defaultOptions,
options
);
}
You can create a temp object i.e obj and then loop over the object keys and then add property on this
const __defaults = {
isDisabled: false,
caption: undefined,
};
class Button {
constructor(options) {
const obj = { ...options, ...__defaults };
Object.keys(obj).forEach((k) => (this[k] = obj[k]));
}
}
const registerButton = new Button({
isDisabled: true
});
console.log(registerButton);
I can create custom decorator using reflect-metadata and it work fine.
Problem is, that I don`t know how to get all instance decorators.
import 'reflect-metadata';
console.clear();
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
const args = Reflect.getMetadata(
'design:paramtypes',
target,
propertyKey
).map(c => c.name);
const ret = Reflect.getMetadata('design:returntype', target, propertyKey);
console.log(`Arguments type: ${args.join(', ')}.`);
console.log(`Return type: ${ret.name}.`);
};
return decorator;
}
class Foo {}
class Bar {
#readTypes()
public fn(a: number, b: string, c: Foo): boolean {
return true;
}
}
const barInstance = new Bar();
I would like to get all functions with decorator #readTypes from barInstance. How can I do it?
See working example:
https://stackblitz.com/edit/decorators-metadata-example-nakg4c
First of all, you aren't writing any metadata, only reading it. If you want to lookup which properties were decorated, then you have to write metadata to those properties.
For simplicity's sake, let's simplify the decorator to:
// It's a best practice to use symbol as metadata keys.
const isReadTypesProp = Symbol('isReadTypesProp')
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
Reflect.defineMetadata(isReadTypesProp, true, target, propertyKey);
};
return decorator;
}
Now when the decorator is used, it's executed when the class is parsed, not when instances are created. This means that target in the decorator function is actually the prototype of the class constructor function.
In other words target is the same object as
Bar.prototype or barInstance.constructor.prototype or barInstance.__proto__.
Knowing that we can loop over all the property names in the prototype object and look up that metadata we set earlier:
function getReadTypesPropsFromInstance(object: {
constructor: {
prototype: unknown;
};
}) {
const target = object.constructor.prototype;
const keys = Object.getOwnPropertyNames(target);
return keys.filter(key => Reflect.getMetadata(isReadTypesProp, target, key));
}
Which now returns the names of the decorated properties:
const barInstance = new Bar();
console.log(getReadTypesPropsFromInstance(barInstance)); // ["fn"]
Working example
Is it possible to get a handle to the "class object" (constructor) in a decorator?
Background: I want to parse a json'ish format with string values that are tagged with types, e.g. "#date:2019-01-25" or "#latlong:51.507351,-0127758".
This is a modernization effort of an older js library where this was achieved by overriding both subclassing and instance creation.
Decorators looked promising, at least I can define the tag as a class attribute:
function dkdatatype({tag}) {
return function decorator(cls) {
if (cls.kind !== 'class') throw `not class ${cls.kind}`;
cls.elements.push({
kind: 'field',
key: 'tag',
placement: 'static',
descriptor: {
configurable: false,
enumerable: true,
writable: false
},
initializer: () => tag
});
return {
kind: 'class',
elements: cls.elements
};
};
}
#dkdatatype({tag: '#date:'})
export class DkDate extends datatype {
constructor(...args) {
super();
const clstag = this.constructor.tag;
if (typeof args[0] === 'string' && args[0].startsWith(clstag)) {
this.value = new Date(args[0].substr(clstag.length));
} else {
this.value = new Date(...args);
}
}
toJSON() {
return this.constructor.tag + this.value.toISOString().slice(0, 10);
}
}
I can add the class to the type registry manually:
type_registry[DkDate.tag] = DkDate
but is there any way to do this automatically (and only once) from the decorator (or perhaps the base class, or some other way)?
According to the current proposal docs, you want to add an extras property to the class descriptor returned by your decorator, which should contain a "hook" descriptor, and that descriptor should have a finish method that will be called with the class itself as an argument once the class is fully defined.
Here's the example code:
function defineElement(tagName) {
return function(classDescriptor) {
let { kind, elements } = classDescriptor;
assert(kind == "class");
return {
kind,
elements,
// This callback is called once the class is otherwise fully defined
extras: [
{
kind: "hook",
placement: "static",
finish(klass) {
window.customElements.define(tagName, klass);
}
}
]
};
};
In your case, the extras would look like this:
extras: [
{
kind: "hook",
placement: "static",
finish(klass) {
type_registry[tag] = klass;
},
},
],
I'm trying to default options in ES7 using babel. Here is what I can do:
class Foo {
constructor({key='value', foo='bar', answer=42}) {
this.key = key;
this.foo = foo;
this.number = number;
}
}
This might work for this example, but I would like to know how can I assign for very large config objects; here is an example of what I wanna do:
class Foo {
constructor(opts = {key='value', foo='bar', answer=42}) {
this.opts = opts;
}
}
However this does not compile. I tried to do it like this:
class Foo {
constructor(opts = {key:'value', foo:'bar', answer:42}) {
this.opts = opts;
}
}
But then it replaces the whole object, like this:
let foo = new Foo({key: 'foobar'});
console.log(foo.opts);
// {key: 'foobar'} is what is displayed
// When I want {key: 'foobar', foo:'bar', answer:42}
I don't think you can do this with ES6 optional parameters (object as a parameter with optional keys), because when you call the constructor, it's a new object with a new reference. That's because it's being replaced.
But, as a suggestion, if you want to handle a large options object, one common approach is store somewhere a default options Object and merge the object with the one passed when you instantiate it.
Something like that:
class Foo {
constructor(opts) {
this.opts = Object.assign({}, Foo.defaultOptions, opts)
console.log(this.opts)
}
}
Foo.defaultOptions = {
key: 'value',
foo: 'bar',
answer: 42
}
let foo = new Foo({key: 'another value'})
//{ key: 'another value', foo: 'bar', answer: 42 }
You can merge with Object.assign (be aware that it does not perform deep merging - nested objects are replaced).
Or, if you want to declare your default options Object as a class variable (not at the end, after class declaration, or inside constructor), as you're using babel, you can use this plugin and do this:
class Foo {
defaultOptions = {
key: 'value',
foo: 'bar',
answer: 42
}
constructor(opts) {
this.opts = Object.assign({}, this.defaultOptions, opts)
console.log(this.opts)
}
}
It's more readable.
It is
class Foo {
constructor({key='value', foo='bar', answer=42} = {}) {
...
}
}
It is ES6 destructuring feature, not specific to ECMAScript 7 (ECMAScript Next) proposals.
Without destructuring it is usually done with object cloning/merging, Object.assign comes to help:
class Foo {
constructor(opts = {}) {
this.opts = Object.assign({
key: 'value',
foo: 'bar',
answer: 42
}, opts);
}
}
My use case is the following: I want to create a factory which produces various kinds of data transfer objects (DTOs). They must be easily serializable and they must have a few additional methods.
My current implementation looks like this (simplified):
window.Dto = function(type, properties)
{
var
self = this,
values = {},
object = Object.create(self);
properties.forEach(function(prop){
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
this.getType = function()
{
return type;
};
this.doSomeMagic = function()
{
// ...
};
return object;
};
// creating a DTO of the Transport.Motorized.Car class
var carObject = new Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
(Note: I do not want to create an explicit class for each of these objects, because there are hundets of them, and they are exported from the server side. Also, what you see as properties parameter above, is actually a map of meta data with validation constraints etc.)
I did a quick performance check with a loop where 50,000 of such objects were created. performance.now() tells me that it took a bit more than 1s – which looks ok, but not too impressive.
My question is mainly: Is it ok that the factory creates an instance from its own prototype (if I understand correctly what that code does) and returns it? What side effects can it have? Is there a better way?
As far as I understand factory functions, their whole point is not needing to create new instances of the function itself. Instead, it just returns a newly created object.
So instead of using instance properties (via this) of the newly created instance (via the new operator), I would just create an object (let's call it factoryProto) and assign all the "instance" methods to that object instead.
Then, you can use factoryProto as the [[Prototype]] for your new object:
window.Dto = function(type, properties) {
var factoryProto = {
getType: function() {
return type;
},
doSomeMagic: function() {
// ...
}
},
values = {},
object = Object.create(factoryProto);
properties.forEach(function(prop) {
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
return object;
};
// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
If you want to fully profit from the prototype-chain, you could define the factoryProto outside of the factory function. To keep track of type, you could add it as a non-enumerable object property:
window.Dto = (function() {
var factoryProto = {
getType: function() {
return this.type;
},
doSomeMagic: function() {
// ...
}
};
return function(type, properties) {
var values = {},
object = Object.create(factoryProto);
properties.forEach(function(prop) {
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
Object.defineProperty(object, 'type', {
value: type,
enumerable: false
});
return object;
};
})();
// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);