如何最好地确定参数是否没有发送到JavaScript函数

我已经看到了两种方法,用于确定参数是否已传递给JavaScript函数。我想知道是一种方法比另一种好,还是一种方法不好用?

 function Test(argument1, argument2) {
if (Test.arguments.length == 1) argument2 = 'blah';


alert(argument2);
}


Test('test');

 function Test(argument1, argument2) {
argument2 = argument2 || 'blah';


alert(argument2);
}


Test('test');

据我所知,它们的结果是一样的,但我以前在生产中只使用过第一个。

汤姆提到的另一个选项:

function Test(argument1, argument2) {
if(argument2 === null) {
argument2 = 'blah';
}


alert(argument2);
}

根据Juan的评论,Tom的建议最好改为:

function Test(argument1, argument2) {
if(argument2 === undefined) {
argument2 = 'blah';
}


alert(argument2);
}
194564 次浏览

有几种不同的方法来检查参数是否传递给了函数。除了你在(最初的)问题中提到的两个——检查arguments.length或使用||操作符提供默认值——如果你是偏执狂,还可以通过argument2 === undefinedtypeof argument2 === 'undefined'显式检查undefined的参数(见评论)。

使用||操作符已经成为标准实践——所有酷孩子都这么做——但要小心:如果参数的计算值为false,则会触发默认值,这意味着它实际上可能是undefinednullfalse0''(或任何Boolean(...)返回false的东西)。

所以问题是什么时候使用哪种检查,因为它们产生的结果都略有不同。

检查arguments.length显示了“最正确”的行为,但如果有多个可选参数,这可能是不可行的。

undefined的测试是下一个“最好”的测试——只有在显式地使用undefined值调用函数时才会“失败”,这很可能与省略实参一样。

使用||操作符可能会触发使用默认值,即使提供了有效参数。另一方面,它的行为实际上可能是需要的。

总结:只有当你知道你在做什么时才使用它!

在我看来,如果有多个可选参数,并且不想传递对象字面量作为命名参数的变通方法,使用||也是一种方法。

另一种使用arguments.length提供默认值的好方法是通过切换switch语句的标签:

function test(requiredArg, optionalArg1, optionalArg2, optionalArg3) {
switch(arguments.length) {
case 1: optionalArg1 = 'default1';
case 2: optionalArg2 = 'default2';
case 3: optionalArg3 = 'default3';
case 4: break;
default: throw new Error('illegal argument count')
}
// do stuff
}

这样做的缺点是,程序员的意图(在视觉上)并不明显,而是使用“神奇的数字”;因此,它可能容易出错。

我很抱歉,我仍然不能评论,所以回答汤姆的回答… 在javascript中(undefined != null) == false 事实上,该函数不会使用“null”,你应该使用“undefined”

这是我发现的少数几个测试案例之一:

if(! argument2) {


}

工作得很好,并且在语法上具有正确的含义。

(同时限制,我不允许合法的空值argument2有一些其他的含义;但这会让人困惑。)

编辑:

这是一个很好的例子,说明了松散类型语言和强类型语言之间的风格差异;这是javascript提供的一种风格选择。

我个人的偏好是极简主义(对其他偏好没有批评)。只要我是一致和简洁的,代码需要表达的越少,其他人就需要理解的越少,从而正确地推断出我的意思。

这种偏好的一个暗示是,我不想——也不认为它有用——堆积一堆类型依赖的测试。相反,我试图让代码的意思像它看起来的那样;只测试我真正需要测试的东西。

我在其他人的代码中发现的一个令人恼火的问题是,需要弄清楚他们是否期望在更大的上下文中实际遇到他们正在测试的用例。或者如果他们试图测试所有可能的情况,他们可能没有完全预测到上下文。这意味着在我能够自信地重构或修改任何东西之前,我最终需要在两个方向上详尽地跟踪它们。我认为,他们很有可能会实施这些不同的测试,因为他们预见了需要这些测试的情况(而这些情况通常对我来说并不明显)。

(我认为这是这些人使用动态语言的一个严重缺点。人们经常不愿意放弃所有的静态测试,最终以假装测试而告终。)

在比较全面的ActionScript 3代码和优雅的javascript代码时,我最清楚地看到了这一点。AS3可以是js的3到4倍,我怀疑可靠性至少不会更好,只是因为所做的编码决策的数量(3-4倍)。

如你所说,Shog9, YMMV。: D

两者存在显著差异。让我们建立一些测试用例:

var unused; // value will be undefined
Test("test1", "some value");
Test("test2");
Test("test3", unused);
Test("test4", null);
Test("test5", 0);
Test("test6", "");

使用您描述的第一个方法,只有第二个测试将使用默认值。第二个方法将默认除第一个方法外的所有方法(因为JS将把undefinednull0""转换为布尔值false。如果您使用Tom的方法,那么只有第四个测试将使用默认的方法!

你选择哪种方法实际上取决于你的预期行为。如果除了undefined之外的值被允许用于argument2,那么你可能需要对第一个值进行一些更改;如果需要一个非零、非空、非空的值,那么第二种方法是理想的——实际上,它经常被用来快速地从考虑中消除如此广泛的值。

url = url === undefined ? location.href : url;

通过调用带有可选属性的Object来调用函数,可以方便地检测参数:

function foo(options) {
var config = { // defaults
list: 'string value',
of: [a, b, c],
optional: {x: y},
objects: function(param){
// do stuff here
}
};
if(options !== undefined){
for (i in config) {
if (config.hasOwnProperty(i)){
if (options[i] !== undefined) { config[i] = options[i]; }
}
}
}
}

如果你正在使用jQuery,一个不错的选项(特别是在复杂的情况下)是使用jQuery的扩展方法

function foo(options) {


default_options = {
timeout : 1000,
callback : function(){},
some_number : 50,
some_text : "hello world"
};


options = $.extend({}, default_options, options);
}

如果你像这样调用函数:

foo({timeout : 500});

选项变量将是:

{
timeout : 500,
callback : function(){},
some_number : 50,
some_text : "hello world"
};

为什么不使用!!操作符?这个运算符放在变量之前,将其转换为布尔值(如果我理解得很好),因此!!undefined!!null(甚至!!NaN,这可能非常有趣)将返回false

这里有一个例子:

function foo(bar){
console.log(!!bar);
}


foo("hey") //=> will log true


foo() //=> will log false

有时,您可能还想检查类型,特别是当您使用函数作为getter和setter时。下面的代码是ES6(不能在EcmaScript 5或更老版本中运行):

class PrivateTest {
constructor(aNumber) {
let _aNumber = aNumber;


//Privileged setter/getter with access to private _number:
this.aNumber = function(value) {
if (value !== undefined && (typeof value === typeof _aNumber)) {
_aNumber = value;
}
else {
return _aNumber;
}
}
}
}

有一个找到参数是否传递给函数的方法也很棘手。看看下面的例子:

this.setCurrent = function(value) {
this.current = value || 0;
};

这意味着如果value的值不存在/传递-将其设置为0。

很酷啊!

在ES6 (ES2015)中,你可以使用默认参数

function Test(arg1 = 'Hello', arg2 = 'World!'){
alert(arg1 + ' ' +arg2);
}


Test('Hello', 'World!'); // Hello World!
Test('Hello'); // Hello World!
Test(); // Hello World!

fnCalledFunction (Param1 Param2 window.YourOptionalParameter)

如果上面的函数是从很多地方调用的,并且你确定前两个参数是从每个地方传递的,但不确定第三个参数,那么你可以使用window。

窗口。如果它没有从调用方方法中定义,Param3将进行处理。

function example(arg) {
var argumentID = '0'; //1,2,3,4...whatever
if (argumentID in arguments === false) {
console.log(`the argument with id ${argumentID} was not passed to the function`);
}
}

因为数组继承自Object.prototype。想让世界更美好。

有时你想要undefined作为一个可能的参数,但你仍然有可能不传递参数的情况。在这种情况下,你可以使用arguments.length来检查传递了多少个参数。


// Throw error if the field is not matching our expectations
function testField(label, fieldValue, expectedValue) {
console.log(arguments) // Gives: [Arguments] { '0': 'id', '1': 1, '2': undefined }
if(arguments.length === 2) {
if(!fieldValue) {
throw new Error(`Field "${label}" must have a value`)
}
}


else if(expectedValue === undefined) {
if(fieldValue !== undefined) {
throw Error(`Field "${label}" must NOT have a value`)
}
}


// We stringify so our check works for objects as well
else {
if(JSON.stringify(fieldValue) !== JSON.stringify(expectedValue)) {
throw Error(`Field "${label}" must equal ${expectedValue} but was ${fieldValue}`)
}
}
}

testField('id', 12)→pass,我们不希望id为空

testField('id', undefined, undefined)→传递时,我们希望id为undefined

testField('id', 12, undefined)→错误,我们希望id为undefined