Reference to class property is null - javascript

I was under the impression that anything that is part of an object is set by reference. This doesn't seem to be the case in this example
class System {}
class Foo {
constructor() {
this.system = null;
this.dictionary = {
system: this.system
}
}
}
class Bar extends Foo {
constructor() {
super();
this.system = new System();
}
}
var bar = new Bar();
console.log(bar.system); // System{}
console.log(bar.dictionary.system); // null
I would expect that dictionary is holding a reference to this.system which starts as null, but only because what it is referencing is null. However as can be seen, bar.dictionary.system is actually still null even though its reference was updated.
Can anyone explain what is happening here?

It's still being set by reference. The issue here is that by writing this.system = new System() you're not modifying the referenced value, you're making this.system reference a different value altogether. Meanwhile, this.dictionary.system is still pointing towards the old value.
Consider the following code:
class Foo {
constructor() {
this.system = {};
this.dictionary = { system: this.system };
}
}
class Bar {
constructor() {
super();
this.system.bananas = 5;
}
}
this would correctly add bananas to this.system and this.dictionary.system, as you're modifying the value being referenced, not making this.system reference a different value entirely.

You are trying to use a value of system, which is assigned, after you actually want to use it. That's like the follwing, which won't also work
var x = null;
console.log(x);
x = "Hallo Welt";
The problem in your case is, you couldn't simply swap the call of super() with the assignment - which would solve the problem. Instead you could use properties or functions, which are getting overridden in the child class and return the actual value.
class Foo {
constructor() {
this.dictionary = {
system: this.system
}
}
get system() { return null; }
}
class Bar extends Foo {
constructor() {
super();
}
get system() {
return this._system ?
this._system :
this._system = new System();
}
}

You are assigning this.system after you call the base constructor. If you do this, you don't update dictionary once you call this.system = new System();
Here is a rough imitation of what the compiler sees:
this.system = null;
this.dictionary = {
system: this.system
}
this.system = new System();
See how you are not actually updating this.dictionary.
Imagine I have this code:
var a = 5;
var b = a;
a = 6;
At this point, a is 6 and b is 5. This is because I set b to the previous value of a. If I want to update b, I have to equate them again.

Related

Use overwritten value in parent constructor

I'm invoking a function in constructor where I want to use the variable from child in the function call that's made through parent constructor. For demo purpose I've created a small script to show what I want to do:
class A{
intVal = 1;
constructor() {
this.identifyClass();
}
identifyClass()
{
console.log("I am class A", this.intVal); // This should be the value that B has overwitten
}
}
class B extends A
{
intVal = 2;
}
const x = new B();
So I'd want that the function in parent constructor should use the value that was overwritten by B ie. intVal = 2 but currently it uses the original value. Can I use any workaround for this so that I don't have to create a constructor in B and then invoke the function there?
Note: It's a very complex app where I don't want breaking changes to A which is being used at a lot of places and the Class B is being exposed to public so I don't want the people using Class B to change anything if possible where currently they just overwrite the instance variable
Ok, I think I got it. We create a symbol, if the last argument passed to the constructor is not the symbol, then we create a new object, using this.constructor, passing in the original arguments, plus the symbol, and then call identify. This effectively separates the call to identify from the constructor, allowing us to get this.intVal from the subclass, while still being transparent to the user of the subclass.
const _sym = Symbol();
class A {
intVal = 1;
constructor() {
const args = Array.from(arguments);
const _ = args[args.length - 1];
if (_ !== _sym) {
args.push(_sym);
const el = new this.constructor(...args);
el.identify();
return el;
}
}
identify() {
console.log(this.intVal);
}
}
class B extends A {
intVal = 2;
}
const x = new A();
const y = new B();

Overriden method, that is invoked from parent constructor, doesn't initialize a field

Here is the problem:
class A {
value;
constructor() {
this.setUp();
}
setUp() {
this.value = 1;
}
}
class B extends A {
value2;
constructor() {
super();
}
setUp() {
super.setUp();
this.setUp2();
}
setUp2() {
this.value2 = 5;
console.log(this.value2); // However, the value was assigned here.
}
}
let b = new B();
console.log(b.value);
console.log(b.value2); // But here value2 field is undefined.
I noticed, that if turn on ES2015, value2 is also displayed, but in my browser and application doesn't (I use ES6). Does anyone know how to solve the problem?
This is one of the reasons that it's generally not a good idea to call overrideable methods from constructors, exactly this kind of interaction is problematic.
The issue isn't that value2 doesn't get set (as you've shown with your console.log, it does). The problem is that that value gets overwritten with undefined because of the public field declaration. Your
value2;
declaration in the class construct is functionally equivalent to:
value2 = undefined;
That initialization is processed immediately after the call to super in B's constructor, overwriting the value setUp2 put there during the super call.
The solution is not to call overrideable methods from constructors. :-) Instead, do your setup in the constructor, or in a private method (if your environment supports them), or in a utility function you use from the constructor (if you need this logic in multiple places in the class).
So, the simple way is to use initializers:
class A {
value = 1;
}
class B extends A {
value2 = 5;
}
let b = new B();
console.log(b.value); // 1
console.log(b.value2); // 5
...or put that initialization logic in the constructor explicitly, which is what constructors are for:
class A {
value;
constructor() {
this.value = 1;
}
}
class B extends A {
value2;
constructor() {
super();
this.value2 = 5;
}
}
let b = new B();
console.log(b.value); // 1
console.log(b.value2); // 5
But if you need that logic in a function because you use it elsewhere, here's an example of making setUp a private method in each class (again, this assumes you need that functionality in a method because you're going to use it outside the constructor as well as inside):
// >>>>> THIS ONLY WORKS IN ENVIRONMENTS WITH PRIVATE METHODS
// (such as recent versions of Chromium, Chrome, and Brave)
class A {
value;
constructor() {
this.#setUp();
}
#setUp() {
this.value = 1;
}
}
class B extends A {
value2;
constructor() {
super();
this.#setUp();
}
#setUp() {
this.value2 = 5;
}
}
let b = new B();
console.log(b.value); // 1
console.log(b.value2); // 5
If you also need to expose setUp as a public method, you can do that by having it call the private version. Only call the private one from the constructor, though:
// >>>>> THIS ONLY WORKS IN ENVIRONMENTS WITH PRIVATE METHODS
// (such as recent versions of Chromium, Chrome, and Brave)
class A {
value;
constructor() {
this.#internalSetUp();
}
setUp() {
this.#internalSetUp();
}
#internalSetUp() {
this.value = 1;
}
}
class B extends A {
value2;
constructor() {
super();
this.#internalSetUp();
}
setUp() {
super.setUp();
this.#internalSetUp();
}
#internalSetUp() {
this.value2 = 5;
}
}
let b = new B();
console.log(b.value); // 1
console.log(b.value2); // 5

Force the use of setters instead of straight assignments in a JS ES6 Class

I have a CameraBuilder class that looks like this:
class CameraBuilder {
constructor() {
if (arguments.length) {
throw new Error('[CameraBuilder constructor ERROR] class constructor does not accept parameters.');
}
this.camera = {};
}
withFarmLabel(farmLabel) {
this.camera.farm_label = farmLabel;
return this;
}
// more methods here
build() {
const missingProps = [];
if (!this.camera.farm_label) {
missingProps.push('\nMissing farm_label property. Use the withFarmLabel method in order to assign it.');
}
// more validations like the one above here
if (missingProps.length) {
const errorMsg = missingProps.join('');
throw new Error(`[CameraBuilder build ERROR] ${errorMsg}`);
}
return this.camera;
}
}
Since most of my validations are on the build() method and there are some business logic on some of these methods associated with how the user is building an instance of CameraBuilder, I wouldn't want anyone assigning cameraBuilderObj.camera directly. Is there any way I can enforce the use of the Class methods in order to assign properties to the Camera object?
You could make the camera property private by putting # in front of it, ensuring that only CameraBuilder's internals can reference it:
class CameraBuilder {
#camera = {};
constructor() {
if (arguments.length) {
throw new Error('[CameraBuilder constructor ERROR] class constructor does not accept parameters.');
}
}
withFarmLabel(farmLabel) {
this.#camera.farm_label = farmLabel;
return this;
}
// more methods here
build() {
const missingProps = [];
if (!this.#camera.farm_label) {
missingProps.push('\nMissing farm_label property. Use the withFarmLabel method in order to assign it.');
}
// more validations like the one above here
if (missingProps.length) {
const errorMsg = missingProps.join('');
throw new Error(`[CameraBuilder build ERROR] ${errorMsg}`);
}
return this.#camera;
}
}
const c = new CameraBuilder();
c.withFarmLabel('label');
console.log(c.camera);
console.log(c.build().farm_label);
CertainPerformance's answer probably makes more sense--don't expose it in the first place--but if for some reason you didn't want to go that route (or if you're in an environment where private fields aren't supported) you could define setters on it, so that direct assignments go through your function.
class Foo {
constructor () {
this._bar = 'baz';
}
set bar (value) {
this._bar = value;
console.log('do whatever you want to do here.');
}
}
const f = new Foo();
f.bar = 'hey'; // direct assignment invokes the setter

Instantiating a class object with window[string]() [duplicate]

This question already has answers here:
Javascript ES6 class definition not accessible in window global
(2 answers)
Closed 3 years ago.
class Unit {
constructor(){
}
}
var str = "Unit";
var a = new window[str](); // error
var b = new window["Unit"](); // error
var u = new Unit(); // works
(u instanceof Unit) // true
I only recently made the switch to ES 6 syntax in regards to declaring classes. Im pretty sure that formerly i could instantiate a class like this, however ever since i have used "class" syntax, the instantiation of an object by windowclassName is not working anymore.
What exactly am i missing here ?
Variables declared with class behave similarly to those declared with const and let - they do not get implicitly assigned to the window object (which is arguably a good thing). If you wanted to put Unit on window, you would have to do so explicitly:
class Unit {
constructor(){
console.log('constructing');
}
}
window.Unit = Unit;
var str = "Unit";
var a = new window[str]();
You might consider using your own object of classes to avoid global pollution:
const myClasses = {
Unit: class Unit {
constructor(){
console.log('constructing');
}
}
};
var str = "Unit";
var a = new myClasses[str]();
Object values cannot reference each other while in the process of declaring an object literal - to put a subclass on myClasses that extends one of the existing classes, you'll have to do so outside of the myClasses declaration:
const myClasses = {
Unit: class Unit {
constructor(){
console.log('constructing');
}
}
};
myClasses.Child = class Child extends myClasses.Unit {
constructor() {
super();
console.log('Child running');
}
}
var str = "Child";
var a = new myClasses[str]();

ES6 Classes - Updating Static Properties

I am trying to figure out alternative ways to set a static (or class) property an ES6 Class and then change it after new instances of the class are created.
For example, lets say I have a class called Geo, and I need a static property called all that will give me the array of all instances of the Geo class.
This version works:
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
}
Geo.all = [];
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 2
I would prefer to not set the property OUTSIDE of the class definition though. I've tried a few things but can't seem to create a static property within the class that I can update from the constructor.
I should also mention I need to be able to do this in the browser (Chrome) without use of Babel or similar.
Here are examples of some things I've tried:
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
static get all() {
return [];
}
}
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 0
And another
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
static all = [];
}
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => error unexpected "="
There's no such thing as static all = [] in ES6. Class instance and static fields are currently stage 3 proposals which can be used via a transpiler, e.g. Babel. There's already existing implementation in TypeScript that may be incompatible with these proposals in some way, yet static all = [] is valid in TS and ES.Next.
Geo.all = [];
is valid and preferable way to do this in ES6. The alternative is getter/setter pair - or only a getter for read-only property:
class Geo {
static get all() {
if (!this._all)
this._all = [];
return this._all;
}
constructor() { ... }
}
Tracking instances in static property can't generally be considered a good pattern and will lead to uncontrollable memory consumption and leaks (as it was mentioned in comments).
This works for me for static properties.
class NeoGeo {
constructor() {
}
static get topScore () {
if (NeoGeo._topScore===undefined) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
static set topScore (value) {
NeoGeo._topScore = value;
}
}
And your example:
class NeoGeo {
constructor() {
NeoGeo.addInstance(this);
console.log("instance count:" + NeoGeo.all.length);
}
static get all () {
if (NeoGeo._all===undefined) {
NeoGeo._all = [];
}
return NeoGeo._all;
}
static set all (value) {
NeoGeo._all = value;
}
static addInstance(instance) {
// add only if not already added
if (NeoGeo.all.indexOf(instance)==-1) {
NeoGeo.all.push(instance);
}
}
}
Note: In the getter you could also check for the existence of the property using the in keyword or the hasOwnProperty keyword.
static get topScore () {
if (!("_topScore" in NeoGeo)) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
And using hasOwnProperty:
static get topScore () {
if (NeoGeo.hasOwnProperty("_topScore")==false) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
I recently had a similar issue of creating static classes.
I first tried it with constant class variables, but Chrome debugger threw an error.
So I defined the class variables 'static', also the getter methods.
Worked in Chrome.
class TestClass {
//static properties.
static _prop1 = [ 'A', 'B', 'C'];
static _prop2 = true;
static _prop3 = 'some String';
//constructor. Commented out because the class only has static elements.
//constructor () {}
//Getters.
static get prop1 () {
return this._prop1;
}
static get prop2 () {
return this._prop2;
}
static get prop3 () {
return this._prop3;
}
}
The only way to properly add a getter is to extend the class and use that extended class.
class Basic {
get firstGetter() {
return 'firstGetter'
}
}
class ExtendedClass extends Basic {
get firstGetter() {
return 'updatedFirstGetter'
}
}
}
Update your node to the version 12 or up and that's it ;)

Categories