Class and Property Initialization

# Brian Barnes (9 years ago)

I know properties on classes are getting a look over for the next iteration (last I checked) and I understand javascript is obviously a different language then other oo languages with a different foundation, but I bring this up for it's usage in producing stricter code that reduces errors and is easier to analyze. A vote for this if anybody considered it!

class Test {

constructor() { this.x=1; }

func1() { this.y=2; }

func2() { console.log(this.x+','+this.y); } }

var test1=new Test(); test1.func1(); test2.func2(); // outputs 1,2

var test2=new Test(); test2.func(); // outputs 1,undefined

I know classes contents are meant to be in strict mode, and I thinking that only allowing properties to be created in the constructor (or eventually static properties on the class itself) would make a system less prone to the conditions like you see above. Basically, func1() would produce a error when run.

I can see why this type of initialization of properties could be desired, though, especially as it reflect the way it would have worked if you used a function instead of a class.

[>] Brian

# Sébastien Doeraene (9 years ago)

The Strong Mode experiment was canceled: groups.google.com/forum/#!topic/strengthen-js/ojj3TDxbHpQ

# Michael Theriot (9 years ago)

You can accomplish this by calling Object.seal(this) in the constructor.

constructor() {
  this.x = 1;
  Object.seal(this);
}
# Brian Barnes (9 years ago)

Sealing is a good thing, and I operate everything under strict mode, anyway. kdex, I just read the strong mode proposal, and it's great. It's literally my list of things I've run into, I'll be happy to see that -- or at least parts of it -- make it into the spec and major browsers. I have no say but I still silently vote yes!

Anything that makes it easier to statically analyze instead of having to dynamically analyze it and also disallow some of the things that creates hard to track errors would be great.

My current code is enormous and it would be nice -- as it's not compiled but tested by running -- your regular development environments could better detect errors during syntax highlighting -- being able to more statically analyze the code would be a boon for our coding cycles.

[>] Brian

# Brian Barnes (9 years ago)

Ugh, well, I guess I typed a lot of stuff for nothing! And by the look of their experiment, what I wanted was actually one of the major blockers.

It seems classes will really need a more standard type of syntax (non static) before you could actually achieve this, and might be a bridge too far for javascript:

class test { let x=1; let y=2; constructor() {} func() { this.z=2; } // syntax error }

Though it could be kind of faked by some kind of reverse hoisting, i.e., rebuilding the code inside the engine:

class test { constructor() { this.x=1; this.y=2; // stuff that was in the constructor Object.seal(this); } ... }

[>] Brian

# Brian Barnes (9 years ago)

Great, glad to hear that's coming! I think sealing is a fine solution, it'll do what I want and doesn't cause fits for the engine makers.

That said, it has one problem -- base classes. You can't seal them because the constructor in the extended class would fail (I tried it) and so the base classes would always have to remain unsealed which means you either (1) understand that or (2) always use an extended class if you care for level of code safety.

[>] Brian

# kdex (9 years ago)

Already considered and to be implemented via strong mode IIRC.

# Kevin Smith (9 years ago)

That said, it has one problem -- base classes. You can't seal them because the constructor in the extended class would fail (I tried it) and so the base classes would always have to remain unsealed which means you either (1) understand that or (2) always use an extended class if you care for level of code safety.

One idea that I've been playing around with: instead of thinking about class property definitions in terms of normal object properties, what if they were sugar for a private field with a getter and setter? For instance,

class A { x = 100; constructor() { Object.seal(this) } }

is sugar for:

class A { #x = 100; get x() { return this.#x } set x(value) { this.#x = value } }

If we did it like this, then it should work even with subclasses.

# Michael Theriot (9 years ago)

Try this... It will only seal if calling new (the constructor syntax only allows you to call new as well). The subclasses will still need to seal though.

'use strict';

class Test {
  constructor() {
    this.x = 1;
    if(new.target === Test) {
      Object.seal(this);
    }
  }
}

class SubTest extends Test {
  constructor() {
    super();
    this.y = 2;
    if(new.target === SubTest) {
      Object.seal(this);
    }
  }
}

var st = new SubTest();
st.x; // 1
st.y; // 2
st.z = 3; // throws error
# kdex (9 years ago)

Note that this does prevent extensions, but will only throw in strict mode. In sloppy mode, the assignment will silently fail.

# Bradley Meck (9 years ago)

In my own code I tend to do the constructor/executor pattern:

class A {
  constructor(exec) {
    this.a = 1;
    exec(this);
    Object.freeze(this);
  }
}
class B extends A {
  constructor() {
    super(() => {
      this.b = 2;
    });
  }
}

And it works out pretty well.

# kdex (9 years ago)

Brian, Your first example isn't too far from ES2017. Leave away the lets and seal it, and you got:

"use strict";
class Test {
     x = 1;
     y = 2;
     constructor() {
         Object.seal(this);
         this.z = 3; // TypeError
     }
}

You can already use this using transpilers (at least babel supports it).

# kdex (9 years ago)

Another way that allowed you to use new on both base and derived classes would be something like this:

"use strict";
function sealInstance(bool) {
     if (bool) {
         Object.seal(this);
     }
}
class Base {
         x = 1;
         constructor(seal = true) {
                 sealInstance(seal);
         }
}
class Derived extends Base {
         y = 2;
         constructor(seal = true) {
                 super(false);
                 sealInstance(seal);
         }
}
let b = new Base(); // Base class can be instantiated with sealing
let d = new Derived(); // Derived class can be instantiated with sealing

Not a particular esthetic way, but it doesn't break new and transpiles without private fields. (It will make your constructor signatures a little ugly, though.)

# Logan Smyth (9 years ago)

Bradley, looks like you have a typo and should be using the this argument you pass to exec rather than this in the arrow?

class B extends A {
  constructor() {
    super(inst => {
      inst.b = 2;
    });
  }
}
# Raul-Sebastian Mihăilă (9 years ago)

Bradley, your example throws an error because this is uninitialised when the arrow function is called. (Checked in Firefox).