0

I would like to instantiate classes without calling new by using Object.create (which is made for it), but how can I get all properties defined aswell?

class Vec2 {
  x = 0;
  y = 0;
}
a = new Vec2;
b = Object.create(Vec2.prototype);

console.log(a.x, a.y);
console.log(b.x, b.y);

a.x and a.y exist, but b.x and b.y do not.

Appendum for Bergi comments:

[1]

function Vec1() {
  this.x = 0;
}
b = Object.create(Vec1.prototype);
Vec1.apply(b);

[2]

class Vec3 {
  x = console.log("x = ...");
  constructor() {
    console.log("constructor");
  }
  y = console.log("y = ...");
}
vec3 = new Vec3;
kungfooman
  • 4,473
  • 1
  • 44
  • 33
  • @JackBashford With all respect, but that question is completely different and Bergis answer doesn't answer ANYTHING regarding this question. You clearly need to learn what `Object.create` does in the first place. – kungfooman Jun 16 '22 at 10:43
  • 1
    Sorry kungfooman, I completely misread that part of the question. https://stackoverflow.com/q/4166616/10221765 might be more helpful to you. I mis-interpreted the Object.create as something very different *facepalm* - sorry! – Jack Bashford Jun 16 '22 at 10:46
  • 1
    It's the constructor that creates those properties, so, you need to call the constructor. You can do that by... well... using `new`. Do you have any reason for not using it? – FZs Jun 16 '22 at 10:56
  • @FZs Since JS is/shall-be a prototypal language one cannot argue anymore that `class` is just "sugar" if the old/prototypal way of using JS doesn't catch up to full functionality anymore. That doesn't answer your question, but I simply need this for my own TypeScript->ES5-ish compiler. Something like a `Vec2.prototype.propertyInitialisations` method. – kungfooman Jun 16 '22 at 11:11
  • @kungfooman ES6+ `class` syntax is a bit [more than syntactic sugar](https://stackoverflow.com/a/48036928/1048572) (but still prototypal inheritance). You should read the spec before writing your own transpiler. – Bergi Jun 16 '22 at 12:57
  • @kungfooman How does your own compiler compile the class field initialisers into ES5-ish? It could easily provide an `__initialiseFields()` method if you wanted. Also it's still not clear why your ES5-ish output would need to avoid `new`. – Bergi Jun 16 '22 at 14:13
  • @Bergi My current transpiler also produces ES6 classes, but with two rewrites: 1) `class A extends B` becomes `class A` and later the prototype chain is "repaired": `B.prototype.__proto__ = A.prototype;` - because extending from classes that don't exist yet is a circular-dependency-PITA in ES6 2) The real constructor just becomes a forwarding-placeholder because of the `Vec2.apply is not a constructor` limitation like `constructor() { this.__constructorTS(...arguments); }`. – kungfooman Jun 16 '22 at 15:10
  • @Bergi I don't need to avoid `new`, but with these two rewrites I still lack the properties from extended classes. I just hoped there is a nice way like `Vec2.apply` but for properties. Thank you for all your input, for my particular problem the only solution seems to be to collect the properties and put them into `__initialiseFields()` (and call it in the `this.__constructorTS` method). – kungfooman Jun 16 '22 at 15:14
  • 1
    "*`class A extends B` becomes `class A` and later the prototype chain is "repaired"*" - please don't do that. Notice it also destroys `super` calls. Also it doesn't really fix the circular-dependency problem, it still doesn't work if `A` doesn't exist when you "repair" `B.prototype`. Fix the dependency problem by declaring the classes in the correct order, point. (And if you really need to choose the "repairing" approach, at least transpile to `class B extends noop {…} Object.setPrototypeOf(B, A); Object.setPrototypeOf(B.prototype, A.prototype);`) – Bergi Jun 16 '22 at 15:20
  • @Bergi Good points, for the `super` calls I have a bunch of other rewrites (constructor/methods/getters/setters). The prototype chains are repaired when every `import` got handled, so far this approach works quite well. I consider your `Object.setPrototypeOf`, thank you! – kungfooman Jun 16 '22 at 15:29
  • Yeah, then you won't need to rewrite `super` and `new` calls. However if you're rewriting stuff, I recommend to rather rewrite all the `import` declarations so that they appear in the correct order (always importing the module with parent class first, before importing any of the modules with the child classes) - then you won't have problems with `class B extends A` anywhere. – Bergi Jun 16 '22 at 15:44

1 Answers1

1

I would like to instantiate classes without calling new by using Object.create (which is made for it)

No, that's not what Object.create is made for. Its purpose is to create objects with a custom prototype object - a very useful low-level functionality.

To instantiate a class, and in particular to run its constructor code, you must use new, there's no way around it.

Of course you can ignore to do that, and just create your own objects with the same prototype chain, nothing to stop you there:

class Vec2 {
  x = 0;
  y = 0;
  print() {
    console.log(`(${this.x}, ${this.y})`);
  }
}
const a = new Vec2();
a.print(); // (0, 0)
const b = Object.create(Vec2.prototype);
b.x = 1;
b.y = 1;
b.z = 1;
b.print(); // (1, 1)
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • In ES5 you have/had the freedom of simply [1], so **must use** is a bit of an overstatement, since every function was basically considered to be used as a `class`. I need something like that, but it doesn't work anymore with mixed ES6 classes (Class constructor Vec2 cannot be invoked without 'new' - error). So basically what I seek is a replacement for `Vec1.apply(b)` - even if it just handles the property initialisation around the constructor [2]. We both agree that ES6 classes are not just sugar, the problem is that this extra sugar is incompatible with the old prototypal ways. – kungfooman Jun 16 '22 at 14:04
  • I added appendum code blocks to the question to illustrate what I mean, labeled [1] and [2]. Thank you for your feedback, it is highly appreciated! – kungfooman Jun 16 '22 at 14:06
  • Yes, with a `function` you could (and still can) use `apply`, but for ES6 `class`es you *must use* `new` (or `Reflect.construct`), not an overstatement. There is no replacement for `apply`, once you *opt-in* to `class` syntax you must use it (with `new`) everywhere. It is not incompatible with the old ways, the old ways are incompatible with the new syntax - not a problem. You can of course transpile everything into the old syntax. – Bergi Jun 16 '22 at 14:10
  • Yea, isn't it funny, "once you opted in, unsubscribe to your ES5 code base or you better transpile ES6 back to ES5 anyway". – kungfooman Jun 16 '22 at 14:22
  • 1
    @kungfooman I don't see the problem. The ES5 code base still works? You just cant write new ES5 subclasses for new ES6 classes, and you cannot migrate parent classes to ES6 without also migrating their subclasses. Which shouldn't be a huge problem if you wrote conventional ES5 classes - and if you didn't, why would you even want to switch to ES6 `class` syntax? – Bergi Jun 16 '22 at 14:41