Here is my way I usually use to prevent to override name property.
let _name = Symbol('name');
class Cat {
constructor(name) {
this[_name] = name;
}
set name(newName) {
return this[_name];
}
get name() {
return this[_name];
}
}
// default name
let cat = new Cat('Hermione');
// new name
cat.name = 'Voldermort';
// testing
console.log(cat.name);
My idea: Saving the value of name property to another variable.
But if I have multiple class properties, like this:
that would be wasting to create a lot of variables for saving.
let _Min = Symbol('Min'), _Max = Symbol('Max'); // and so on
Is there another way to achieve this goal? Thank you!
In order to be read-only, the property should have no set accessor:
class Foo {
get bar() {
return 'bar';
}
}
If the property is supposed to be defined on construction, the descriptor can be defined too:
class Cat {
constructor(name) {
Object.defineProperty(this, name, {
get: () => name,
configurable: true
});
}
}
Or
class Cat {
constructor(name) {
Object.defineProperty(this, name, {
value: name,
writable: false,
configurable: true
});
}
}
Try following examples, which may solve your problem
Example 1:
class A {
a = () => {
console.log('A#a');
}
}
class B extends A {
// this will throw error as
// Class 'A' defines instance member property 'a', but extended class 'B'
// defines it as instance member function.
a() {
console.log('B#a')
}
}
new B().a()
/**
Error: Class 'A' defines instance member property 'a', but extended class 'B'
defines it as instance member function.
*/
Example 2:
class Renderer {
constructor(args) {
Object.defineProperty(this, 'render', {
writable: false,
configurable: true,
value: () {
return this.childRender();
}
});
}
childRender() {
throw Error('render() is already instance of Renderer. Did you mean? childRender()');
}
// do not define methodName() here
}
class Draw extends Renderer {
render() {
console.log('DrawB#render');
}
}
new Draw().render()
/**
VM1597:13 Uncaught Error: render() is already instance of Renderer. Did you mean? childRender()
at Drawee.childRender (<anonymous>:13:11)
at Drawee.value (<anonymous>:7:22)
at <anonymous>:25:14
*/
I hope this helps 🙂
Related
I am trying to get my class to behave like a "normal" object, in that when it is called in object.entries it returns an array of key value pairs. After quite a bit of searching around I have been able to make my class iterable. But I cannot find where to start in terms of implementing object.entries.
Here is where I have got up to,
'use strict'
class Person {
#name
constructor(name) {
this.#name = name
}
get name () {
return this.#name
}
*iterator () {
var props = Object.getOwnPropertyNames(Object.getPrototypeOf(this))
for (const prop of props) {
if (typeof this[prop] !== 'function') {
const o = {}
o[prop] = this[prop]
yield o
}
}
}
[Symbol.iterator] () {
return this.iterator()
}
}
const person = new Person ('bill')
//works - produces { name: 'bill' }
for (const prop of person){
console.log (prop)
}
// doesn't work. Prints an empty array
console.log (Object.entries(person))
The issue is that your instances have no public "own" (not inherited) properties, which is what Object.entries includes in its array.
I am trying to get my class to behave like a "normal" object, in that when it is called in object.entries it returns an array of key value pairs.
That's the default behavior, whether you create the object via a class constructor or some other way.
After quite a bit of searching around I have been able to make my class iterable.
Object.entries has nothing to do with whether an object is iterable.
I don't think you can make Object.entries return [[name, "value"]] without making name an "own" data property. But you can make it a read-only own data property via Object.defineProperty:
class Person {
#name;
constructor(name) {
this.#name = name;
Object.defineProperty(this, "name", {
value: name,
writable: false,
enumerable: true,
configurable: true
});
}
}
const bill = new Person("Bill");
console.log(`bill.name:`, bill.name);
console.log(`Object.entries(bill):`, Object.entries(bill));
I've kept #name there, but there's probably no reason for it, so:
class Person {
constructor(name) {
Object.defineProperty(this, "name", {
value: name,
writable: false,
enumerable: true,
configurable: true
});
}
}
const bill = new Person("Bill");
console.log(`bill.name:`, bill.name);
console.log(`Object.entries(bill):`, Object.entries(bill));
If you want to be able to set name internally, just give yourself a private method that repeates the defineProperty:
class Person {
constructor(name) {
this.#setName(name);
}
#setName(name) {
Object.defineProperty(this, "name", {
value: name,
writable: false,
enumerable: true,
configurable: true
});
}
}
const bill = new Person("Bill");
console.log(`bill.name:`, bill.name);
console.log(`Object.entries(bill):`, Object.entries(bill));
Unfortunately, because the property is configurable, your code isn't the only code that can use defineProperty to change it. (This is rather like the name property on functions, which is read-only by default but can be changed via defineProperty.) So it's not exactly the same as having #name and get name, it's just close while providing support for Object.entries.
You actually don't have to implement the iterator. The reason Object.entries wouldnt work is that due to the # the property is treated as private and therefore is not returned. If you remove the # name is returned by Object.entries like in the example below.
class Person {
name
constructor(name) {
this.name = name
}
}
const person = new Person ('bill')
// doesn't work. Prints an empty array
console.log(Object.entries(person))
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 have this code snipet:
proxy('http://my-custom-api-endpoint.com', {
proxyReqOptDecorator(options) {
options.headers['x-forwarded-host'] = 'localhost:3000'
return options
}
})
It is a call to a function named proxy, the first argument is a string, but the second argument has a syntax than I can't recognize:
{
functionName(args) {
// statements
}
}
Can someone explain that syntax please?
Its a shorthand method in Object Initializer to create a property whose value is a function.
// Shorthand method names (ES2015)
let o = {
property(parameters) {}
}
//Before
let o = {
property: function(parameters) {}
}
This syntax is also sued in classes to declare class methods.
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}class Animal {
speak() {
return this;
}
eat() {
return this;
}
}
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;
},
},
],
Good day,
I dont know if am can explain this well for you to help but i will like to use a an ES6 class to create an object that can be called like this.
var = varaibles
obj = objects
obj.var
obj.var.method
obj.var.var.method
obj.method.var
and so on.
I can only do one step
obj.var && obj.method
i will kind appreciate if one can help me here thanks
this is what i have done
class Table extends someClass {
constructor() {
super();
this.column = {
sort: () => {
console.log("firing");
},
resize: () => {
console.log("firing");
}
};
this.cells = {
edit: () => {
console.log("firing");
}
};
}
myMethods() {
//BLAH
}
}
From what I understood, here is my solution.
If I return a object full of methods, I can use that object as I like.
class someClass {
// this is a parent method
Parent() {
console.log(`From a Parent`)
}
// a getter that returns an object
get parentWithChild() {
return {
child() {
console.log(`From a Child`)
}
}
}
// a function that returns an object
Methods() {
return {
child() {
console.log(`From a Child`)
}
}
}
}
const cool = new someClass();
cool.Parent(); // From a Parent
cool.parentWithChild.child(); // From a Child
cool.Methods().child(); // From a Child
You can use similar pattern on the extended class too.