tags:

views:

83

answers:

4

I have a bunch of functions (methods of a class actually) and I'm writing a method that will record the interactions with other methods in an array. so for example :

 foo = Base.extend ({
      a : function (a1,a2){
            ....
      },
      b:function(b1,b2){
        ...
      },
      history : function(){ ... }
  })

to simplify the history method, I'd like to read the name of the optional arguments and add them to the array, so for example if the method a is called, I want to record a1,a2 ... so basically, is there any way to read the name of the optional arguments list of an array in javascript ?

here is the code :

var element = Base.extend({
constructor : function() {
    if(arguments.length==1){
        ...
    }else{
        ...
    }
},
setLocation : function (top, left){
    oldArgs = [this.top,this.left];
    this.top = top;
    this.left = left;
    return(oldArgs);
},
setAspects : function (width, height){
    oldArgs = [this.width,this.height]
    this.width = width;
    this.height = height;
    return(oldArgs);
},
draw : function (page){
    ...
},
delet : function () {
        ...
},
$ : function(method,args){
    var oldArgs = this[method].apply(this,args);
    this.history(method,oldArgs);
    Nx.pages[this.page].modified = true;
},
history : function (method,args){
    Nx.history[Nx.history.length]=[this.id,method,args]
}

})

so in this class, if I want to call any method, I'll pas it through the $ method, and it will call the history method, so far what I've done is for example in the setLocation method it will return the old arguments and I will story them in my array Nx.history, but it's easier to factorise all of these "return" calls in the methods, and add a line to the $ method , that reads the name of the expected arguments of the method, and send it to the history method, so something like this :

$ : function(method,args){
    this[method].apply(this,args);
    **var oldArgs = this[method].arguments // get the list of argument names here
    $.each(oldArgs, function(value) { Args[Args.length] = this.value //get the stored value in the class
    })
    this.history(method,Args); // and pass it to the history**
    Nx.pages[this.page].modified = true;
}
+2  A: 

2.0

The idea with this newer version is to define the properties that you want to record for the object beforehand. It's a level of duplication, but it's only a one time thing. Then, in the constructor, create property setters for each of these properties. The setter does some side work along with setting the property. It pushes the arguments name and value onto a stack, and assigns the properties. The $ method is supposed to call dispatch the call to the appropriate method. Once the call is complete, the stack will be populated with the parameters that were set in that function. Pop off each parameter from that stack, until the stack is empty. Then call history with the method name, and the parameters that we just popped off the stack. Please let me know if this doesn't make any sense, I might have to word it better.

See an example here.

Here's a code example written in MooTools which is slightly similar to your Base class.

var Device = new Class({
    _properties: ['top', 'left', 'width', 'height'],

    _parameterStack: [],

    initialize: function() {
        this._createPropertyAccessors();
    },

    _createPropertyAccessors: function() {
        this._properties.each(function(property) {
            Object.defineProperty(this, property, {
                enumerable: true,
                configurable: true,
                set: function(value) {
                    var o = {};
                    o[property] = value;
                    // push the parameter onto the stack
                    this._parameterStack.push(o);
                }.bind(this)
            });
        }.bind(this));
    },

    // method stays unchanged
    setLocation: function(top, left) {
        this.top = top;
        this.left = left;
    },

    setAspects: function(width, height) {
        this.width = width;
        this.height = height;
    },

    // dispatches call to method
    // pops off the entire stack
    // passed method name, and emptied stack arguments to history
    $: function(method, args) {
        this[method].apply(this, args);
        var argsStack = [];
        while(this._parameterStack.length) {
            argsStack.push(this._parameterStack.pop());
        }
        this.history(method, argsStack);
    },

    history: function(method, args) {
        console.log("%s(%o) called", method, args);
    }
});

1.0

The arguments passed to a JavaScript function are accessible through an array-like object named arguments which is available for all functions.

When calling history, pass the arguments object to it.

a: function(a1, a2) {
    this.history.apply(this, arguments);
}

history will then be invoked as if it was called with two arguments with this being the base object - foo unless you call it with a different context.

I am not sure how Base plays into this. You would have to elaborate more as to the role of Base here.

Here' s a simple example:

var foo = {
    a: function(a1, a2) {
        this.history.apply(this, arguments);
    },
    history: function() {
        console.log("history received " + arguments.length + " arguments.");
    }
};

foo.a("hello", "world"); // history received 2 arguments

Also note that although a has two named parameters here, we can still pass it any number of arguments, and all of them will be passed to the history method in turn. We could call a as:

foo.a(1, 2, 3); // history received 3 arguments
Anurag
Great answer, but it's unclear from the question whether he wanted the *name* of each argument or the *value* of each argument... ugh.
Mark Eirich
I see now @Mark. I don't think it's possible to get the *names* of formal function parameters without already knowing them in advance.
Anurag
yeahh great answer, but I want the name of each argument not their value
hakim-sina
@hakim-sina - If these objects are merely setters, then you could reduce a lot of code using some other techniques. I might have a cool solution for you, if you're willing to support only ECMAScript 5 browsers - which is only Chrome, and Firefox nightly at the moment. However, just to let you know there is no way to access the names of the formal arguments of a function.
Anurag
that'll be great, because my plan is to have 2 versions , an HTML5 one, and a normal one, so your ECMAScript 5 one will be really useful, thanks :)
hakim-sina
@hakim-sina - updated, it might be a little buggy, but I hope it gets the idea across :)
Anurag
thanks alot , I wrote a code based on your solution and it was pretty much what I wanted, but then harto's solution was "exactly" what I wanted, so I'd go with his solution , but I really appreciate your help :)
hakim-sina
+1  A: 

Something like this should work. When you want to access the arguments that have been used you can loop through the foo.history array which contains the arguments list.

var foo = {
    storeArgs: function (fn) { 
                 return function() { 
                    this.history += arguments
                    return fn.apply(null, arguments); 
                 }
              },

    a: storeArgs(function(a1, a2) {
        alert(a1+a2);
    }),

    history: []
};

I read your updated post. What do you mean by "names?" Variable names? For example, if someone called a(1337), would you want ["a1"] to be added to the array? And if a(1337, 132) was called ["a1", "a2"] would be added? I don't think there's any sane way to do that.

This is the best I can do. You will have to include a list of parameter names when defining your functions using the storeArgs function.

var foo = {
    storeArgs: function (params, fn) { 
                 return function() { 
                    var arr = [];
                    for (var i = 0; i < arguments.length; i++) {
                       arr.push(params[i]);
                    }
                    this.history += arr;
                    return fn.apply(null, arguments); 
                 }
              }

    a: storeArgs(["a1", "a2"], function(a1, a2) {
        alert(a1+a2);
    }),

    history: []
};

Let me know if it works.

CD Sanchez
yeahh, I want the names, so as u said [a1, a2 ] for example ... I hope I find a sane way, it will reduce alotta codes for me :s
hakim-sina
@hakim-sina: Would an index be acceptable? For example, [0, 1] to represent [a1, a2]? Or maybe [1] or [n] where n is a number, to represent that the optional parameters 0-n were included in the function call.
CD Sanchez
well, i used a1,a2 as examples and my actual class as u can see has names for its properties, such as : height,width, src etc ...
hakim-sina
@hakim-sina: I edited my post, let me know if that'll work for you. If not, tough luck :).
CD Sanchez
A: 

This works in IE, but I'm not sure about other browsers...

Function.prototype.params = function() {
    var params = this.toString().split(/(\r\n)|(\n)/g)[0];
    params = params.trim().replace(/^function.*?\(/, "");
    params = params.match(/(.*?)\)/)[1].trim();

    if (!params) {
        return([]);
    }

    return(params.split(/, /g));
};

function Test(a, b) {
  alert(Test.params());
}

Test();
Marcus Pope
Not only did harto just beat me to the answer, but their solution is better. I just grabbed my example from an old library.
Marcus Pope
+2  A: 

I'm not 100% sure what you're asking for - a way to extract the formal parameter names of a function?

I have no idea if this would work or not, but could you parse the string representation of the function to extract the parameter names?

It would probably be a very lame solution, but you might be able to do something like:

function getArgNames(fn) {
    var args = fn.toString().match(/function\b[^(]*\(([^)]*)\)/)[1];
    return args.split(/\s*,\s*/);
}
harto
I don't get the regex part `.+?`. I replaced it by `[^\(]*` because getArgNames didn't work in IE.
Protron
I mean, it didn't work in IE for anonymous functions which had no spaces before the parenthesis. e.g. `var fn = function() {`
Protron
Oh right, I hadn't considered that case. Cheers
harto