Как лучше всего реализовать параметры в JavaScript?

Я использую Javascript с jQuery. Я хотел бы реализовать параметры. В C# это будет выглядеть примерно так:

/*
 * odp      the object to test
 * error    a string that will be filled with the error message if odp is illegal. Undefined otherwise.
 *
 * Returns  true if odp is legal.
 */
bool isLegal(odp, out error);

Как лучше всего сделать что-то подобное в JS? Объекты?

function isLegal(odp, errorObj)
{
    // ...
    errorObj.val = "ODP failed test foo";
    return false;
}

Firebug говорит мне, что описанный выше подход будет работать, но есть ли лучший способ?


person Nick Heiner    schedule 04.07.2010    source источник
comment
Ник, я знаю, что это было давно, но, кажется, у меня наконец-то есть ответ для вас. Да, вы можете указать параметры в JavaScript.   -  person Matt    schedule 30.01.2018


Ответы (10)


Подход с обратным вызовом, упомянутый @Felix Kling, вероятно, является лучшей идеей, но я также обнаружил, что иногда легко использовать синтаксис литерала объекта Javascript, и ваша функция просто возвращает объект при ошибке:

function mightFail(param) {
  // ...
  return didThisFail ? { error: true, msg: "Did not work" } : realResult;
}

затем, когда вы вызываете функцию:

var result = mightFail("something");
if (result.error) alert("It failed: " + result.msg);

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

person Pointy    schedule 04.07.2010
comment
+1, этот подход распространен в вызовах JSON, но я редко видел его в коде. - person gradbot; 05.07.2010
comment
Зайдя на эту страницу, потому что меня интересовало, как Javascript может или не может обрабатывать параметры, я разочарован принятым ответом. Возможность иметь определенный тип возвращаемого значения вместе с выходными параметрами от одного до многих с определенными типами очень полезна при программировании очень процедурной логики. К сожалению, с моей крошечной репутацией я не могу проголосовать против. - person Anthony Ruffino; 11.06.2014

Я думаю, что это почти единственный способ (но я не хардкорный программист JavaScript;)).

Вы также можете рассмотреть возможность использования функции обратного вызова:

function onError(data) {
    // do stuff
}


function isLegal(odp, cb) {
    //...
    if(error) cb(error);
    return false;
}

isLegal(value, onError);
person Felix Kling    schedule 04.07.2010

Да, как вы сами упомянули, объекты — это лучший и единственный способ передачи данных по ссылке в JavaScript. Я бы оставил вашу функцию isLegal как таковую и просто назвал бы ее так:

var error = {};
isLegal("something", error);
alert(error.val);
person casablanca    schedule 04.07.2010

Ответы, которые я видел до сих пор, не реализуют параметры out в JavaScript, поскольку они используются в C # (ключевое слово out). Это просто обходной путь, возвращающий объект в случае ошибки.

Но что делать, если вам действительно нужны выходные параметры?

Поскольку Javascript не поддерживает его напрямую, вам нужно создать что-то близкое к выходным параметрам C#. Взгляните на этот подход: я эмулирую функцию C# DateTime.TryParse в JavaScript. Параметр out является результатом, и поскольку в JavaScript нет ключевого слова out, я использую .value внутри функции для передачи значения за пределы функции (как вдохновлено рекомендация MDN):

// create a function similar to C#'s DateTime.TryParse
var DateTime = [];
DateTime.TryParse = function(str, result) {
  result.value = new Date(str); // out value
  return (result.value != "Invalid Date");
};

// now invoke it
var result = [];
if (DateTime.TryParse("05.01.2018", result)) {
  alert(result.value);
} else {
  alert("no date");
};

Запустите фрагмент, и вы увидите, что он работает: он преобразует параметр str в дату и возвращает его в параметре result. Обратите внимание, что result необходимо инициализировать как пустой массив [], перед вызовом функции (это также может быть object{} в зависимости от ваших потребностей). Это необходимо, потому что внутри функции вы вводите свойство .value.


Теперь вы можете использовать приведенный выше шаблон, чтобы написать функцию, как в вашем вопросе (это также показывает, как эмулировать новый параметр discard, известный как out _ в C#: в JavaScript мы передаем [], как показано ниже):

// create a function similar to C#'s DateTime.TryParse
var DateTime = [];
DateTime.TryParse = function(str, result) {
  result.value = new Date(str); // out value
  return (result.value != "Invalid Date");
};

// returns false, if odb is no date, otherwise true
function isLegal(odp, errorObj) {
  if (DateTime.TryParse(odp, [])) { // discard result here by passing []
    // all OK: leave errorObj.value undefined and return true
    return true;
  } else {
    errorObj.value = "ODP failed test foo"; // return error
    return false;
  }
}

// now test the function
var odp = "xxx01.12.2018xx"; // invalid date
var errorObj = [];
if (!isLegal(odp, errorObj)) alert(errorObj.value); else alert("OK!");

В этом примере используется параметр result для передачи сообщения об ошибке следующим образом:

errorObj.value = ошибка теста ODP foo; // возвращаем ошибку

Если вы запустите пример, он отобразит это сообщение во всплывающем диалоговом окне.

Примечание. Вместо использования параметра discard, как показано выше, в JavaScript можно также использовать проверку на undefined, т. е. внутри функции проверки на

if (result === undefined) { 
   // do the check without passing back a value, i.e. just return true or false 
};

Затем можно полностью опустить result в качестве параметра, если он не нужен, поэтому вы можете вызывать его как

if (DateTime.TryParse(odp)) { 
    // ... same code as in the snippet above ...
};
person Matt    schedule 30.01.2018
comment
Зачем использовать массивы вместо объектов? - person Kyle Delaney; 12.12.2020
comment
@KyleDelaney - Какое решение вы имеете в виду? - person Matt; 14.12.2020
comment
var DateTime = {}; и var errorObj = {}; - person Kyle Delaney; 14.12.2020
comment
@KyleDelaney - Извините, поздний ответ. Я думаю, что {} (объекты) тоже можно использовать. - person Matt; 09.02.2021

есть еще один способ, которым JS может передавать параметры «out». но я считаю, что лучшие для вашей ситуации уже упоминались.

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

поэтому в каждой ситуации вы можете спросить себя: «Что лучше — массив или объект?»

person Humilulo -- Shawn Kovac    schedule 27.01.2012
comment
хотя это возможно, ради ясности кода в вызывающем коде я бы рекомендовал ответ @Pointy: используйте перегруженное возвращаемое значение, поскольку оно немного яснее. - person cmroanirgo; 14.01.2013

Я использую метод обратного вызова (аналогичный подходу Феликса Клинга) для имитации поведения выходных параметров. Мой ответ отличается от ответа Клинга тем, что функция обратного вызова действует как замыкание с захватом ссылок, а не как обработчик.

Этот подход страдает от многословного синтаксиса анонимных функций JavaScript, но точно воспроизводит семантику параметров из других языков.

function isLegal(odp, out_error) {
    //...
    out_error("ODP failed test foo"); // Assign to out parameter.
    return false;
}

var error;
var success = isLegal(null, function (e) { error = e; });

// Invariant: error === "ODP failed test foo".
person Gooseberry    schedule 09.06.2014

Я не собираюсь публиковать какие-либо code, но в этих ответах нельзя сделать приводить рифму к разуму. Я работаю в области нативного JS, и возникла проблема, что некоторые native API calls нужно преобразовать, потому что мы не можем писать параметры без уродливых позорных хаков.

Это мое решение:

    // Functions that return parameter data should be modified to return
    // an array whose zeroeth member is the return value, all other values
    // are their respective 1-based parameter index.

Это не означает определение и возврат каждого параметра. Только те параметры, которые получают вывод.

Причина такого подхода такова: Multiple return values может понадобиться для любого количества процедур. Это создает ситуацию, когда объекты с именованными значениями (которые в конечном итоге не будут синхронизированы с лексическим контекстом всех операций) необходимо постоянно запоминать, чтобы правильно работать с процедурой(ами).

Используя предписанный метод, вам нужно знать только what you called и where you should be looking, а не знать, что вы ищете.

Существует также то преимущество, что "надежные и глупые" алгоритмы могут быть написаны так, чтобы обернуть нужные вызовы процедур, чтобы сделать эту операцию "более прозрачной".

Было бы разумно использовать object, function или array (все они являются объектами) в качестве параметра "обратная запись-вывод", но я считаю, что если необходимо выполнить какую-либо постороннюю работу, она должна выполняться один пишет набор инструментов, чтобы упростить задачу или расширить функциональность.

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

Поздравляю и желаю удачи.

Я использую webkitgtk3 и взаимодействую с некоторыми собственными процедурами библиотеки C. так что этот проверенный пример кода может послужить хотя бы иллюстрацией.

// ssize_t read(int filedes, void *buf, size_t nbyte)
SeedValue libc_native_io_read (SeedContext ctx, SeedObject function, SeedObject this_object, gsize argument_count, const SeedValue arguments[], SeedException *exception) {


    // NOTE: caller is completely responsible for buffering!

                    /* C CODING LOOK AND FEEL */


    if (argument_count != 3) {
        seed_make_exception (ctx, exception, xXx_native_params_invalid,
            "read expects 3 arguments: filedes, buffer, nbyte: see `man 3 read' for details",
            argument_count
        );  return seed_make_undefined (ctx);
    }

    gint filedes = seed_value_to_int(ctx, arguments[0], exception);
    void *buf = seed_value_to_string(ctx, arguments[1], exception);
    size_t nbyte = seed_value_to_ulong(ctx, arguments[2], exception);

    SeedValue result[3];

    result[0] = seed_value_from_long(ctx, read(filedes, buf, nbyte), exception);
    result[2] = seed_value_from_binary_string(ctx, buf, nbyte, exception);

    g_free(buf);
    return  seed_make_array(ctx, result, 3, exception);

}
person Community    schedule 31.12.2012
comment
Это отличная идея для возврата параметров в JavaScript. Имеет смысл для меня. - person Sunil; 01.02.2014

Ниже приведен подход, который я использую. И это ответ на этот вопрос. Однако код не тестировался.

function mineCoords( an_x1, an_y1 ) {
  this.x1 = an_x1;
  this.y1 = an_y1;
}

function mineTest( an_in_param1, an_in_param2 ) {

  // local variables
  var lo1 = an_in_param1;
  var lo2 = an_in_param2;

  // process here lo1 and lo2 and 
  // store result in lo1, lo2

  // set result object
  var lo_result = new mineCoords( lo1, lo2 );
  return lo_result;
}

var lo_test = mineTest( 16.7, 22.4 );
alert( 'x1 = ' + lo_test.x1.toString() + ', y1 = ' + lo_test.y1.toString() );
person Dominik Lebar    schedule 28.02.2013

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

Я бы просто сделал это. Если вам действительно нужно передать пользовательские данные вызывающей функции, вы можете создать подкласс Error.

var MyError = function (message, some_other_param)
{
    this.message = message;
    this.some_other_param = some_other_param;
}
//I don't think you even need to do this, but it makes it nice and official
MyError.prototype = Error; 
...
if (something_is_wrong)
    throw new MyError('It failed', /* here's a number I made up */ 150); 

Я знаю, что ловить исключения — это больно, но опять же, так же как и отслеживать ссылки.

Если вам действительно нужно что-то, что приближается к поведению переменных out, объекты по умолчанию передаются по ссылке и могут легко захватывать данные из других областей —

function use_out (outvar)
{
    outvar.message = 'This is my failure';
    return false;
}

var container = { message : '' };
var result = use_out(container );
console.log(container.message); ///gives the string above
console.log(result); //false

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

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

(Кстати, некоторые люди настоятельно не рекомендуют замыкания, потому что они вызывают плохие побочные эффекты, но я бы не стал их слушать. Они пуристы. Побочные эффекты почти неизбежны во многих приложениях без большого количества утомительных возвратов и преодоления препятствий, которые не могут получить оттуда отсюда. Если они вам нужны, держать их все вместе в аккуратной лексической области, а не разбросать по адскому ландшафту неясных указателей и ссылок, звучит намного лучше для меня)

person Chuck Bajax    schedule 11.04.2016

Основное преимущество реальных выходных параметров — прямое изменение одной или нескольких скалярных переменных в области действия вызывающего объекта. Среди подходов, предложенных в других ответах, этому требованию удовлетворяют только обратные вызовы:

function tryparse_int_1(s, cb)
{   var res = parseInt(s);
    cb(res);
    return !isNaN( res );
}

function test_1(s)
{   var /* inreger */ i;
    if( tryparse_int_1( s, x=>i=x ) )
        console.log(`String "${s}" is parsed as integer ${i}.`); else
        console.log(`String "${s}" does not start with an integer.`);
}

test_1("47");
test_1("forty-seven");

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

В следующем примере реализована расширенная версия вышеприведенной функции синтаксического анализа целых чисел, которая теперь имеет два выходных параметра: результирующее целое число и флаг, указывающий, является ли оно положительным:

/* ------------ General emulator of output parameters ------------ */

function out_lit(v)
{   var res;
    if( typeof(v) === "string" )
        res = '"' + v.split('\"').join('\\\"') + '"'; else
        res = `${v}`;
    return res;
}

function out_setpar(col, name, value)
{   if( col.outs == undefined ) col.outs = [];
    col.outs[name] = value;
}

function out_setret(col, value)
{   col.ret = value;  }

function out_ret( col )
{   var s;
    for(e in col.outs)
    {   s = s + "," + e + "=" + out_lit( col.outs[e] );  }
    
    if( col.ret != undefined )
    {   s = s + "," + out_lit( col.ret );  }
    
    return s;
}

/* -------- An intger-parsing function using the emulator -------- */

function tryparse_int_2 // parse the prefix of a string as an integer
(   /* string  */ s,    // in:  input string
    /* integer */ int,  // out: parsed integer value
    /* boolean */ pos   // out: whether the result is positive
)
{   var /* integer */ res; // function result
    var /* array   */ col; // collection of out parameters
    
    res = parseInt(s);
    col = [];    
    out_setpar( col, int, res           );
    out_setpar( col, pos, res > 0       );
    out_setret( col,          !isNaN( res ) );
    return out_ret( col );
}

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

function test_2(s)
{   var /* integer */ int;
    var /* boolean */ pos;
    
    if( !eval( tryparse_int_2( s, "int", "pos" ) ) )
    {   console.log(`String "${s}" does not start with an integer.`);  }
    else
    {   if( pos ) adj = "positive";
        else      adj = "non-positive";
        console.log(`String "${s}" is parsed as a ${adj} integer ${int}.`);
    }
}

test_2( "55 parrots"    );
test_2( "-7 thoughts"   );
test_2( "several balls" );

Вывод тестового кода выше:

String "55 parrots" is parsed as a positive integer 55.
String "-7 thoughts" is parsed as a non-positive integer -7.
String "several balls" does not start with an integer.

Однако у этого решения есть недостаток: оно не может обрабатывать возвраты небазовых типов.

Возможно, более чистым подходом является эмуляция указателей:

// Returns JavaScript for the defintion of a "pointer" to a variable named `v':
// The identifier of the pointer is that of the variable prepended by a $.
function makeref(v)
{   return `var $${v} = {set _(val){${v}=val;},get _() {return ${v};}}`;  }

// Calcualtes the square root of `value` and puts it into `$root`.
// Returns whether the operation has succeeded.
// In case of an error, stores error message in `$errmsg`.
function sqrt2
(   /* in  number */  value, /*  value to take the root of  */
    /* out number */ $root , /* "pointer" to result         */
    /* out string */ $errmsg /* "pointer" to error message  */
)
{   if( typeof( value ) !== "number" )
    {   $errmsg._ = "value is not a number.";
        return false;
    }
    if( value < 0 )
    {   $errmsg._ = "value is negative.";
        return false;
    }
    $root._ = Math.sqrt(value);
    return true;
}

Следующий тестовый код:

function test(v)
{   var /* string */ resmsg;
    var /* number */ root  ; eval( makeref( "root"   ) );
    var /* string */ errmsg; eval( makeref( "errmsg" ) );
    
    if( sqrt2(v, $root, $errmsg) ) resmsg = `Success: ${root}`;
    else                           resmsg = `Error: ${errmsg}`;
    console.log(`Square root of ${v}: ` + resmsg );
}

test("s"  );
test(-5   );
test( 1.44);

печатает:

Square root of s: Error: value is not a number.
Square root of -5: Error: value is negative.
Square root of 1.44: Success: 1.2

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

// Append string `sep' to a string pointed to by $s, using `sep` as separator:
// $s shall not point to an undefined value.
function append($s, sep, val)
{   if( $s._ != '' ) $s._ += sep;
    $s._ += val;
}

и используйте его таким образом:

const sep = ", "
var s; eval( makeref("s") );

s = '';
append( $s, sep, "one"   );
append( $s, sep, "two"   );
append( $s, sep, "three" );
console.log( s );

Он будет печатать:

one, two, three
person Ant_222    schedule 14.03.2021