1

I'm trying to change function arguments in javascript.

f = function(){
    console.log(a,b,c);
};

SetArgumentList( f, ["a", "b", "c"] );

f(1,2,3);
// should print "1 2 3"

// [edit]
// alternatively SetArgumentList could also work like
f = SetArgumentList( f, ["a", "b", "c"] );

Is there some solid way of doing this?


Where do I need it?... basically I'm trying to add type checked functions:

Object.prototype.method = function( name, typedef, func ){ ... }

function Thing(){};

Thing.method("log", 
    { arr: Array, str: String }, 
    function(){
        console.log(arr, str);
    });

t = new Thing();
t.log([1,2,3], "ok");
t.log("err", "ok"); // <-- causes an exception

// I know I can do this way
Thing.method("log", 
    [Array, String], 
    function(arr, str){
        console.log(arr, str);
    });
// but that's harder to read

NOTE! I know how to do type checking, but not the new function construction.

7
  • 1
    So you're trying to break lexical scoping? Why? Commented Nov 26, 2011 at 13:27
  • 1
    You mean... "should pring 1,2,3"? Anyway, why would you need to do this? By not giving the arguments in the correct place you lose their ordering. Is "arr' supposed to be the first or the second argument? What about "str"? The array version that is supposedly harder to read is both simpler and more in line with the rest of the language. Commented Nov 26, 2011 at 13:36
  • @delnan nope I'm not trying to break lexical scoping, I'm trying to change modify function definition. Commented Nov 26, 2011 at 13:40
  • 1
    @egon: Correct me if I'm wrong, but the way I read your question, you want to manipulate which variables are considered locals (arguments are locals) outside of the function definition. Is that correct? If so, well, that's against lexical scoping. Lexical scoping means, among other things, that the scope of all bindings is determined by the function definition and set in stone afterwards. Commented Nov 26, 2011 at 13:43
  • 1
    @egon: Only the variant of making them global could work. But that's clearly not acceptable. More generally, you'd have to inject these bindings into the scope a, b and c are bound to, as you cannot change what scope they are bound to. But the only scope you can inject into like that is the global scope, oh well. Inlining lexically would be compatible with lexical scoping in theory (you'd define a new function = re-compute bindings) but it's impossible. It would also be very fragile (remember C preprocessor macros?). Long story short, it's far easier to just play by the rules. Commented Nov 26, 2011 at 13:58

6 Answers 6

3

As delnan said in the comments, it seems like what you're trying to do is essentially "rename" the variables which are local to a function. Like he said, this is not possible (and for good reason too! Could you imagine debugging that? maaan...)

Anyway, I don't know exactly why you'd want that, but Javascript is a flexible language and you could probably get close using a more sane method. It's hard to know exactly what you're trying to achieve, but perhaps this information might get you on the right track:

  • The arguments which are passed to a function at call time are referenced in a variable named arguments.

    function f() {
      console.log(arguments);
    }
    f(); // []
    f(1, 2); // [1, 2]
    
  • You can call a function with an arbitrary list of arguments using .apply, which is a method on the Function prototype. It takes 2 parameters. The first is the object which will be this inside the function call, and the second is an array of arguments.

    f.apply(null, []); // []
    f.apply(null, [1, 2, 3]); [1, 2, 3]
    

Applying this in your situation, perhaps this is what you're after:

function f() {
    console.log.apply(console, arguments);
}
Sign up to request clarification or add additional context in comments.

Comments

1

Tested in IE7,8,9, opera, chrome, firefox and safari. Uses evil in the background, but I cannot see any other way if you must rename arguments.

(function(){
var decompileRE = /function\s*\([\s\S]*?\)\s*\{([\s\S]*)/,
    lastBrace = /\}[^}]*$/;

    window.SetArgumentList = function( fn, argNames ) {
    var match

        if( !( match = fn.toString().match( decompileRE ) ) ) {
        return fn;
        }

    argNames.push( match[1].replace( lastBrace, "" ) );


    return Function.apply( null, argNames );
    };

})()

f = function(){
    console.log(a,b,c);
};

f = SetArgumentList( f, ["a","b","c"] );

console.log(f);

Logs this in all browsers mentioned above:

function anonymous(a,b,c) {

    console.log(a,b,c);

}

Comments

1

I've got a simpler solution similar to the accepted answer for anyone out there that is looking. Works in Chrome, Firefox, Safari, IE7+

Solution

function SetArgList(fn, args) {

   var fnbody = fn.toString().replace(
         /^\s*function\s*[\$_a-zA-Z0-9]+\(.*\)\s*\{/, //BEWARE, removes original arguments
         ''
      ).replace(
        /\s*\}\s*$/,
        ''
      );

   return new Function(args, fnbody) 

}

How to use

Just redefine your original function like this using SetArgList:

function main() {
   alert(hello);
   alert(world);
}

main = SetArgList(main, 'hello,world');

main('hello', 'world');

In my solution there's no need for an array but you could edit it, my function only requires argument names separated by a comma.

Comments

0

you can use .apply: this works for me:

 f = function(a,b,c){ 
    console.log(a,b,c); 
}; 
var args = ["a", "b", "c"];  
f.apply(this, args); //print "abc"

using arguments:

f = function(){ 
    for(var key in arguments) {
        console.log(arguments[key]);
    }
}; 
var args = ["a", "b", "c"];  
f.apply(this, args); 

it's that you looking?

2 Comments

I'm aware of that way, the point of the whole thing is modifying the function declaration with code, not by hand.
I'm wrong on post above. what you want do it's totally different of that I imagined. you want handling local variables of a function from another function. I think it's not possible.
0

As said before, you can't do it the way you want, you're breaking lexical scoping. However, here a minimalism version of the way you can implement it (a lot of improvements should be done !). The only thing required is that you function has the named parameter in arguments.

function getType( el ) {
    // TODO
    return "";
}

function addMethod( obj, name, typedef, func ) {
    var argumentsLength = typedef.length;

    obj[ name ] = function() {
        var len = arguments.length, i = 0;

        if( argumentsLength != len )
        {
            throw new TypeError( "Wrong number of arguments for method " + name );
        }

        for( i = 0; i < len; i++ )
        {
            // TODO better type checking
            if( getType( arguments[i] ) != getType( typedef[i] ) )
            {
                throw new TypeError( "Wrong type for arguments number " + i + " for method " + name );
            }
        }

        return func.apply( obj, arguments );

    };
};


var o = {};

addMethod( o, "log", [Array, String], function( arr, str ) {
      // arguments MUST be explicitly declared above
      console.log( arr, str );
});
o.log( ["a"], "b" ); // OK
o.log( "a", "b" ); // TypeError
o.log( ["a"], "b", "c" ); // TypeError

3 Comments

Read the question properly, I know how to do type-checking.
Read the answer so. You can not break lexical scoping, so you can't do what you want. That's easy. The only way you could do so is by relying on bad practices such as eval method and browser quirks. If you want do to such a thing, I think you want your code to be "solid" (can't call a function with wrong parameter), so you're probably a smart guy. But relying on such quirks show off you're maybe not that smart...
It's about knowing the language and possibilities, benefits and cons. And the problem I didn't know was how to modify argument list that works with all browsers.
0

I found a solution that only works on webkit browsers:

f = function(){ console.log(a,b,c); };
fx = eval(
    f.toString().replace(
        /^function [^\(]*\(\)/,
        "var __temp = function (a,b,c)") 
        + "; __temp");
fx(1,2,3);

This can also be generalized.

[edit] This works for other browsers as well, memory let me down - comments // /**/ in some browsers get discarded.

2 Comments

Don't you know that eval is evil ?
@pomeh the other alternative I know is global scope which is much worse, also I could've used Function and it would've seen less evil (although there's no practical difference)... There are two reasons why eval is usually considered evil, performance (not an issue as it's done only once), injection (not an issue as it's only used for functions declared).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.