开发者

Javascript Handling Calling Functions with Named Args and Normal

If I have the function:

function(foo, bar, baz);

And I want to allow for both named arguments and normal function calls, what is the best way of handling this? In php you can extract the variables into the local namespace but as far as I know the only way to handle this in javascript is by handling both scenarios separately. I've given a code example below:

function(foo, bar, baz)
{
    if(typeof(foo) == 'object') // Named args
    {
        alert(foo.foo);
        alert(foo.bar);
        alert(foo.baz);
    }
    else
    {
        alert(foo);
        alert(bar);
        alert(baz);
    }
}

myFunc('a', 'b', 'c');
myFunc({ foo: 'a', 开发者_Go百科bar: 'b', baz: 'c' });

Any javascript gurus out there who can teach me the ways of javascriptFu?


Since you cannot access the local scope dynamically (without evil eval), you should consider the following approach:

var myFunc = function (foo, bar, baz) {
    if (typeof(foo) === 'object') {
        bar = foo.bar;
        baz = foo.baz;
        foo = foo.foo; // note: foo gets assigned after all other variables
    }

    alert(foo);
    alert(bar);
    alert(baz);
};

You simply translate the named args to regular variables manually. After that, your code will run for both cases without changes.


Do it with elegance:

var myFunc = (function (foo, bar, baz) {
                   // does whatever it is supposed to do
               }).
    withNamedArguments({foo:"default for foo", bar:"bar", baz:23 });

myFunc({foo:1}); // calls function(1, "bar", 23)
myFunc({});  // calls function("default for foo", "bar", 23);
myFunc({corrupt:1}); // calls function({corrupt:1})
myFunc([2,4], 1);  //calls function([2,4], 1)

Even this one works

Array.prototype.slice =
    Array.prototype.slice.withNamedArguments({start:0, length:undefined});

[1,2,3].slice({length:2}) //returns [1,2]
[1,2,3].slice(1,2) //returns [2,3]

... or here, parseInt()

parseInt = parseInt.withNamedArguments({str:undefined, base:10});
parseInt({str:"010"}); //returns 10

Just enhance the Function object:

Function.prototype.withNamedArguments = function( argumentList ) {
    var actualFunction = this;
    var idx=[];
    var ids=[];
    var argCount=0;
    // construct index and ids lookup table
    for ( var identifier in argumentList ){
        idx[identifier] = argCount;
        ids[argCount] = identifier;

        argCount++;
    }

    return function( onlyArg ) {
        var actualParams=[];
        var namedArguments=false;

        // determine call mode
        if ( arguments.length == 1 && onlyArg instanceof Object ) {
            namedArguments = true;
            // assume named arguments at the moment
            onlyArg = arguments[0];
            for ( name in onlyArg )
                if (name in argumentList ) {
                    actualParams[idx[name]] = onlyArg[name];
                } else {
                    namedArguments = false;
                    break;
                }
        }
        if ( namedArguments ) {
            // fill in default values
            for ( var i = 0; i < argCount; i++ ) {
                if ( actualParams[i] === undefined )
                    actualParams[i] = argumentList[ids[i]];
            }
        } else 
            actualParams = arguments;

        return actualFunction.apply( this, actualParams );
    };
};


This is always awkward and not very rigourous but it's far safer to check the arguments for the absence of data than for a particular positive expectation, especially typeof on object.

Some variation on the below, the strategy here being to translate a DTO style input into a named argument style input (the opposite is also reasonable but I find less obvious). The advantage of this strategy is once you've passed this translation block, the rest of the code doesn't care how you got there.

// translate to named args - messy up front, cleaner to work with
function(foo, bar, baz) 
{
    // Opt 1: default to named arg, else try foo DTO
    bar = (typeof(bar) != 'undefined' ? bar : foo.bar);

    // Opt 2: default to named arg, else check if property of foo, else hard default (to null)
    baz = (typeof(baz) != 'undefined' ? baz : typeof(foo.baz) != 'undefined' ? foo.baz : null);

    // the first argument is always a problem to identify in itself
    foo = (foo != null ? typeof(foo.foo) != 'undefined' ? foo.foo : foo : null);
}

// translate to object - cleaner up front, messier to work with
function(foo, bar, baz) 
{
    var input = (typeof(foo.foo) != 'undefined' ? foo : { 'foo' : foo, 'bar' : bar, 'baz' : baz });
}

The first arg (foo here) is always a problem because you expect it to be in one of two complex states (where the other args are always a single complex state or undefined) and you cannot process it until you've dealt with all the other args because obviously once you've changed it it's unreliable to use it for initialising anything else.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜