1

I want to create three functions out of one javascript string containing the js source code.

Lets say i have the following code as one string:

"
function func1()
{
    console.log('This comes from func1')
}

function func2()
{
    console.log('This comes from func2')
}

function func3()
{
    console.log('This comes from func3')
}
"

Now i want to create these three function as javascript functions and attach them to an object, like:

object1.func1 = function func1(){...}
object1.func2 = function func2(){...}

I have a lot of objects and want to be able to attach a script file that contains these three functions func1, func2 and func3. The script file can contain other functions too, but these getting called by one of the three functions. So a script file for an object could look like as follows:

"
// Main functions of a script file
// A script file MUST contain these three functions

function func1()
{
    console.log('This comes from func1')
    userFunc1();
    // do some user code
}

function func2()
{
    console.log('This comes from func2')
    userFunc2();
    // do some user code
}

function func3()
{
    console.log('This comes from func3')
    userFunc3();
    userFunc4();
    // do some user code
}


// User functions
// these are optional

function userFunc1()
{
    // do stuff
}

function userFunc2()
{
    // do stuff
}

function userFunc3()
{
    // do stuff
}

function userFunc4()
{
    // do stuff
}

"

I have no problem creating a function when the string only contains one function, like as follows:

var jsCode = "(function func1(){console.log('This comes from func1')})";
var script=new Function (jsCode);
script();

My problem is how to parse two or more functions out of one string and create these functions accordingly?

I don't want to use any libraries, just pure javascript and I don't want to use eval as it seems to be horribly slow.

Can somebody help me with this problem?

Horst
  • 229
  • 1
  • 5
  • 15
  • 1
    Three functions - either a fiddly regex, or `eval`. – Jack Bashford May 11 '19 at 13:20
  • @JackBashford `eval` is dangerous and should never be used, as it could execute arbitrary and malicious JS code: https://stackoverflow.com/questions/18269797/what-does-eval-do-and-why-its-evil – Maxime Launois May 11 '19 at 13:22
  • If it’s a string, how about a split function? – sideroxylon May 11 '19 at 13:24
  • 3
    @MaximeLaunois Yes, it's potentially dangerous--but that doesn't mean "never". It depends entirely on the usecase. – Dave Newton May 11 '19 at 13:28
  • You need to discern the start and end of each function. [There's a common programming challenge](https://www.geeksforgeeks.org/check-for-balanced-parentheses-in-an-expression/) that solves uses a stack to verify that a function is balanced (much like a compiler would), but in this case you could iterate over the string looking for the first index of `function` and the next index of `function` (or end of string), then taking a substring between the two to split the function apart. – StoriKnow May 11 '19 at 13:33
  • Eval would't be a problem because this app only runs locally and will never be hosted on a server. I just want to be able to attach some basic js scripts to objects. Im doing this by storing the js code as string, and when the app gets loaded, it should parse the string and create these functions and attach them to the object. This only happens once when the app is loaded. This all works with one function per script, im only having problems with more than one function. But i want to be able to define more than one function per script. – Horst May 11 '19 at 13:34
  • Does the code with one function work? According to MDN [Function constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) takes the body of the function as a parameter (without the `function ()` and `{}` bit) – adiga May 11 '19 at 13:47
  • Is this in a browser context or on nodejs? – trincot May 11 '19 at 13:49
  • it is in browser context as it is pure javascript without any extensions or libs. – Horst May 11 '19 at 14:15

1 Answers1

3

When running in a browser, you can dynamically add a script element and execute it with createContextualFragment:

function executeCode(code) {
    document.head.appendChild(
        document.createRange().createContextualFragment('<script>' + code + '<\/script>')
    );
}

// Demo
const code = `
function func1()
{
    console.log('This comes from func1')
}

function func2()
{
    console.log('This comes from func2')
}

function func3()
{
    console.log('This comes from func3')
}`;

executeCode(code);

func1();
func2();
func3();

Getting functions loaded as object members

After the update of the question, the functions must be object members. In that case I would request that the provided code follows this format:

({
    func1()
    {
        console.log('This comes from func1')
    },
    func2()
    {
        console.log('This comes from func2')
    },
    func3()
    {
        console.log('This comes from func3')
    }
})

Then you can use eval to get this object expression into a variable:

const code = `
    ({
        func1()
        {
            console.log('This comes from func1')
        },
        func2()
        {
            console.log('This comes from func2')
        },
        func3()
        {
            console.log('This comes from func3')
        }
    })
`;

const obj = eval(code);

obj.func1();
obj.func2();
obj.func3();

A less intrusive change to the code format would be to make it an array expression:

const code = `
    [
        function func1()
        {
            console.log('This comes from func1')
        },
        function func2()
        {
            console.log('This comes from func2')
        },
        function func3()
        {
            console.log('This comes from func3')
        }
    ]
`;

const obj = Object.fromEntries(eval(code).map(f => [f.name, f]));

obj.func1();
obj.func2();
obj.func3();

When the code cannot be changed

If your current syntax cannot be changed, then I would suggest to do a string-replacement on it so to arrive at the above-mentioned array syntax:

const code = `
function func1()
{
    console.log('This comes from func1')
}

function func2()
{
    console.log('This comes from func2')
}

function func3()
{
    console.log('This comes from func3')
}`;

const array = "[" + code.replace(/}(\s*function\b)/g, "},$1") + "]";

const obj = Object.fromEntries(eval(array).map(f => [f.name, f]));

obj.func1();
obj.func2();
obj.func3();

Obviously, such a string replacement has some drawbacks, as the code may have nested functions, and those should not get the additional comma (although it would not always break the code). One can also imagine code with string literals that have the sequence } function in them, ...etc, ...etc.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Is this what you are looking for? – trincot May 11 '19 at 14:08
  • I need to be able to call the functions per object. Like object1.func1(), object1.func2(), therefore they have to be attached to the object. – Horst May 11 '19 at 14:29
  • Oh, I see you changed the question. – trincot May 11 '19 at 15:15
  • Sorry, i didn't consider that one could solve it the way you suggested, wich is in general a good solution, but doesn't work in my case. – Horst May 11 '19 at 15:54
  • I added some alternatives to my answer. – trincot May 11 '19 at 16:10
  • Thank you so much, these approaches are exactly what i was looking for. These work like a charm! I now use a combination of the first and the last method. I define a script without these opening and closing brackets "({" and "})" and add them right before the eval function. Defining a script with functions seperated by comma is not that much of a deal. – Horst May 11 '19 at 18:23