我如何 "recursively" 字符串化调用其他作用域函数的 javascript 函数?

How can I "recursively" stringify a javascript function which calls other scoped functions?

因为 javascript 函数不可序列化,为了有时(尽管很少)将它们传递到新的上下文中,对它们进行字符串化然后稍后重新评估它们可能很有用,例如:

const foo = () => { // do something }
const fooText = foo.toString()

// later... in new context & scope
const fooFunc = new Function(' return (' + fooText + ').apply(null, arguments)')
fooFunc() // works!

但是,如果 foo 引用另一个函数 bar,则范围未被字符串化,因此,如果 bar 未在新上下文中定义,则计算foo 函数在调用时会抛出错误。

我想知道是否有一种方法可以递归地对函数进行字符串化?

即不仅将父函数字符串化,还将从父函数调用的子函数的内容字符串化。

例如:

let bar = () => { alert(1) }
let foo = () => { bar() }

// what toString does
let fooString = foo.toString()
console.log(fooString) // "() => { bar() }"

// what we want
let recursiveFooString = foo.recursiveToString()
console.log(recursiveFooString) // "() => { alert(1) }"

如果您对如何完成类似 "recursiveToString"

的事情有任何想法,请告诉我

唯一好的方法是从包含所有函数的父作用域开始foo最终引用。例如,对于你的 foobar,如果你想将 foo 传递到另一个上下文中,这样 bar 也是可调用的,传递一个声明两个 foobar,以及 returns foo。例如:

const makeFoo = () => {
  let bar = () => { alert(1) }
  let foo = () => { bar() }
  return foo;
};
const makeFooStr = makeFoo.toString();

// ...

const makeFooFunc = new Function(' return (' + makeFooStr + ').apply(null, arguments)');
const foo = makeFooFunc();
foo();

要很好地实施这类事情确实需要像上面那样有预谋的设计(不幸的是)。字符串化时,您不能真正包含所有祖先 LexicalEnvironments(变量名到给定范围内值的内部映射)。

I'm wondering if there is a way to stringify a function recursively?

我认为我们可以相当简单地证明这通常是不可能的。

我们来思考一下这两个函数

const greet = (greeting) => (name) => `${greeting} ${name}`
const sayHi = greet ('Hi') 

sayHi ('Jane') //=> "Hi Jane"

在您的 foobar 示例中,我们可以想象一些检查函数主体并使用当前范围内可用的所有内容来执行基于解析的扩展 stringify 函数函数并知道实际使用了哪些局部变量。 (我猜这也是不可能的,原因与Rice's Theorem有关,但我们当然可以想象。)

但是在这里,请注意

sayHi.toString() //=> "(name) => `${greeting} ${name}`"

所以 sayHi 依赖于一个未存储在我们当前范围内的自由变量 greeting。我们只是没有将用于创建函数 的 "Hi" 存储在任何地方 除了 sayHi 的闭包范围之外,它不会在任何地方暴露。

所以即使是这个简单的函数也不能可靠地序列化;似乎对更复杂的事情没有希望。

我最终滚动的内容受到@CertainPerformance 的回答的启发。

诀窍是构建一个定义所有子被调用函数的函数。然后你就拥有了对父函数进行字符串化所需的一切。

注意: 为了允许从其他文件导入被调用者函数,我决定以编程方式构建一个带有被调用者定义的字符串,而不是最初在同一范围内定义它们。

代码:

    // original function definitions (could be in another file)
    let bar = () => { alert(1) }
    let foo = () => { bar() }


    const allCallees = [ bar, foo ] 

    // build string of callee definitions
    const calleeDefinitions = allCallees.reduce(
      (definitionsString, callee) => {
        return `${definitionsString} \n const ${callee.name} = ${callee.toString()};`;
      }, 
      "",
    );

    // wrap the definitions in a function that calls foo
    const fooString = `() => { ${calleeDefinitions} \n return foo(); \n }`;

    console.log(fooString);
    /** 
     * fooString looks like this:
     * `() => {  
     *    const bar = () => { alert(1) }; 
     *    const foo = () => { bar() }; 
     *    return foo();
     *  }`
    **/ 
     

    // in new context & scope
    const evaluatedFoo = new Function(' return (' + fooString + ').apply(null, arguments)');

    // works as expected
    evaluatedFoo();