Randy Buchholz
Randy Buchholz

Reputation: 253

Create Class Definition from String

I'm trying to create a ES class definition from a string.

const def = "class M {}";
// ???
const inst = new M();

I'm not in Window, so I can't use DOM based approaches like putting it in script tags. I've tried a few approaches with function() and eval() but no real success.

The closest I've come is a pretty ugly factory approach.

const M = new Function('return new class M { test; constructor(){ this.test = "Hello"; } tfun(input){ return input + 7; } }');

const inst = new M();
inst.test; // Hello
inst.tfun(5); // output: 12

This doesn't call the constructor with params though.

const M = new Function('return new class M { test; constructor(param){ this.test = param; } tfun(input){ return input + 7; } }');

const inst = new M("Hello");
inst.test; // undefined 

Upvotes: 4

Views: 303

Answers (2)

takaturre
takaturre

Reputation: 126

Actually your original code was very close to working, and I prefer it to using eval. If you just wrap the new Function("return class M { ... }") call into ( ... )(), it works. (I guess otherwise we just have a function ready to return a class vs. executing the func to return the class.)

const M = (new Function('return class M { test; constructor(param){ this.test = param; } tfun(input){ return input + 7; } }'))();
const inst = new M("Hello");
inst.test; // "Hello"

A side point: The test; part could be dropped (in JS) since test is set in the constructor.

So given all this, we can write a general "createClass" function:

// Class creator function.
// .. The namedArgs become as if globals for the class (= parenting func params).
// .. The className can include extends, eg. "MyClass extends OtherClass".
//
function createClass(className, classBody, namedArgs) {
    const fullCode = "return class " + className + " { " + classBody + " }";
    return namedArgs ? 
        // With args.
        (new Function(...Object.keys(namedArgs), fullCode))(...Object.values(namedArgs)) : 
        // No args.
        (new Function(fullCode))();
}

// Test run.
let MyClass = createClass(
    "MyClass",
    "testArgs() { return Context.test; }",
    { Context: { test: true }}
);
let myClass = new MyClass();
myClass.testArgs(); // Outputs: true

Upvotes: 0

Mureinik
Mureinik

Reputation: 311978

One way to achieve this is to add the text that instantiates the class to the string and only then eval it:

const def = 'class M {}';
const instantiator = def + '; new M();';

const m = eval(instantiator);

EDIT:
To follow-up on the comment, if you want the class itself, it's even simpler - just add its name to the string you're evaluating:

const def = 'class M {}';
const statement = def + '; M;';

const M = eval(statement);
const m = new M(); // This now works!

Upvotes: 3

Related Questions