简体   繁体   中英

Optional argument first in JavaScript

For the user's ease, I have a function that receives an optional argument first, before the required argument. For example:

ns.myFunction('optional arg', function(){
    //the required callback
});

I'm doing this rather than doing the following since the callback body could be long, and the user might forget to override the defaults to the optional arguments:

ns.myFunction(function(){
    //the required callback
}, 'optional arg');

Currently I'm doing this to check:

function myFunction(first, second) {

    //if second is undefined and first is a function
    if (typeof second === 'undefined' && typeof first === 'function') { 
        second = first;
    }
}

Questions

  • Is this the right way?
  • How would I do it so that it scales, especially if there were N optional arguments that are before the required argument?

This is not the right way because optional parameters are by convention always placed at the end. And you see a reason why: it is much easier to handle them. If the length of anonymous function is your concern, clients of your API should use function references or variables:

function callback1() { //...

var callback2 = function() {//...

myFunction(callbackX, optional);

The problem with escaping this can be solved with bind() .

If you really want to go the path of multiple optional parameters and callback at the end, I can think of two ways: arguments object or wrapping all optional arguments in one options objects.

With arguments you can write:

var lastOptionalIndex = arguments.length - 2;
var callback = arguments[lastOptionalIndex + 1];  //required callback is always last
var optionalFirst = lastOptionalIndex >=0? arguments[0] : undefined;
var optionalSecond = lastOptionalIndex >=1? arguments[1] : undefined;
//...

See how ugly it is compared to:

function myFunction(callback, firstOptional, secondOptional //...

With options wrapper object you always have two arguments:

function myFunction(options, callback);

Where options is just an object:

{
  firstOptional: 1,
  secondOptional: 'foo'
  //...
}

It can easilly be done with ArgueJS :

function myFunction (){
  arguments = __({first: [String], second: Function})

  // and now on, you can access your arguments by
  //   arguments.first and arguments.second
}

A very similar example can be found at ArgueJS' first example . Note that only the parameter in the middle is required:

function range(){ 
  arguments = __({start: [Number, 0], stop: Number, step: [Number, 1]})

  for(var i = arguments.start; i < arguments.stop; i += arguments.step)
    console.log(i);
}

repl:

>>> range(3)
 0
 1
 2
>>> range(3, 5)
 3
 4
>>> range(0, 5, 2)
 0
 2
 4

You can configurate function arguments, depending on their number:

myFunction() {
    let first, second;
    if (arguments.length === 2) {
        [first, second] = arguments;
    } else if (arguments.length === 1) {
        [second] = arguments;
    }
    if ( ! first) {
        // first is undefined, so only second argument was passed.
    }
}

ES6 destructuring makes code clean and suitable for scaling.

It's not necessary to name any of the parameters. you can simply say:

if (arguments.length < 2)  // There are no optional parameters

And retrieve each parameter via arguments[i]. The function is found at arguments[arguments.length - 1].

On a minor note, the typeof operator always returns a string, so == can be used instead of ===

ArgueJS:

function range(){ 
  arguments = __({start: [Number, 0], stop: Number, step: [Number, 1]});
  for(var i = arguments.start; i < arguments.stop; i += arguments.step)
    console.log(i);
}

Become even prettier with ES6:

function range(){ 
  {start, stop, step} = __({start: [Number, 0], stop: Number, step: [Number, 1]})
  for(var i = start; i < stop; i += step)
    console.log(i);
}

Or CoffeeScript:

range = ->
  {start, stop, step} = __ {start: [Number, 0], stop: Number, step: [Number, 1]}
  console.log i for i in [start...stop] by step

If you really want to pass multiple optional parameters first, you could do it like this:

function myFunction() {
    alert("optional:");
    for(var i=0; i<arguments.length-1) alert(arguments[i]);
    alert("function: "+ arguments[arguments.length-1]);
}

But It's rather ugly. I think you should just put the optional stuff at the end.

 function test(num = 1) { console.log(typeof num) } test() // 'number' (num is set to 1) test(undefined) // 'number' (num is set to 1 too) // test with other falsy values: test('') // 'string' (num is set to '') test(null) // 'object' (num is set to null)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

A simple functional approach:

Idea:

  1. Write a function myFunction(required, optional) where the optional argument is last, and thus easy to deal with.
  2. Use function appendFirstArgumentIf() to create a new function myNewFunction(optional, required) out of myFunction.
  3. Function appendFirstArgumentIf calls myFunction with the first argument moved to the last position if it passes a testfunction, otherwise it doesn't change the arguments.

_

 /**
 * @param {function} fn(a,...b)
 * @param {function} argumentTest - a function to test the first parameter
 * @return {function} returns a function that passes its first argument into 
  argumentTest. If argumentTest returns true, fn is called with the first 
  argument shifted to the last position else fn is called with the arguments 
   order unchanged
 */
function appendFirstArgumentIf (fn,argumentTest){
   return function(a,...b){
       return argumentTest(a) ? fn(...b,a) : fn(a,...b) 
   }
}

usage:

function myFunction(callback, string){
    string = string ? string : 'Default Message'
    callback(string)
}

var myNewFunction = appendFirstArgumentIf(myFunction,
                                          firstArgument=> typeof firstArgument !== 'function')


myNewFunction('New Message', console.log)  // using console.log as callback function
//>>>New Message
myNewFunction(console.log)
//>>>Default Message

also possible with this:

function mySecondFunction(callback, name, string){
string = string ? string : 'Welcome'
callback(string, name)
}

var myLatestFunction = appendFirstArgumentIf(mySecondFunction,
                                      firstArgument=> typeof firstArgument !== 'function')


myLatestFunction('Hello', console.log, 'John')
//>>>Hello John
myLatestFunction(console.log, 'John')
//>>>Welcome John

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM