2017-02-10 6 views
7

Допустим, у нас есть функция, которая выглядит следующим образом:Есть ли простой способ превращения глобального var в локальный var?

const fn =() => x; 

Эта функция должна возвращать значение x где x доступна в глобальном масштабе. Первоначально это undefined, но если мы определим x:

const x = 42; 

Тогда можно ожидать, fn вернуться 42.

Теперь предположим, что мы хотим отобразить fn в виде строки. В JavaScript для этого мы имеем toString. Однако предположим, что мы хотели в конечном итоге выполнить fn в новом контексте (то есть с использованием eval), и поэтому любые глобальные ссылки, которые он использует, должны быть интернализованы до или во время нашего вызова до toString.

Как мы можем сделать x локальной переменной, значение которой отражает глобальное значение x во время преобразования fn в строку? Предположим, что мы не можем знать x имеет имя x. При этом мы можем предположить, что переменные содержатся в одном модуле.

+0

Как 'fn.toString() => \' (... args) => {const x = $ {JSON.stringify (window.x)} ; return $ {this.toString()} (... args); } \ ';'? Или если вы действительно хотите захватить возвращаемое значение функции: 'fn.toString =() => \'() => $ {JSON.stringify (this())} \ ';'. –

+1

Сколько усилий вы хотите вложить в это? Легко, если вы знаете, какие переменные захватывать. Это также легко, если вы просто захватили вывод (если функция не принимает никаких входных данных и не имеет побочных эффектов, то этого достаточно). Если вы действительно хотите запустить функцию, фиксируя значения ее свободных переменных, но вы не знаете имя свободных переменных, вам нужно сначала найти имена. Вы можете сделать это, проанализировав строковое представление функции и используя инструмент https://github.com/estools/escope, чтобы найти свободные переменные. –

+0

@FelixKling в какой-то момент будет стоить приличного количества усилий. Пока мы можем дублировать переменные в рамках отдельных функций - это не очень хороший подход, но он делает то, что нам нужно. Очевидно, если есть лучший метод, мы бы хотели его использовать! – maxcountryman

ответ

0

Мы не можем знать, x называется x. Это центральная часть этой головоломки и поэтому выделена в исходный вопрос. Хотя было бы неплохо, если бы у нас было более простое решение, похоже, правильный ответ здесь сводится к реализации какого-то анализатора или обхода AST.

Зачем это необходимо? Хотя мы можем сделать предположение, что x живет в модуле как глобальный (он обязательно разделяется между функциями), мы не можем предположить, что оно имеет известное имя. Итак, нам нужно каким-то образом извлечь x (или все глобальные списки) из нашего модуля, а затем предоставить его в качестве контекста, когда мы в конечном итоге получим eval.

N.B .: предоставление известных переменных в качестве контекста тривиально. Несколько ответов здесь, похоже, предполагают, что это сложная проблема, но на самом деле это довольно легко сделать с eval; просто добавьте контекст в виде строки.

Итак, какой правильный ответ здесь? Если бы мы использовали AST (Acorn, может быть, например, жизнеспособной отправной точкой), мы могли бы изучить модуль и программно извлечь все глобалы в нем. Это включает x или любую другую переменную, которая может быть разделена между нашими функциями; мы можем даже проверить функции, чтобы определить, какие переменные необходимы для их выполнения.

Опять же, надежда на то, чтобы задать этот вопрос вначале, заключалась в том, чтобы отгонять более простое решение или раскрывать предшествующий уровень техники, который может быть адаптирован под наши нужды.В конечном итоге мой ответ и ответ, который я принимаю, сводятся к нетривиальной задаче разбора и извлечения глобалов из модуля JavaScript; не представляется простым способом. Я думаю, что это справедливый ответ, если не практический для нас, чтобы реализовать сегодня. (Мы, тем не менее, рассмотрим это позже по мере развития нашего проекта.)

-1

Вы имеете в виду это? только ответ может размещать код, поэтому я использую ответ

var x = 42 
 

 
function fn() { 
 
    return x 
 
} 
 

 

 
(() => { 
 
    var x = 56 
 
    var localFn = eval('"use strict";(' + fn.toString()+')') 
 
    console.log(localFn) 
 
    console.log(localFn()) 
 
})()

  • почему переименовать localFn, если вы используете var fn=xx в этой области внешняя сноска не существует!
  • in nodejs? refer nodejs vm
  • Проходящий контекст? вы не можете сохранить js-контекст, если вы не сохраните свой собственный масштаб, например angularjs
-1

Если вы уже «собираетесь туда», используя eval() для выполнения fn() в новом контексте, то почему бы не определить функцию самостоятельно используя eval()?

eval('const fn =() => ' + x + ';')

1

Если вы хотите замок некоторые переменные при преобразовании функции в строку, вы должны передать, что переменные вдоль строковой функции.

Это может быть реализовано, как это (написано с типами - машинопись обозначения)

const prepareForEval = 
    (fn: Function, variablesToLock: { [varName: string]: any }): string => { 
    const stringifiedVariables = Object.keys(variablesToLock) 
     .map(varName => `var ${varName}=${JSON.stringify(variablesToLock[varName])};`); 

    return stringifiedVariables.join("") + fn.toString(); 
    } 

Затем используйте его как этот

const stringifiedFunction = prepareForEval(someFunction, { x: x, y: y }) 

// you can even simplify declaration of object, in ES6 you simply write 
const stringifiedFunction = prepareForEval(someFunction, { x, y }) 
// all variables you write into curly braces will be stringified 
// and therefor "locked" in time you call prepareForEval() 

Любой eval объявим строковой переменные и Funtion в месте, где он был выполнен. Это может быть проблемой, вы можете переопределить какую-либо переменную в новое неизвестное значение, вы должны знать имя стробированной функции, чтобы иметь возможность вызвать ее, или она может вызвать ошибку, если вы повторно указали уже объявленную переменную const.

Чтобы преодолеть эту проблему, вы должны реализовать строковую функцию, как мгновенный выполняется анонимная функция со своей собственной сферой, как

const prepareForEval = 
    (fn: Function, variablesToLock: { [varName: string]: any }): string => { 
    const stringifiedVariables = Object.keys(variablesToLock) 
     .map(varName => `var ${varName}=${JSON.stringify(variablesToLock[varName])};`); 

    return ` 
     var ${fn.name} = (function() { 
     ${stringifiedVariables.join("")} 
     return ${fn.toString()}; 
    )(); 
    `; 
    } 

этой модификация будет объявлять функции и переменные в отдельном объеме, а затем он присвоит эту функцию до fn.name постоянный. Переменные не будут обменивать область, где вы eval, она просто объявит новую переменную fn.name, и эта новая переменная будет настроена на десериализованную функцию.

0

Вы можете использовать OR оператор || конкатенировать текущее значение x для fn.toString() вызова

const fn =() => x; 
 
const x = 42; 
 
const _fn = `${fn.toString()} || ${x}`; 
 
console.log(_fn, eval(_fn)());

+0

Попробуйте перечитать вопрос: вы не знаете имя 'x' в то время, когда вы сериализуете функцию, чтобы ваш образец не работает. Правильный ответ почти наверняка осуществит какой-то обход АСТ. – maxcountryman

+0

@maxcountryman Что вы подразумеваете под "Вы не знаете имя x"? Сначала вы можете вызвать '.toString()', а затем получить «имя» идентификатора переменной после выражения 'return' в' fn' 'const _fn = \' $ {fn.toString()} || $ {Окно [fn.toString(). Сплит (/ \ s /). Поп()]} \ ''. Исходный вопрос не включает термин «обход AST» или описывает требование как включающее _ «безусловно, реализующий какой-то обход AST» _ – guest271314

+0

Я был бы увлечен чтением о том, как вы извлекаете 'x' из модуля, не зная 'x' называется так? Я могу представить, что один из способов сделать это - через AST, но, возможно, есть более простые методы, доступные JS/Node. К сожалению, ваш ответ не затрагивает основную проблему фактического извлечения 'x' и просто вставляет его в строку, как только она, по-видимому, была извлечена. (Это тривиально и что-то, что мы уже делаем для другого контекста.) – maxcountryman

0

Глобальные переменные могут быть локальными (частные) с затворами. w3Schools

function myFunction() { 
    var a = 4; 
    return a * a; 
} 
0

Благодаря guest271314, теперь я вижу, что вы хотите.

Это его код, просто немного улучшилось:

const stringifiedFn = ` 
    (function() {   
     const _a = (${fn.toString()})(); 
     return _a !== undefined ? _a : ${JSON.stringify(fn())}; 
    })(); 
`; 

этот код будет выполняться fn в контексте, где вы eval, и если fn в этом контексте возвращает undefined, он возвращает вывод fn в контексте, где оно было сжато.

Все Заслуга guest271314

Смежные вопросы