4

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 
3
  • 1
    See this Running Dynamic Javascript code Commented Jul 31, 2020 at 21:15
  • 1
    I tried a few approaches from that arena, but it seems like eval doesn't put the class in the global scope or have it persist outside of the eval lifetime. Similar to my comment on @Mureinik answer, when eval runs it seems the class definition is available in that context, but once eval exists the definition is disposed. I need it to ba available after the creating code is done. Commented Jul 31, 2020 at 21:39
  • Is this of any use? stackoverflow.com/questions/1366127/… Commented Jul 31, 2020 at 21:50

2 Answers 2

3

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!
Sign up to request clarification or add additional context in comments.

2 Comments

That's much cleaner, but what I really need to do is create the class itself not just instances. I am creating instances using Reflect.construct(classDef, []) in many places.
@RandyBuchholz If you want the class itself, I think it's even simpler - see my edit above
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.