Получить значение функции по умолчанию?

Есть ли способ получить значение аргумента функции по умолчанию в JavaScript?

function foo(x = 5) {
    // things I do not control
}

Есть ли способ получить значение по умолчанию x здесь? Оптимально что-то вроде:

getDefaultValues(foo); // {"x": 5}

Обратите внимание, что toStringing функция не будет работать, так как она нарушится при значениях по умолчанию, которые не являются постоянными.


person Benjamin Gruenbaum    schedule 04.10.2015    source источник
comment
Python, Ruby и C# все это делают.   -  person Benjamin Gruenbaum    schedule 04.10.2015
comment
проверьте это stackoverflow.com/questions/894860/   -  person Or Bachar    schedule 04.10.2015
comment
@OrBachar Это вопрос установки параметра по умолчанию, здесь OP хочет получить значение параметра по умолчанию извне функции, не вызывая ее.   -  person Tushar    schedule 04.10.2015
comment
каков вариант использования?   -  person Mulan    schedule 04.10.2015
comment
Что ты пытаешься сделать? Это напоминает мне создание LINQ-подобного API запросов в JavaScript   -  person Bergi    schedule 11.10.2015


Ответы (4)


Поскольку у нас нет классического отражения в JS, которое вы можете найти в C#, Ruby и т. д., мы должны полагаться на один из моих любимых инструментов, регулярные выражения, чтобы сделать эту работу за нас:

let b = "foo";
function fn (x = 10, /* woah */ y = 20, z, a = b) { /* ... */ }

fn.toString()
  .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1] // Get the parameters declaration between the parenthesis
  .replace(/(\/\*[\s\S]*?\*\/)/mg,'')             // Get rid of comments
  .split(',')
  .reduce(function (parameters, param) {          // Convert it into an object
    param = param.match(/([_$a-zA-Z][^=]*)(?:=([^=]+))?/); // Split parameter name from value
    parameters[param[1].trim()] = eval(param[2]); // Eval each default value, to get strings, variable refs, etc.

    return parameters;
  }, {});

// Object { x: 10, y: 20, z: undefined, a: "foo" }

Если вы собираетесь использовать это, просто убедитесь, что вы кэшируете регулярные выражения для повышения производительности.

Спасибо bubersson за советы по первым двум регулярным выражениям.

person pilau    schedule 05.10.2015
comment
Это ломается, если среда не одинакова: function foo(){ var x = 5; return function bar(y = x){}}; var bar = foo(); - person Benjamin Gruenbaum; 05.10.2015
comment
Не правда. Вы все еще можете использовать .toString() для этой возвращаемой функции. - person pilau; 05.10.2015
comment
Нет, потому что вам придется его оценить, а это вызовет побочный эффект. Например, function foo(){ var x = prompt("Please enter a number"); return function (y = x) {}; } var bar = foo() - как бы вы извлекли значение, связанное с y здесь? - person Benjamin Gruenbaum; 05.10.2015
comment
Я понимаю, но как вы собираетесь обойти врожденное ограничение языка? Это как если бы вы спросили, как получить переменную с областью действия вне ее закрытия. - person pilau; 05.10.2015
comment
Что ж, будем надеяться, что в ES2015 или ES2016 есть API или что-то запланированное (или причина, по которой оно было отклонено), которое позволяет вам выполнять Reflect.getDefaultParameterValues функцию или что-то подобное. В этом нет ничего неожиданного, поскольку другие динамически типизированные языки с параметрами по умолчанию, такие как Python, Ruby и PHP, могут делать это, а также статически типизированные языки, такие как C# или Scala. Конечно, было бы неожиданно не иметь возможности сделать это. - person Benjamin Gruenbaum; 05.10.2015
comment
Извините, я не знал, что вы искали вклад ES2016 в этот ответ, я подумал, что должен просто попытаться решить вашу проблему, используя код, который может работать прямо сейчас. - person pilau; 05.10.2015
comment
Почему вы так уверены, что нет кода, который делает это прямо сейчас :) Я не удивлюсь, если есть неясный метод, который делает это, о котором я просто не знаю :) - person Benjamin Gruenbaum; 05.10.2015
comment
Потому что я вполне уверен в своем JS skeelz :) Но я буду более чем счастлив узнать что-то новое и при этом съесть свою шляпу! - person pilau; 05.10.2015

Я бы решил это, извлекая параметры из строковой версии функции:

// making x=3 into {x: 3}
function serialize(args) {
  var obj = {};
  var noWhiteSpace = new RegExp(" ", "g");
  args = args.split(",");
  args.forEach(function(arg) {
    arg = arg.split("=");
    var key = arg[0].replace(noWhiteSpace, "");
    obj[key] = arg[1];
  });
  return obj;
  }

 function foo(x=5, y=7, z='foo') {}

// converting the function into a string
var fn = foo.toString();

// magic regex to extract the arguments 
var args = /\(\s*([^)]+?)\s*\)/.exec(fn);

//getting the object
var argMap = serialize(args[1]); //  {x: "5", y: "7", z: "'foo'"}

метод извлечения аргумента был взят отсюда: Регулярное выражение для получения списка параметров из определения функции

ваше здоровье!

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

person silicakes    schedule 04.10.2015
comment
Это не работает, если функция привязана к чему-либо, кроме литерала. Например let y = 5; function foo(x=y) {};. Это не похоже на решения Python Ruby или C#. - person Benjamin Gruenbaum; 04.10.2015
comment
@BenjaminGruenbaum Это также не будет работать на C #. он должен быть постоянным во времени компиляции. - person Royi Namir; 04.10.2015
comment
в зависимости от вашей ситуации вы можете использовать это, используя что-то вроде obj[key] = eval(arg[1]) || аргумент [1]; в сериализации фн. да, eval.. но на самом деле это разумное решение - person silicakes; 04.10.2015
comment
@RoyiNamir, однако, это полностью действительный JavaScript. Он компилируется, и параметр по умолчанию работает, но код в этом ответе не возвращает правильное значение по умолчанию. - person Benjamin Gruenbaum; 04.10.2015
comment
@silicakes, которые не сработают в случаях, когда лексическая среда не одинакова. Не говоря уже о this. Я надеялся на более Reflect.getDefaultArguments или что-то в этом роде, мы обсуждали крайние случаи, начиная с этого: goo.gl /iqx5yf - person Benjamin Gruenbaum; 04.10.2015
comment
Я имел в виду let y = 5; функция foo(x=y) {}; Это отличается от решений Python Ruby или C#. В С# объявление значения по умолчанию для другой переменной является ошибкой. - person Royi Namir; 04.10.2015
comment
@RoyiNamir не нужно придираться, const int y = 6; int Bar(int x = y){ return x;} отлично работает в LINQPad, и отражение выдает правильные значения. - person Benjamin Gruenbaum; 04.10.2015
comment
@BenjaminGruenbaum Нет. Пожалуйста, будьте точны в том, о чем вы пишете примеры и об аналогии с C #. Я только что проверил код, и если бы он не был CONST, он бы не скомпилировался. Так что это не может быть динамической переменной. в вашем примере - let может иметь динамическое/вычисляемое значение. Это огромная разница. - person Royi Namir; 04.10.2015

Как говорится в вопросе, использование toString является ограниченным решением. Это даст результат, который может быть чем угодно, от литерала значения до вызова метода. Однако это относится к самому языку — он допускает такие объявления. Преобразование всего, что не является буквальным значением, в значение в лучшем случае является эвристическим предположением. Рассмотрим следующие 2 фрагмента кода:

let def;
function withDef(v = def) {
  console.log(v);
}

getDefaultValues(withDef); // undefined, or unknown?

def = prompt('Default value');
withDef();
function wrap() {
  return (new Function(prompt('Function body')))();
  // mutate some other value(s) --> side effects
}

function withDef(v = wrap()) {
  console.log(v);
}
withDef();
getDefaultValues(withDef); // unknown?

В то время как первый пример может быть оценен (при необходимости рекурсивно) для извлечения undefined, а затем для любого другого значения, второй действительно не определен, поскольку значение по умолчанию не является детерминированным. Конечно, вы можете заменить prompt() на любой другой внешний вход/генератор случайных чисел.

Так что лучший ответ тот, который у вас уже есть. Используйте toString и, если хотите, eval() то, что вы извлекаете, но это будет иметь побочные эффекты.

person Amit    schedule 07.10.2015

Есть ли способ получить значение по умолчанию x здесь?

Нет, для таких вещей нет встроенной функции отражения, и в любом случае это совершенно невозможно, учитывая то, как параметры по умолчанию спроектированы в JavaScript.

Обратите внимание, что toString()ing функция не будет работать, так как она сломается при значениях по умолчанию, которые не являются постоянными.

Конечно. Единственный способ узнать это — вызвать функцию, так как значения по умолчанию могут зависеть от вызова. Просто подумайте о

function(x, y=x) { … }

и попытайтесь получить разумное представление для значения по умолчанию ys.

В других языках вы можете получить доступ к значениям по умолчанию либо потому, что они являются постоянными (оцениваются во время определения), либо их отражение позволяет вам разбивать выражения и оценивать их в контексте, в котором они были определены.

Напротив, JS оценивает выражения инициализатора параметров при каждом вызове функции (если требуется). параметры? подробнее. И эти выражения, как если бы они были частью тела функции, недоступны программно, так же как любые значения, на которые они ссылаются, скрыты в закрытой области действия функции.
Если у вас есть API-интерфейс отражения, который разрешает доступ к значениям закрытия (например, API отладки движка), вы также можете получить доступ к значениям по умолчанию.

Совершенно невозможно отличить function(x, y=x) {…} от function(x) { var y=x; …} по их общедоступным свойствам или по их поведению, единственный способ - .toString(). И вы не хотите полагаться на это обычно.

person Bergi    schedule 11.10.2015
comment
в любом случае это совершенно невозможно, учитывая, как параметры по умолчанию спроектированы в JavaScript. - Ну, эта часть неверна. Ваш Reflect.getDefaultParameterValues может возвращать геттеры для параметров. - person Benjamin Gruenbaum; 11.10.2015
comment
Вы имеете в виду, что он должен возвращать объект, который при оценке выполняет побочные эффекты (выражение инициализатора по умолчанию), а затем выдает ошибку типа x is not defined? Это так же невозможно, как получение закрытых значений из объекта функции замыкания - это возможно, но не предназначено для этого. - person Bergi; 11.10.2015