Как я могу "рекурсивно" привести в порядок функцию javascript, которая вызывает другие функции с областью действия? - PullRequest
7 голосов
/ 01 ноября 2019

Поскольку функции 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"

Ответы [ 3 ]

3 голосов
/ 01 ноября 2019

Единственный хороший способ сделать это - начать с родительской области, которая охватывает все функции, на которые в конечном итоге ссылаются foo. Например, с вашими foo и bar, если вы хотите передать foo в другой контекст, так что bar также может быть вызван, передайте функцию, которая объявляет и foo и bar, и возвращает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 (внутреннюю карту имен переменных в значения в данной области) при строковом преобразовании.

1 голос
/ 03 ноября 2019

То, чем я закончил, было вдохновлено ответом @ 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();
1 голос
/ 01 ноября 2019

Мне интересно, есть ли способ рекурсивной строковой функции?

Я думаю, мы можем просто продемонстрировать, что это вообще невозможно.

Давайте подумаем об этих двух функциях

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

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

Хотя на вашем примере foo и bar мы могли бы представить что-то, что проверило бы тело функции и использовало все доступное в текущей области, чтобырасширенная функция stringify, основанная на анализе функции и знании того, какие локальные переменные фактически используются. (Я предполагаю, что это тоже было бы невозможно по причинам, связанным с теоремой Райса , но мы, конечно, можем себе это представить.)

Но здесь обратите внимание, что

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

и так sayHi зависит от свободной переменной , которая не хранится в нашей текущей области, , а именно greeting. Мы просто не сохранили «Привет», использованный для создания этой функции в любом месте , за исключением области закрытия sayHi, которая нигде не отображается.

Так что даже эта простая функция не моглабыть надежно сериализованным;кажется, мало надежды на что-то более сложное.

...