Как лучше всего реализовать параметры в 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 говорит мне, что вышеприведенный подход будет работать, но есть ли лучший способ?
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);
Не причудливый и едва ли пуленепробиваемый, но, конечно, это нормально для некоторых простых ситуаций.
Я думаю, что это в значительной степени единственный способ (но я не хардкорный программист JavaScript;)).
Что вы также можете рассмотреть, так это использовать функцию обратного вызова:
function onError(data) {
// do stuff
}
function isLegal(odp, cb) {
//...
if(error) cb(error);
return false;
}
isLegal(value, onError);
Да, как вы сами упомянули, объекты являются лучшим и единственным способом передачи данных по ссылке в JavaScript. Я бы сохранила твой isLegal
функция как таковая и просто вызвать его так:
var error = {};
isLegal("something", error);
alert(error.val);
Ответы, которые я видел до сих пор, не реализуют параметры в JavaScript, так как они используются в C# (out
ключевое слово). Это просто обходной путь, который возвращает объект в случае ошибки.
Но что вы будете делать, если вам действительно нужны параметры?
Поскольку Javascript не поддерживает его напрямую, вам нужно создать что-то, что близко к параметрам C# out. Взгляните на этот подход, я эмулирую функцию DateTime. TryParse в C# в 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");
};
Запустите фрагмент, и вы увидите, что он работает. Обратите внимание, что result
нужно инициализировать как пустой массив []
перед вызовом функции. Это необходимо, потому что внутри функции, которую вы "вводите" .value
имущество.
Теперь вы можете использовать вышеприведенный шаблон, чтобы написать функцию как функцию в вашем вопросе (это также показывает, как эмулировать новый параметр сброса, известный как 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!");
Примечание. Вместо использования параметра 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 ...
};
Я использую метод обратного вызова (аналогично подходу Феликса Клинга) для имитации поведения выходных параметров. Мой ответ отличается от ответа Клинга тем, что функция обратного вызова действует как замыкание захвата ссылок, а не как обработчик.
Этот подход страдает от подробного синтаксиса анонимной функции 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".
Есть еще один способ, которым JS может передавать параметры. но я верю, что лучшие для вашей ситуации уже упоминались.
Массивы также передаются по ссылке, а не по значению. таким образом, точно так же, как вы можете передать объект в функцию, а затем установить свойство объекта в функции, а затем вернуть и получить доступ к свойству этого объекта, вы можете аналогичным образом передать массив в функцию, установить некоторые значения массива внутри функции, и вернуть и получить доступ к этим значениям за пределами массива.
Таким образом, в каждой ситуации вы можете спросить себя, "массив или объект лучше?"
Ниже приводится подход, который я использую. И это ответ на этот вопрос. Однако код не был проверен.
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() );
Я не собираюсь публиковать какие-либо 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);
}
Обычный подход к конкретному варианту использования, который вы описали в 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);
Я знаю, что ловить исключения - это боль, но опять-таки отслеживает ссылки.
Если вам действительно нужно что-то, что приближается к поведению наших переменных, объекты по умолчанию передаются по ссылке и могут легко захватывать данные из других областей:
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 поддерживает так много гораздо более элегантных и мощных способов получить множество значений из функции. Немного почитайте о генераторах, замыканиях, а в некоторых ситуациях даже обратные вызовы могут быть полезны - посмотрите стиль прохождения продолжения.
Моя точка зрения на всю эту напыщенную речь состоит в том, чтобы побудить любого, кто читает это, адаптировать свой стиль программирования к ограничениям и возможностям языка, который они используют, вместо того, чтобы пытаться навязать ему то, что они узнали из других языков.
(Кстати, некоторые люди настоятельно рекомендуют против замыканий, потому что они вызывают злые побочные эффекты, но я бы их не слушал. Они пуристы. Побочные эффекты практически неизбежны во многих приложениях без большого утомительного возврата и обхода -получить препятствия отсюда. Если они вам нужны, мне лучше будет держать их все вместе в аккуратном лексическом контексте, а не разбросанном по адскому ландшафту неясных указателей и ссылок)
Основным преимуществом реальных выходных параметров является прямое изменение одной или нескольких скалярных переменных в области действия вызывающего. Среди подходов, предложенных в других ответах, этому требованию удовлетворяют только обратные вызовы:
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.
Однако у этого решения есть недостаток: оно не может обрабатывать возвраты неосновных типов.