Control variables and method created while a program is running

Indeed they’re the same. My version uses “short-circuit evaluation”:

That is, Object.preventExtensions(instance), which is the 2nd operand of &&, is only evaluated if isBottomClass, which is the 1st operand, happens to be “truthy”:

2 Likes

That would be a more direct comparison, skipping caching it in the local variable isBottomClass.

The reason I’ve chosen to have a local variable is b/c I wanted to return it from the function, flagging whether the object sealing operation was a success.

2 Likes

Already answered!!!
How much I appreciate this forum !!!

(Still new things to learn on Javascript! Cool!)

1 Like

I wonder…

is there a way to add the static _sealInstance() method to the Object class ?

You see what I mean ! lol

  • It’s bad etiquette modifying JS built-ins.
  • Even if you do, you’d still need to use extends Object in order to inherit it.
  • instead, what about having a @protected super class Sealer containing _sealInstance()?
#!/usr/bin/env node

// @ts-check

// https://Discourse.Processing.org/t/
// control-variables-and-method-created-while-a-program-is-running/47995/26

"use strict";

class Sealer {
  /**
   * @protected
   * @throws {TypeError}
   */
  constructor() {
    if (Sealer._sealInstance(this)) throw TypeError(
      "Sealer is an abstract utility super class and cannot be instantiated!");
  }

  /**
   * Seals `instance` if its *constructor* is the invoking class itself.
   * Prevents further property additions after constructor completes.
   *
   * @protected
   *
   * @param {Sealer} 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 A extends Sealer {
  /**
   * Creates a new being with the given name.
   *
   * @param {string} name - the name for the being
   */
  constructor(name) {
    super();

    /** the name for the being */ this.name = name;

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

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();
1 Like

Yes, that’s what I thought!
I just had the same idea!

I will also take a closer look at the try/catch and Throw Errors…

Thank you
:slight_smile:

P.S. I copy/paste your post (with the link to the forum) in an annex in my project documentation !

1 Like

The throw inside Sealer’s constructor() is so no1 would accidentally attempt to directly instantiate it thinking it does anything by itself; even though it’s only to be subclassed for it’s utility static method.

The 3 try {} / catch () {} is to prove we can’t add any additional properties to neither subclasses after their instantiation.

The directive comment // @ts-check enables an IDE’s TS linter to catch those 3 disallowed property additions even before running the example btW: