42

我正在使用带有 jQ​​uery 的 Javascript。我想实现参数。在 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 告诉我上述方法可行,但有更好的方法吗?

4

10 回答 10

48

@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);

不花哨,几乎没有防弹,但对于一些简单的情况肯定没问题。

于 2010-07-04T18:30:01.437 回答
19

是的,正如您自己提到的,对象是在 JavaScript 中通过引用传递数据的最佳且唯一的方法。我会保持你的isLegal功能,并简单地这样称呼它:

var error = {};
isLegal("something", error);
alert(error.val);
于 2010-07-04T18:59:42.537 回答
18

我认为这几乎是唯一的方法(但我不是一个铁杆 JavaScript 程序员;))。

您还可以考虑使用回调函数:

function onError(data) {
    // do stuff
}


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

isLegal(value, onError);
于 2010-07-04T18:24:56.143 回答
8

到目前为止,我看到的答案并没有在 JavaScript 中实现 out 参数,因为它们在 C#(out关键字)中使用。它们只是一种在发生错误时返回对象的解决方法。

但是如果你真的需要输出参数怎么办?

因为 Javascript 不直接支持它,所以你需要构建一些接近 C# 的 out 参数的东西。看看这种方法,我在 JavaScript 中模拟 C#s DateTime.TryParse 函数。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参数解析为 Date 并在result参数中返回。请注意,在调用函数之前result需要将其初始化为空数组[](根据您的需要,它也可以是一个对象)。这是必需的,因为在函数内部您“注入”了属性。{}.value


现在您可以使用上面的模式编写一个函数作为您的问题中的那个(这也向您展示了如何模拟在 C# 中称为新的丢弃out _参数:在 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 测试失败"; // 返回错误

如果您运行该示例,它将在弹出对话框中显示此消息。

注意:除了使用如上所示的丢弃参数,在 JavaScript 中您还可以使用检查 for 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 ...
};
于 2018-01-30T09:28:14.720 回答
4

我正在使用回调方法(类似于Felix Kling 的方法)来模拟 out 参数的行为。我的回答与 Kling 的不同之处在于回调函数充当引用捕获闭包而不是处理程序。

这种方法受到 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".
于 2014-06-09T19:34:34.807 回答
3

还有另一种方式 JS 可以传递 'out' 参数。但我相信已经提到了最适合您的情况。

数组也是通过引用而不是值传递的。因此,就像您可以将对象传递给函数,然后在函数中设置对象的属性,然后返回并访问该对象的属性一样,您可以类似地将数组传递给函数,设置数组的一些值在函数内部,并返回和访问数组外部的那些值。

因此,在每种情况下,您都可以问自己:“数组还是对象更好?”

于 2012-01-27T23:22:10.853 回答
1

我不会发布任何code内容,但在这些答案中未能做到的是将押韵推向理性。我在本机 JS 领域工作,出现了一些native API calls需要转换的问题,因为我们无法在没有丑陋可耻的 hack 的情况下写入参数。

这是我的解决方案:

    // 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 calledwhere you should be looking不必知道您在寻找什么

还有一个优点是可以编写“健壮和愚蠢”的算法来环绕所需的过程调用,从而使该操作“更加透明”。

使用 an 、 或 an (所有这些都是对象)作为“回写输出”参数是明智的objectfunctionarray我相信如果必须完成任何无关的工作,应该由编写使事情变得更容易或扩展功能的工具包。

这是每个场合的通用答案,它始终保持乍一看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);

}
于 2012-12-31T09:56:19.290 回答
1

以下是我正在使用的方法。这就是这个问题的答案。但是代码尚未经过测试。

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() );
于 2013-02-28T17:47:21.610 回答
0

实际输出参数的主要优点是直接修改调用者范围内的一个或多个标量变量。在其他答案中提出的方法中,只有回调满足此要求:

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");

在这种情况下,传递每个输出参数需要五个额外的字符来将其标识符包装到匿名 setter 函数中。它既不可读也不容易频繁输入,因此人们可以求助于脚本语言最有趣的一个特性——它们做魔术的能力,例如将字符串作为代码执行。

下面的例子实现了上面整数解析函数的扩展版本,它现在有两个输出参数:结果整数和一个指示它是否为正的标志:

/* ------------ 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
于 2021-03-14T14:24:08.177 回答
0

您在 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 支持许多更优雅、更强大的方法来从函数中获取多个值。阅读有关生成器、闭包、地狱甚至回调在某些情况下可能会很好的信息——查看延续传递样式。

我对整个咆哮的观点是鼓励任何阅读本文的人调整他们的编程风格以适应他们正在使用的语言的限制和功能,而不是试图强迫他们从其他语言中学到的东西。

(顺便说一句,有些人强烈建议不要使用闭包,因为它们会导致邪恶的副作用,但我不会听他们的。他们是纯粹主义者。在许多应用程序中,副作用几乎是不可避免的,没有大量繁琐的回溯和绕行-get-there-from-here 障碍。如果你需要它们,将它们放在一个整洁的词法范围内,而不是分散在晦涩难懂的指针和引用的地狱般的环境中,这对我来说听起来要好得多)

于 2016-04-11T20:25:23.140 回答