Control variables and method created while a program is running

Another weird question ! lol

In java, it is impossible to create a variable or a method outside the class declaration.

But in Javascript, it is quite possible. I just spent a lot of time looking for the origin of a very strange bug and ended up realizing… that I had misspelled the name of a variable somewhere in my code (apart from a class declaration), which, instead of updating this variable, was creating a new one with the wrong name in the concerned classes.

So, I wonder if there is a way in Javascript to detect the creation of new variables (or methods) in a class during the operation of the program and how to do it?

Maybe we could list the methods and variables present in the constructor and check during the execution of the program that there are no new ones?

something which could be applied in all constructors and be undone (when debugging is finished)

Is it possible?
What do you think about it?

:slight_smile:

In Java requires all variables have to be declared before assigning a value to it so in Java we have

int n; // declaration
n = 42;// assignment 

in JavaScript we don’t need to declare the variable so

n = 42;

is perfectly acceptable and will assign the value 42 to the variable n. If the variable doesn’t exist then it will create it.

It is a similar when creating classes if a method has the statement

this.n = 42;

will create a class field n even if it doesn’t exist.

So a few typos in naming your variables can create a multitude of logical errors that will melt the brain trying to fix.

A logical error is one that

  • does not generate a compiler / interpreter warning, or
  • does not causing the program to halt unexpectedly

Logical errors are the hardest to find and fix :frowning:

The solution is not to find the unexpectedly created variables at runtime but prevent them in the first place.

You might try strict mode to help prevent this.

For large projects that make extensive use of classes I would recommend Typescript, I used this when creating the canvasGUI library.

2 Likes

Inside a class block, all code is already in “strict mode”:

Meaning all variables have to be declared before usage.

Moreover inside a class, we can’t declare global variables anyways.
Therefore, if we mistype a global variable name inside a class, the code should just crash!

For regular JS files, always place “use strict” as their 1st statement, so as to force variables to be declared 1st.

2 Likes

thanks.

I use strict mode already everywhere.

I had to modify the value of an existing variable in a series of instances of a class (so outside the declaration of the class) . By making a mistake in the name of the variable, I think I created a new variable in these classes instead of modifying its value.

Sorry,

I realize that I wrote my message too quickly and that I was not very clear!

yes, I took 2 hours to realize that I had forgotten a letter in the name of this variable!

Reflecting, I tell myself that the solution to this problem would be to declare more variables as private (marked with a _ now and preceded by a #later cf Classes - JavaScript | MDN ) because if we make a mistake in writing a function (a “get” or a “set”) we will inevitably have an error.

… but it makes class writing more cumbersome!

I believe you’re confusing variables w/ object properties (A.K.A. fields).

Strict mode only forces variables to be declared 1st; but no effect on object properties!
For those, you can call Object.preventExtensions() over each object you instantiate:

Let’s say you have this JS class:

class MyClass {
  deer = 10;
  plum = 20;
}

Then you instantiate it and attempt to change its properties, accidentally mistyping their names:

const instance = new MyClass;

instance.dear = 100; // "dear" instead of "deer"
instance.plumb = 200; // "plumb" instead of just "plum"

console.table(instance);

You might expect this output:

┌───────┬────────┐
│ (idx) │ Values │
├───────┼────────┤
│ deer  │ 100    │
│ plum  │ 200    │
└───────┴────────┘

But you end up w/ this 1 instead:

┌───────┬────────┐
│ (idx) │ Values │
├───────┼────────┤
│ deer  │ 10     │
│ plum  │ 20     │
│ dear  │ 100    │
│ plumb │ 200    │
└───────┴────────┘

B/c of 1 wrong letter for each those properties, 2 new properties got created rather than updating the values of the original properties!

Now, if we apply Object.preventExtensions() over instances of class MyClass, the code would crash immediately rather than continue on unintentional behavior:

"use strict";

class MyClass {
  deer = 10;
  plum = 20;
}

const instance = Object.preventExtensions(new MyClass);

instance.deer = -10; // ALLOWED! Property name correctly typed!

instance.dear = 100; // CRASHES right here!!! No new props allowed!
instance.plumb = 200; // Same for "plumb" prop.

console.table(instance);

P.S.: Seems like it won’t crash if code is pasted in a browser console or in the NodeJS REPL.
Instead, any new properties would be silently ignored!
However, it will still crash on both Deno & Bun REPL though!

2 Likes

If you decide to use Object.preventExtensions() as a way to protect yourself from accidentally add new properties on-the-fly for instances of your classes, it’s way better to call that right inside the constructor() as its last statement rather than repeating it for each single instantiation:

"use strict";

class MyClass {
  deer = 10;
  plum = 20;

  constructor() {
    // Add any extra class properties below before sealing the instance!

    // ...

    Object.preventExtensions(this); // No new props are allowed from now on!
  }
}

const instance = new MyClass; // Instances are already non-extensible!

instance.deer = -10; // ALLOWED! Property name correctly typed!

instance.dear = 100; // CRASHES right here!!! No new props allowed!
instance.plumb = 200; // Same for mistyped "plumb" prop.

console.table(instance);
3 Likes

Thank you very much!!!
I was not aware of this possibility at all, I am really happy that it exists (Javascript is increasing in my esteem ->I’m joking<-)

Yes but… :woozy_face:

How does it work in classes that inherit from other classes?
What I mean is that since we often invoke the parent class constructor (with super() ) in the first instruction of the new constructor, can we add new fields since Object.preventExtensions(this); is used on the last line of the parent class constructor?

class A {
   constructor() {
      (...);
      Object.preventExtensions(this);
   }
...
}

class B extends A {
   constructor() {
      super();
      ...
      this.NewField = something; // is there an error here ? I guess there is !!! 
                                 // So, I'm stuck again !!! 
      ...
      
      Object.preventExtensions(this); // does it work here ?
   }
}

:face_with_spiral_eyes:

P.S. I’m going to try tomorrow ( I must really go to sleep now !)

P.S. I suppose I’m going to try something with an if / the actual class of the instance…

Uh no, there’s no confusion about that, it’s just a question of vocabulary. In French, we also talk about variables within classes or to be more precise, about instances variables (as we speak more of methods rather than properties for functions within classes, with Java at least). As these are the same words in French and English (variables, méthodes, propriétés : properties…) I tended to talk about variables (in classes too).
So, I have noted that I should rather talk about properties, fields, or attributes according to my research (mainly there : Object-Oriented Programming Glossary: Key Terms Explained)

I don’t have the opportunity to speak in English with other people : just to read, and see movies in English. Inevitably, I am not absolutely aware of all the nuances of English which for me is a foreign language.

Above all, don’t hesitate to give me details, I really like having the opportunity to improve.

:grin:

Well, it’s foreign to me too, b/c my mother tongue is Portuguese.

Indeed in general abstract terms, object properties are variables, methods are functions and classes are constructor functions.

But the way they’re created and how they interact w/ their object demand a more pedantic terminology.

In JS, there are at least 6 keywords which can be used to declare a variable:

  • var, let, const, function, class, import.

However, none of them can be used to create an object property!

Instead, the default way for it is using the dot . or the brackets [] operator followed by the equal = operator:

  • instance.deer = "animal"; // creating or updating prop. "deer"
  • instance["plum"] = "fruit"; // creating or updating prop. "plum"

If a property doesn’t exist in the object yet, it’s created; otherwise, its value is updated.

It’s pretty much like a dictionary or hashmap container, where each value is associated to a key string name.

BtW, the JSON file format is based on JS objects w/ their { key property: value } pair!

Same applies to methods; which are functions that act upon the properties/fields of an object.

In your 1st post, b/c it didn’t have an example code to infer from, I had to rely on your words only and, naïvely thought you were talking about regular variables.

Only on your follow-up I’ve finally figured out it was all about object properties instead!

3 Likes

You’re gonna need to delegate to the “parent” class the responsibility to add properties of its subclasses as 1 of its constructor()'s parameters.

Before using Object.preventExtensions(this);, the “parent” class needs to call Object.assign() in order to append properties coming from its “children”, b/c they can’t add properties by themselves due to preventExtensions():

"use strict";

class A {
  constructor(subclassProps) {
    // (...);

    // Append subclass properties to this parent class instance:
    subclassProps && Object.assign(this, subclassProps);

    // Now seal the new created object so no more props. can be added anymore:
    Object.preventExtensions(this);
  }
}

class B extends A {
  constructor(animal, food) {
    // Rather than having subclass B creating its own properties:
    // this.animal = animal; this.food = food; // Props here would CRASH!

    // Call parent class passing an object containing this subclass own props:
    super({ animal, food });

    // (...);
  }
}

// Arguments "penguin" & "fish" will become B's properties "animal" & "food"
// respectively; even though parent class A is responsible to append them:
const b = new B("penguin", "fish");

console.table(b);

┌────────┬───────────┐
│ (idx)  │ Values    │
├────────┼───────────┤
│ animal │ "penguin" │
│ food   │ "fish"    │
└────────┴───────────┘
3 Likes

Although this works in Javascript and it does answer the OPs question it goes against the principles of OOP (Object Orientated Programming).

In true OOP a class is autonomous which means it is responsible for its own fields / attributes / properties.

When we use inheritance then the parent class

  1. should have no knowledge of any child classes
  2. should not be modifiable by the child class.

Consider a class A that has two child classes B and C. Both of the child classes are dependent on A to work properly, if we allow B to delete, add, change the fields / attributes / properties of A then objects of type C might fail to work or behave in unexpected ways. :frowning:

Alternatively, what if A is part of a third party library and we create child classes B and C to enable additional functionality then it would be a disaster if we could modify class A because the whole library might fail. :frowning:

Just because Javascript allows us to break these principles that does not mean we have to do it. :smile:

In a well designed object hierarchy there should never, ever be the need for a child class to modify the structure of its parent. :smiley:

2 Likes

Well, maybe we could do this

class Parent {
   constructor(preventExtension=true) { //+ (boolean)
      (...)
      If (preventExtension) Object.preventExtensions(this);
}

class Child extends Parent {
   constructor(preventExtension=true) {
      super(false);
      (...)
      If (preventExtension) Object.preventExtensions(this);
}

const parentInstance = new Parent();
const childInstance = new Child();
1 Like

Although my workaround rigidly couples both constructors, it violates no OOP principles — it’s pure delegation via inheritance.

Normally, subclass B would set this.animal = animal; this.food = food; post-super() if was not for preventExtensions() being in effect.

However, merely forwarding both this.animal & this.food (or any other properties) to its parent class via super({ animal, food }); doesn’t violate any OOP principles for the following reasons:

  1. The forwarding object is received as a constructor() parameter (expected inheritance flow).
  2. Parent class neither change, store, nor even inspect that parameter.
  3. It blindly passes that parameter intact to Object.assign(), which just appends it to this keyword (encapsulation intact).
  4. In the end the result is the same: subclass B’s own parameters this.animal & this.food are appended to keyword this inside A’s constructor(); but A is completely oblivious about those B’s properties.

I’ve already proved parent class A is oblivious to whatever properties subclass B intends to append to the shared object being created.

On #2: Subclasses inherit all non-private members, gaining full right over that heritage!

Therefore, a subclass also owns those members and have the right to modify them too, as long as it follows its parent’s expected behavior!

Regardless in my example, subclass B isn’t touching anything from A.
Actually, A doesn’t even have any fields to be inherited from!

But even if A had any public fields, B would have the right to fully use them as well!

AI Summary

  1. Obliviousness: A is generic; B defines the specific data.
  2. Control: A enforces the “Seal” (protecting the object structure).
  3. Rights: B uses A’s mechanism (super({})) to rightfully populate its own state before the seal is applied.
2 Likes

As I wish (one day) my project for a small game engine and graphical interfaces would be useful to other people and that its code would not be TOO difficult to understand, I must take care not to use really complicated things (even if they respond well to a given problem) and if possible, not too dependent on the technology specific to Javascript so that it is conceivable to rewrite it in another language (like Processing for example) without it being too difficult…

Do you think that my proposal above addresses the problem?
:slight_smile:

1 Like

I’ve tested it. It works !

Thanks very much for all your answers !!!

All are very interesting to help me better understand Javascript !

:grinning_face_with_smiling_eyes:

That was a very smart trick to chain-conditionally postpone callingObject.preventExtensions(this); to the very latest child class! :light_bulb:

The drawback is you’d have to explain that parameter preventExtension isn’t supposed to receive an argument when some1 is instantiating the class on their own. :face_with_spiral_eyes:

Maybe rename it to _hidden_param_no_seal so any1 using your classes would know it should be left alone for internal usage only. :no_pedestrians:

I have an even better solution which skips the constructor()'s parameter preventExtension engine entirely: :flushed_face:

Instead, we’d have a static protected method defined inside the parent class:

#!/usr/bin/env node

// @ts-check

"use strict";

class A {
  /**
   * Creates a new being with the given name.
   *
   * @param {string} name - the name for the being
   */
  constructor(name) {
    /** the name for the being */ this.name = name;

    A._sealInstance(this); // Seal it only if this instance is exactly type A!
  }

  /**
   * Seals `instance` if its *constructor* is the invoking class itself.
   * Prevents further property additions after constructor completes.
   *
   * @protected
   *
   * @param {A} instance - object to be conditionally sealed
   *
   * @returns {boolean} whether `instance` got sealed or not 
   */
  static _sealInstance(instance) {
    const isBottomClass = instance.constructor == this;
    isBottomClass && Object.preventExtensions(instance);
    return isBottomClass;
  }
}

class B extends A {
  /**
   * Creates a new animal with name, type and favorite food.
   *
   * @param {string} name - the name for the animal
   * @param {string} animal - kind of animal
   * @param {string} food - favorite food of the animal
   */
  constructor(name, animal, food) {
    super(name);

    // Redundant re-assignment to change prop name's IntelliSense description:
    /** the name for the animal */ this.name = this.name;

    /** kind of animal */ this.animal = animal;
    /** favorite food of the animal */ this.food = food;

    B._sealInstance(this); // Seal it only if this instance is exactly type B!
  }
}

class Pet extends B {
  /**
   * Creates a new pet with name, type and favorite food.
   *
   * @param {string} name - the name for the pet
   * @param {string} animal - kind of pet
   * @param {string} food - favorite food of the pet
   */
  constructor(name, animal, food) {
    super(name, animal, food);

    Pet._sealInstance(this); // Seal it only if this instance is type Pet!
  }

  feed() {
    const { name, animal, food } = this;
    console.info(name, "the", animal, "eats the delicious", food);
  }

  pet() {
    const { name, animal } = this;
    console.info(name, "the", animal, "loves to be petted!");
  }
}

const a = new A("Thor");

try {
  a.weapon = "hammer"; // Prop 'weapon' does not exist on type 'A' ts(2339)
} catch (err) {
  console.warn("Class A is sealed & prohibits additional properties!\n", err);
}

console.log(a.name); // "Thor"
console.table(a);

const b = new B("Pengo", "penguin", "fish");

// 1. IntelliSense recognizes props "name", "animal" & "food" as strings:
console.log('\n', b.name, b.animal, b.food, '\n'); // "Pengo" "penguin" "fish"

// 2. Sealed object safety works! Property "color" is rejected by the linter:
try {
  b.color = "gray"; // Property 'color' does not exist on type 'B' ts(2339)
} catch (err) {
  console.warn("Class B is sealed & prohibits additional properties!\n", err);
}

// 3. Runtime works:
// The object is sealed, and no class fields were generated to crash it.
console.table(b);

const { name: pet, animal, food } = b;
const c = new Pet(pet, animal, food);

try {
  c.sleep = function () { // Prop 'sleep' doesn't exist on type 'Pet' ts(2339)
    const { name, animal } = this;
    console.info(name, "the", animal, "is dreaming right now!");
  };
} catch (err) {
  console.warn("Class Pet is sealed & prohibits additional props!\n", err);
}

console.table(c);
c.feed(); c.pet();

index.html

<script src=self_prevent_extensions.js></script>
2 Likes

I need to wait until I have a little time to study this thoroughly to understand! lol

(thanks)

is this line the same as :

if (isBottomClass) Object.preventExtensions(instance);

?

or is it the same with (without the return) :

 static _sealInstance(instance) {
   if (instance.constructor==this) Object.preventExtensions(instance);
  }

to see if I understand ?

it’s a better idea : no need to call super(false) ! and nothing to explain in the constructors parameters !