Creating an immutable object as a class in javascript - javascript

I would like to create an object that has various methods, but the core data can never be changed. If using a class-based approach, are using private variables the only way that this can be done? For example:
class Data {
#name;
#elements;
constructor(name, elements = new Set) {
this.#name = name;
this.#elements = elements;
}
get name() { return this.#name };
get elements() { return this.#elements };
get size() { return this.#elements.size };
}
export { Data }; // note: why doesn't export work in StackOverflow's javascript runner?
What can be improved on the above?

If you want the entire object to be immutable you can call Object.freeze in the constructor.
class Data {
constructor(name, elements = new Set) {
this.name = name;
this.elements = elements;
Object.freeze(this);
}
get size() { return this.elements.size };
}
var obj = new Data('A');
obj.name = 'B'; // Has no effect since the object is frozen.
console.log(obj.name); // Prints 'A'
obj.elements.add(42); // This works since the object is frozen only shallowly.
console.log(obj.size); // Prints 1
Of course, this does not work if you just want some properties to be immutable and others not. Also note that Object.freeze creates a shallow freeze. Therefore new elements can be added to obj.elements in the example above. If you don't want this behavior, you need to call Object.freeze recursively on all properties (see deepFreeze).
The call to Object.freeze may pose problems if another classes inherits from your Data class. See this question for solutions in this scenario.

Related

Clone private fields of class to implement immutable pattern

I'm trying to use JS classes with private fields for a React app (because I still find it weird to use naked Object instances everywhere). React uses the concept of immutable state, so I have to clone my objects in order to change them. I'm using private fields - with getters and setters to access them. The problem I have is that private fields don't get cloned in Firefox, Chrome, or Node. Annoyingly, I had a false positive with my React project's Jest setup, where this code works as expected in unit tests.
Is there a way to get around this? Otherwise, it looks like I have to give up some of my (perceived) encapsulation safety and use underscore-prefixed "private" fields instead.
This is my cloning function:
const objclone = obj => {
const cloned = Object.assign(
Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj),
),
obj,
);
return cloned;
};
This clones the getters and setters as well as the object properties and appears to work well until I use private fields.
Example:
class C {
#priv;
constructor() {
this.#priv = 'priv-value';
}
get priv() { return this.#priv; }
}
const obj = new C();
console.log("obj.priv", obj.priv);
const cloned = objclone(obj);
console.log("cloned.priv", cloned.priv);
Error messages that are shown when trying to access cloned.priv:
Firefox:
Uncaught TypeError: can't access private field or method: object is not the right class
Chrome and Node:
Uncaught TypeError: Cannot read private member #priv from an object whose class did not declare it
I solved it. It's not as simple as I'd like - and I don't know if it can be made any simpler, but it looks pretty good to me.
Keys in solving the problem:
Objects of the same class can access each other's private fields
The only way to get an object to define its private fields is by calling its constructor.
I created a Cloner class that can clone normal JS objects, but also object which implement one of two interfaces: cloneMe or copyOther. The cloneMe interface allows an object to create the clone, populate it and return it, while the copyOther interface lets the Cloner call new, which results in slightly less cloning code.
An object has to implement one of these interfaces, and it is responsible for manually copying the private fields over. With a bit of luck, the mental overhead is minimal.
I used Symbol to prevent identifier collisions. I hope I did it right, as I never used this before.
class Cloner {
static cloneMe = Symbol('clone');
static copyOther = Symbol('copy');
static clone(obj, init = []) {
if (!(obj instanceof Object)) {
// reject non-Object input
throw new Error(`Cannot clone non-Object of type ${typeof(obj)}`)
} else if (obj[this.cloneMe]) {
// self-cloning object
return obj[this.cloneMe](...init);
} else if (obj[this.copyOther]) {
// copier object
const cloned = Object.assign(new obj.constructor(...init), obj);
// ask the cloned object to copy the source
cloned[this.copyOther](obj);
return cloned;
} else {
// classic cloning
return Object.assign(Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj),
),
obj,
);
}
}
}
Example cloneMe implementation:
class MyClonerClass {
#priv;
constructor(init) {
this.#priv = init;
}
[Cloner.cloneMe](...init) {
const cloned = new this.constructor(...init);
cloned.#priv = this.#priv;
return cloned;
}
get a() {
return this.#priv;
}
set a(value) {
this.#priv = value;
}
}
Example copyOther implementation:
class MyCopierClass {
#priv;
constructor(init) {
this.#priv = init;
}
[Cloner.copyOther](src) {
this.#priv = src.#priv;
}
get a() {
return this.#priv;
}
set a(value) {
this.#priv = value;
}
}
Usage:
const copySrc = new MyCopierClass('copySrc.#a');
const copyDst = Cloner.clone(copySrc);
copyDst.a = 'copyDst.#a';
console.log(copySrc.a);
console.log(copyDst.a);
const cloneSrc = new MyClonerClass('cloneSrc.#a');
const cloneDst = Cloner.clone(cloneSrc);
cloneDst.a = 'cloneDst.#a';
console.log(cloneSrc.a);
console.log(cloneDst.a);
Not shown here is the init parameter of Cloner.clone. That can be used if the constructor expects certain parameters to exist, and a naked constructor wouldn't work.
The cloneMe interface can take an init via the Cloner, or could supply its own based on internal state, keeping things nicely encapsulated and nearby.
Extra credits
While figuring this out, I thought up a way to simplify the cloning code quite a bit, by keeping the private fields in a dictionary. This crushes the TC39 hopes and dreams of a fixed compile-time list of private fields that cannot be added to or removed from, but it makes things a bit more Javascript-y. Have a look at the copyOther implementation - that's pretty much all of it, ever.
class WeirdPrivPattern {
#priv = {}
constructor(a, b) {
this.#priv.a = a;
this.#priv.b = b;
}
get a() {return this.#priv.a;}
set a(value) {this.#priv.a = value;}
get b() {return this.#priv.b;}
set b(value) {this.#priv.b = value;}
[Cloner.copyOther](src) {
this.#priv = {...src.#priv}
}
}
A note on deep cloning: it is outside of the scope of this answer. I am not worried about deep cloning. I actually rely on child objects keeping their identity if not mutated.

How to discern between a Javascript object's methods and its properties if they share the same name?

I have the following classes, which are designed to build an email template for me.
EmailBuilder.js class
const Email = require("./Email");
class EmailBuilder {
templateId(templateId) {
this.templateId = templateId;
return this;
}
build() {
return new Email(this);
}
}
Email.js Class
class Email {
constructor(builder) {
if (!builder.templateId) {
throw new Error("You need a templateId");
}
this.templateId = builder.templateId;
}
}
module.exports = Email;
When I call the following line, I want an exception to be thrown, because a templateId was not provided, which is required.
const email = new EmailBuilder().build();
However, this doesn't occur because in the Email class, builder.templateId is a function and therefore not falsey.
So the problem here is that my object's property (templateId) in the EmailBuilder.js class shares the same name as the object's method (templateId()).
What is the best way to adjust my code to solve this problem? Should I just change the name of either the property (templateId) or method (templateId) i.e. make the property _templateId and the function templateId? Is there a way of distinguishing between an object's properties and the object's methods in Javascript that I'm not aware of?
Note that EmailBuilder is a builder and therefore it is called in the following manner.
const email = new EmailBuilder()
.templateId("Some templateId")
.build();
You can't have non-method properties and method properties with the same name on the same object.¹ They're all just properties, and you can only have one property with a given name.
Instead:
Have the caller assign to templateId directly
Use an accessor property with a private field (brand new, not quite in the spec yet):
class EmailBuilder {
#templateId: number;
// ^^^^^^^^^^^^^^^^^^^^
set templateId(templateId) {
this.#templateId = templateId;
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
return this;
}
// You might have a getter here as well
get templateId() {
return this.#templateId;
}
build() {
return new Email(this);
}
}
JavaScript private fields are finding their way into implementations now, and for environments that don't have them yet, can be used via a transpiler like Babel.
Use Java-like setter naming (not a popular option)
setTemplateId(templateId) {
this.templateId = templateId;
return this;
}
¹ "You can't have non-method properties and method properties with the same name on the same object." Okay, so, technically, you weren't trying to do that. :-D Technically, you had a templateId method on one object (EmailBuilder.prototype) and you were putting a templateId property on another object (the instance created by new EmailBuilder). Technically, you can do that, but in practical terms using the method once you've created the data property on the instance is far too convoluted for real-world code, because the instance property shadows the prototype method. For code to reliably get the method, it would need to do this:
const email = new EmailBuilder();
Object.getPrototypeOf(email).templateId.call(email, "someId");
I think we can probably agree that's just too much hassle...
// I really don't suggest doing this
class EmailBuilder {
templateId(templateId) {
this.templateId = templateId;
return this;
}
build() {
return new Email(this);
}
}
const email = new EmailBuilder();
console.log(email.templateId); // "templateId(templateId) { this.templateId = templateId; return this; }"
email.templateId("someId"); // Works, but...
try {
email.templateId("anotherId"); // Fails
} catch (e) {
}
Object.getPrototypeOf(email).templateId.call(email, "aThirdId"); // Works, but...blech
console.log(email.templateId); // "aThirdId"
Looks like templateId is a getter and should be renamed getTemplateId.
or rather you could create a setter for the email builder and use the super keyword to inherit the id from the template.

Are classes necessary in Javascript? What effect do they have on creating objects?

Consider this example code:
class Test {
say() {
console.log("I'm a test.");
}
}
let TestFromClass = new Test();
let TestFromObject = {
say() {
console.log("I'm also a test.");
}
};
TestFromClass.say(); // Output: I'm a test.
TestFromObject.say(); // Output: I'm also a test.
I understand that it's possible to create objects, such as TestFromObject, without first creating a class with the class keyword. Is class necessary at all? Is there a difference between these two kinds of objects? If so, what effect does it have to use class explicitly?
Using new creates a new object whose internal prototype is the class's prototype. For example:
class Test {
say() {
console.log("I'm a test.");
}
}
let TestFromClass = new Test();
console.log(Object.getPrototypeOf(TestFromClass) === Test.prototype);
This is useful for creating multiple objects. The usual reason to do this is so that each object can have some sort of associated state - generally, the values of its properties. For example, a Person object might have a name and an age property.
However, if there is no data to associate with an instance (as with TestFromClass in the original code), there's not much point having an instance at all. The TestFromObject approach makes much more sense if the purpose is just to collect named functions into a data structure.
That said, it's sometimes desirable to have a class that has some functions associated with it (like say) which don't have anything to do with an instance of its data, while still being able to create an instance - perhaps using other methods on the prototype. This isn't that uncommon, and is done by making the non-instance-related functions static:
class Person {
static canEat() {
return ['apples', 'bananas', 'carrots'];
}
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const p = new Person('Bob', 99);
console.log(p.name);
console.log(Person.canEat());
A class is more or less just syntactic sugar for a prototype:
// The class way
class Test {
say() {
console.log("I'm a test.");
}
}
// The old fashioned way
function Test() {
}
Test.prototype.say = function () {
console.log("I'm a test.");
};
The difference in both these cases with direct object creation is that the methods belong to the prototype, not directly to the object.
The code TestFromClass.say() must go through the prototype chain to find a say method, while TestFromObject directly has the method.
Other than that, there's no difference.
Is there a difference between these two kinds of objects? If so, what effect does it have to use class explicitly?
There effectively isn't a meaningful difference, but it does have some minor effects:
When you create an object with literal syntax, unless you use the special __proto__ property name, its prototype will always be Object.prototype. (Note that __proto__ is officially optional, a JavaScript engine doesn't have to provide it.) When you create it with new X, provided X is a constructor function created with class syntax, the result will have the prototype X.prototype.
Methods defined within an object literal are directly placed on the object. Methods within class X go on X.prototype (non-static ones) which the object inherits.
Objects come in two forms: the declarative (literal) form, and the constructed form.
The literal syntax for an object looks like this:
var myObj = {
key: value
// ...
};
The constructed form looks like this:
var myObj = new Object();
myObj.key = value;
The constructed form and the literal form result in exactly the same sort of object. The only difference really is that you can add one or more key/value pairs to the literal declaration, whereas with constructed-form objects, you must add the properties one-by-one.
Note: It's extremely uncommon to use the "constructed form" for creating objects as just shown. You would pretty much always want to use the literal syntax form. The same will be true of most of the built-in objects.
P.S. To read more in detail go to
https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/this-object-prototypes/ch3.md
So you created a class called Test which is the base class. The you added a method called say() {}.
class Test {
say() {
console.log("I'm a test.");
}
}
Then you created an instance of the Test class below:
let TestFromClass = new Test();
And below that you created a plain JavaScript object called TestFromObject:
let TestFromObject = {
say() {
console.log("I'm also a test.");
}
};
And yes, they will both print out the output you have to their methods:
TestFromClass.say(); // Output: I'm a test.
TestFromObject.say(); // Output: I'm also a test.
The difference will not begin until you utilize the full power of the class you created, for example, utilizing the cconstructor() function like so:
class Test {
constructor() {
}
say() {
console.log("I'm a test.");
}
}
Now the constructor function is automatically called for us when we use the new keyword for the class name. With a constructor function you know also have access to this like so:
class Test {
constructor() {
this.type = 'test';
}
say() {
console.log("I'm a test.");
}
}
Which allows you to do this:
TestFromClass.say(); // Output: I'm a test.
TestFromObject.say(); // Output: I'm also a test.
TestFromClass.type; // Output: test
The constructor is traditionally used to do some initial setup inside the class or particular instance of the class.
The constructor is commonly used by using some arguments when creating a new instance of the class. Maybe you want to specify that the class of Test you have created has 50 questions. You can pass in an object to the new Test instance like so:
let TestFromClass = new Test({ questions: 50 });
You can call this object whatever you want, let's just call the object examination and you can set the number of questions to this examination object.
So now you pass that object into the constructor function and calling that object, examination like so:
class Test {
constructor(examination) {
this.questions = examination.questions;
this.type = 'test';
}
say() {
console.log("I'm a test.");
}
}
let TestFromClass = new Test({ questions: 50 });
TestFromClass.say(); // Output: I'm a test.
TestFromClass.type; // Output: test
TestFromClass.questions; // 50
The other thing you can do with a class object is create a subclass that would extend the functionality of your base class and then you can also add some customized functionality to it.
class Test {
constructor(examination) {
this.questions = examination.questions;
this.type = 'test';
}
say() {
console.log("I'm a test.");
}
}
class Quiz extends Test {
}
So now your Quiz is inheriting all the methods, functions, properties, etcetera that are in the Test class and define additional methods inside of it.
class Test {
constructor(examination) {
this.questions = examination.questions;
this.type = 'test';
}
say() {
console.log("I'm a test.");
}
}
class Quiz extends Test {
constructor(examination) {
this.topic = examination.topic;
}
}
So now to ensure that the constructor function from the parent class is called as well, I can use the super() keyword into the constructor:
class Test {
constructor(examination) {
this.questions = examination.questions;
this.type = 'test';
}
say() {
console.log("I'm a test.");
}
}
class Quiz extends Test {
constructor(examination) {
super(examination)
this.topic = examination.topic;
}
}
Then you can instantiate that subclass like so:
const javascript = new Quiz({topic: 'javascript', questions: 50 });
and finally print it out:
javascript.questions; // Output: 50

How can I override set method in class to work with array.push?

I'm trying to override set method for some property in my object.
class MyClass extends Element {
set locations(markers) {
//setter implementation
}
}
Works correctly when setting the whole array:
obj.locations = ['location1', 'location2'];
I want to expose an API that locations can be set intuitively, with:
obj.locations.push('location3');
But it doesn't even pass through the setter implementation.
What am I doing wrong?
Your push line does not call the setter. You're not assigning an array, but trying to mutate it. For that to work you need to define the getter.
Here is a simplified example:
class MyClass {
set locations(markers) {
this._locations = markers;
}
get locations() {
return this._locations; // Make sure to return an array instance
}
}
var elem = new MyClass;
elem.locations = [];
elem.locations.push("test");
console.log(elem.locations);
If you cannot return an array that would be directly linked to the state of the instance, then let the getter return an object that implements itself push and any other methods you'd like it to expose.

Class variable using Class syntax in Javascript [duplicate]

Currently in ES5 many of us are using the following pattern in frameworks to create classes and class variables, which is comfy:
// ES 5
FrameWork.Class({
variable: 'string',
variable2: true,
init: function(){
},
addItem: function(){
}
});
In ES6 you can create classes natively, but there is no option to have class variables:
// ES6
class MyClass {
const MY_CONST = 'string'; // <-- this is not possible in ES6
constructor(){
this.MY_CONST;
}
}
Sadly, the above won't work, as classes only can contain methods.
I understand that I can this.myVar = true in constructor…but I don't want to 'junk' my constructor, especially when I have 20-30+ params for a bigger class.
I was thinking of many ways to handle this issue, but haven't yet found any good ones. (For example: create a ClassConfig handler, and pass a parameter object, which is declared separately from the class. Then the handler would attach to the class. I was thinking about WeakMaps also to integrate, somehow.)
What kind of ideas would you have to handle this situation?
2018 update:
There is now a stage 3 proposal - I am looking forward to make this answer obsolete in a few months.
In the meantime anyone using TypeScript or babel can use the syntax:
varName = value
Inside a class declaration/expression body and it will define a variable. Hopefully in a few months/weeks I'll be able to post an update.
Update: Chrome 74 now ships with this syntax working.
The notes in the ES wiki for the proposal in ES6 (maximally minimal classes) note:
There is (intentionally) no direct declarative way to define either prototype data properties (other than methods) class properties, or instance property
Class properties and prototype data properties need be created outside the declaration.
Properties specified in a class definition are assigned the same attributes as if they appeared in an object literal.
This means that what you're asking for was considered, and explicitly decided against.
but... why?
Good question. The good people of TC39 want class declarations to declare and define the capabilities of a class. Not its members. An ES6 class declaration defines its contract for its user.
Remember, a class definition defines prototype methods - defining variables on the prototype is generally not something you do.
You can, of course use:
constructor(){
this.foo = bar
}
In the constructor like you suggested. Also see the summary of the consensus.
ES7 and beyond
A new proposal for ES7 is being worked on that allows more concise instance variables through class declarations and expressions - https://esdiscuss.org/topic/es7-property-initializers
Just to add to Benjamin's answer — class variables are possible, but you wouldn't use prototype to set them.
For a true class variable you'd want to do something like the following:
class MyClass {}
MyClass.foo = 'bar';
From within a class method that variable can be accessed as this.constructor.foo (or MyClass.foo).
These class properties would not usually be accessible from to the class instance. i.e. MyClass.foo gives 'bar' but new MyClass().foo is undefined
If you want to also have access to your class variable from an instance, you'll have to additionally define a getter:
class MyClass {
get foo() {
return this.constructor.foo;
}
}
MyClass.foo = 'bar';
I've only tested this with Traceur, but I believe it will work the same in a standard implementation.
JavaScript doesn't really have classes. Even with ES6 we're looking at an object- or prototype-based language rather than a class-based language. In any function X () {}, X.prototype.constructor points back to X.
When the new operator is used on X, a new object is created inheriting X.prototype. Any undefined properties in that new object (including constructor) are looked up from there. We can think of this as generating object and class properties.
Babel supports class variables in ESNext, check this example:
class Foo {
bar = 2
static iha = 'string'
}
const foo = new Foo();
console.log(foo.bar, foo.iha, Foo.bar, Foo.iha);
// 2, undefined, undefined, 'string'
In your example:
class MyClass {
const MY_CONST = 'string';
constructor(){
this.MY_CONST;
}
}
Because of MY_CONST is primitive https://developer.mozilla.org/en-US/docs/Glossary/Primitive we can just do:
class MyClass {
static get MY_CONST() {
return 'string';
}
get MY_CONST() {
return this.constructor.MY_CONST;
}
constructor() {
alert(this.MY_CONST === this.constructor.MY_CONST);
}
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string ; true
But if MY_CONST is reference type like static get MY_CONST() {return ['string'];} alert output is string, false. In such case delete operator can do the trick:
class MyClass {
static get MY_CONST() {
delete MyClass.MY_CONST;
return MyClass.MY_CONST = 'string';
}
get MY_CONST() {
return this.constructor.MY_CONST;
}
constructor() {
alert(this.MY_CONST === this.constructor.MY_CONST);
}
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string ; true
And finally for class variable not const:
class MyClass {
static get MY_CONST() {
delete MyClass.MY_CONST;
return MyClass.MY_CONST = 'string';
}
static set U_YIN_YANG(value) {
delete MyClass.MY_CONST;
MyClass.MY_CONST = value;
}
get MY_CONST() {
return this.constructor.MY_CONST;
}
set MY_CONST(value) {
this.constructor.MY_CONST = value;
}
constructor() {
alert(this.MY_CONST === this.constructor.MY_CONST);
}
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string, true
MyClass.MY_CONST = ['string, 42']
alert(MyClass.MY_CONST);
new MyClass
// alert: string, 42 ; true
Since your issue is mostly stylistic (not wanting to fill up the constructor with a bunch of declarations) it can be solved stylistically as well.
The way I view it, many class based languages have the constructor be a function named after the class name itself. Stylistically we could use that that to make an ES6 class that stylistically still makes sense but does not group the typical actions taking place in the constructor with all the property declarations we're doing. We simply use the actual JS constructor as the "declaration area", then make a class named function that we otherwise treat as the "other constructor stuff" area, calling it at the end of the true constructor.
"use strict";
class MyClass
{
// only declare your properties and then call this.ClassName(); from here
constructor(){
this.prop1 = 'blah 1';
this.prop2 = 'blah 2';
this.prop3 = 'blah 3';
this.MyClass();
}
// all sorts of other "constructor" stuff, no longer jumbled with declarations
MyClass() {
doWhatever();
}
}
Both will be called as the new instance is constructed.
Sorta like having 2 constructors where you separate out the declarations and the other constructor actions you want to take, and stylistically makes it not too hard to understand that's what is going on too.
I find it's a nice style to use when dealing with a lot of declarations and/or a lot of actions needing to happen on instantiation and wanting to keep the two ideas distinct from each other.
NOTE: I very purposefully do not use the typical idiomatic ideas of "initializing" (like an init() or initialize() method) because those are often used differently. There is a sort of presumed difference between the idea of constructing and initializing. Working with constructors people know that they're called automatically as part of instantiation. Seeing an init method many people are going to assume without a second glance that they need to be doing something along the form of var mc = MyClass(); mc.init();, because that's how you typically initialize. I'm not trying to add an initialization process for the user of the class, I'm trying to add to the construction process of the class itself.
While some people may do a double-take for a moment, that's actually the bit of the point: it communicates to them that the intent is part of construction, even if that makes them do a bit of a double take and go "that's not how ES6 constructors work" and take a second looking at the actual constructor to go "oh, they call it at the bottom, I see", that's far better than NOT communicating that intent (or incorrectly communicating it) and probably getting a lot of people using it wrong, trying to initialize it from the outside and junk. That's very much intentional to the pattern I suggest.
For those that don't want to follow that pattern, the exact opposite can work too. Farm the declarations out to another function at the beginning. Maybe name it "properties" or "publicProperties" or something. Then put the rest of the stuff in the normal constructor.
"use strict";
class MyClass
{
properties() {
this.prop1 = 'blah 1';
this.prop2 = 'blah 2';
this.prop3 = 'blah 3';
}
constructor() {
this.properties();
doWhatever();
}
}
Note that this second method may look cleaner but it also has an inherent problem where properties gets overridden as one class using this method extends another. You'd have to give more unique names to properties to avoid that. My first method does not have this problem because its fake half of the constructor is uniquely named after the class.
As Benjamin said in his answer, TC39 explicitly decided not to include this feature at least for ES2015. However, the consensus seems to be that they will add it in ES2016.
The syntax hasn't been decided yet, but there's a preliminary proposal for ES2016 that will allow you to declare static properties on a class.
Thanks to the magic of babel, you can use this today. Enable the class properties transform according to these instructions and you're good to go. Here's an example of the syntax:
class foo {
static myProp = 'bar'
someFunction() {
console.log(this.myProp)
}
}
This proposal is in a very early state, so be prepared to tweak your syntax as time goes on.
What about the oldschool way?
class MyClass {
constructor(count){
this.countVar = 1 + count;
}
}
MyClass.prototype.foo = "foo";
MyClass.prototype.countVar = 0;
// ...
var o1 = new MyClass(2); o2 = new MyClass(3);
o1.foo = "newFoo";
console.log( o1.foo,o2.foo);
console.log( o1.countVar,o2.countVar);
In constructor you mention only those vars which have to be computed.
I like prototype inheritance for this feature -- it can help to save a lot of memory(in case if there are a lot of never-assigned vars).
[Long thread, not sure if its already listed as an option...].
A simple alternative for contsants only, would be defining the const outside of class.
This will be accessible only from the module itself, unless accompanied with a getter.
This way prototype isn't littered and you get the const.
// will be accessible only from the module itself
const MY_CONST = 'string';
class MyClass {
// optional, if external access is desired
static get MY_CONST(){return MY_CONST;}
// access example
static someMethod(){
console.log(MY_CONST);
}
}
ES7 class member syntax:
ES7 has a solution for 'junking' your constructor function. Here is an example:
class Car {
wheels = 4;
weight = 100;
}
const car = new Car();
console.log(car.wheels, car.weight);
The above example would look the following in ES6:
class Car {
constructor() {
this.wheels = 4;
this.weight = 100;
}
}
const car = new Car();
console.log(car.wheels, car.weight);
Be aware when using this that this syntax might not be supported by all browsers and might have to be transpiled an earlier version of JS.
Bonus: an object factory:
function generateCar(wheels, weight) {
class Car {
constructor() {}
wheels = wheels;
weight = weight;
}
return new Car();
}
const car1 = generateCar(4, 50);
const car2 = generateCar(6, 100);
console.log(car1.wheels, car1.weight);
console.log(car2.wheels, car2.weight);
You can mimic es6 classes behaviour... and use your class variables :)
Look mum... no classes!
// Helper
const $constructor = Symbol();
const $extends = (parent, child) =>
Object.assign(Object.create(parent), child);
const $new = (object, ...args) => {
let instance = Object.create(object);
instance[$constructor].call(instance, ...args);
return instance;
}
const $super = (parent, context, ...args) => {
parent[$constructor].call(context, ...args)
}
// class
var Foo = {
classVariable: true,
// constructor
[$constructor](who){
this.me = who;
this.species = 'fufel';
},
// methods
identify(){
return 'I am ' + this.me;
}
}
// class extends Foo
var Bar = $extends(Foo, {
// constructor
[$constructor](who){
$super(Foo, this, who);
this.subtype = 'barashek';
},
// methods
speak(){
console.log('Hello, ' + this.identify());
},
bark(num){
console.log('Woof');
}
});
var a1 = $new(Foo, 'a1');
var b1 = $new(Bar, 'b1');
console.log(a1, b1);
console.log('b1.classVariable', b1.classVariable);
I put it on GitHub
Still you can't declare any classes like in another programming languages. But you can create as many class variables. But problem is scope of class object. So According to me, Best way OOP Programming in ES6 Javascript:-
class foo{
constructor(){
//decalre your all variables
this.MY_CONST = 3.14;
this.x = 5;
this.y = 7;
// or call another method to declare more variables outside from constructor.
// now create method level object reference and public level property
this.MySelf = this;
// you can also use var modifier rather than property but that is not working good
let self = this.MySelf;
//code .........
}
set MySelf(v){
this.mySelf = v;
}
get MySelf(v){
return this.mySelf;
}
myMethod(cd){
// now use as object reference it in any method of class
let self = this.MySelf;
// now use self as object reference in code
}
}
If its only the cluttering what gives the problem in the constructor why not implement a initialize method that intializes the variables. This is a normal thing to do when the constructor gets to full with unnecessary stuff. Even in typed program languages like C# its normal convention to add an Initialize method to handle that.
Just define a getter.
class MyClass
{
get MY_CONST () { return 'string'; }
constructor ()
{
console.log ("MyClass MY_CONST:", this.MY_CONST);
}
}
var obj = new MyClass();
The way I solved this, which is another option (if you have jQuery available), was to Define the fields in an old-school object and then extend the class with that object. I also didn't want to pepper the constructor with assignments, this appeared to be a neat solution.
function MyClassFields(){
this.createdAt = new Date();
}
MyClassFields.prototype = {
id : '',
type : '',
title : '',
createdAt : null,
};
class MyClass {
constructor() {
$.extend(this,new MyClassFields());
}
};
-- Update Following Bergi's comment.
No JQuery Version:
class SavedSearch {
constructor() {
Object.assign(this,{
id : '',
type : '',
title : '',
createdAt: new Date(),
});
}
}
You still do end up with 'fat' constructor, but at least its all in one class and assigned in one hit.
EDIT #2:
I've now gone full circle and am now assigning values in the constructor, e.g.
class SavedSearch {
constructor() {
this.id = '';
this.type = '';
this.title = '';
this.createdAt = new Date();
}
}
Why? Simple really, using the above plus some JSdoc comments, PHPStorm was able to perform code completion on the properties. Assigning all the vars in one hit was nice, but the inability to code complete the properties, imo, isn't worth the (almost certainly minuscule) performance benefit.
Well, you can declare variables inside the Constructor.
class Foo {
constructor() {
var name = "foo"
this.method = function() {
return name
}
}
}
var foo = new Foo()
foo.method()
Recent browsers as of 2021 (not IE, see MDN browser chart) implement Public class fields which seems to be what you're looking for:
class MyClass {
static foo = 3;
}
console.log(MyClass.foo);
However apparently it's not possible to make this a const: Declaring static constants in ES6 classes?
A static getter looks pretty close:
class MyClass {
static get CONST() {
return 3;
}
}
MyClass.CONST = 4; // property unaffected
console.log(MyClass.CONST);
This is a bit hackish combo of static and get works for me
class ConstantThingy{
static get NO_REENTER__INIT() {
if(ConstantThingy._NO_REENTER__INIT== null){
ConstantThingy._NO_REENTER__INIT = new ConstantThingy(false,true);
}
return ConstantThingy._NO_REENTER__INIT;
}
}
elsewhere used
var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...

Categories