Is it possible to reflect the arguments of a Javascript function? - javascript

Is it possible to get all of the arguments a Javascript function is written to accept? (I know that all Javascript function arguments are "optional")? If not, is it possible to get the number of arguments? For example, in PHP, one could use:
$class = new ReflectionClass('classNameHere');
$methods = $class->getMethods();
foreach ($methods as $method) {
print_r($method->getParameters());
}
... or something like that, I haven't touched PHP in a while so the example above may not be correct.
Thanks in advance!
EDIT: Unfortunately, I have to be able to get the arguments outside of the body of the function... Sorry for the lack of clarification, but thanks for the current answers!

This new version handles fat arrow functions as well...
args = f => f.toString ().replace (/[\r\n\s]+/g, ' ').
match (/(?:function\s*\w*)?\s*(?:\((.*?)\)|([^\s]+))/).
slice (1,3).
join ('').
split (/\s*,\s*/);
function ftest (a,
b,
c) { }
let aftest = (a,
b,
c) => a + b / c;
console.log ( args (ftest), // = ["a", "b", "c"]
args (aftest), // = ["a", "b", "c"]
args (args) // = ["f"]
);
Here is what I think you are looking for :
function ftest (a,
b,
c) { }
var args = ftest.toString ().
replace (/[\r\n\s]+/g, ' ').
match (/function\s*\w*\s*\((.*?)\)/)[1].split (/\s*,\s*/);
args will be an array of the names of the arguments of test i.e. ['a', 'b', 'c']
The value is args will be an array of the parameter names if the ftest is a function.
The array will be empty if ftest has not parameters. The value of args will be null
if ftest fails the regular expression match, i.e it is not a function.

it is possible get all the formal parameter name of a javascript:
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function formalParameterList(fn) {
var fnText,argDecl;
var args=[];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
var r = argDecl[1].split(FN_ARG_SPLIT);
for(var a in r){
var arg = r[a];
arg.replace(FN_ARG, function(all, underscore, name){
args.push(name);
});
}
return args;
}
this can be tested this way :
var expect = require('expect.js');
expect( formalParameterList(function() {} )).to.eql([]);
expect( formalParameterList(function () {} )).to.eql([]);
expect( formalParameterList(function /* */ () {} )).to.eql([]);
expect( formalParameterList(function (/* */) {} )).to.eql([]);
expect( formalParameterList(function ( a, b, c ,d /* */, e) {} )).to.eql(['a','b','c','d','e']);
Note:
This technique is use with the $injector of AngularJs and implemented in the annotate function. (see https://github.com/angular/angular.js/blob/master/src/auto/injector.js and the corresponding unit test in https://github.com/angular/angular.js/blob/master/auto/injectorSpec.js )

Suppose your function name is foo
Is it possible to get all of the arguments a Javascript function is
written to accept?
arguments[0] to arguments[foo.length-1]
If not, is it possible to get the number of arguments?
foo.length would work

check only required chars. with func.toString().regex you checked full length.so if function is class with 500 lines of code...
function getParams(func){
var str=func.toString();
var len = str.indexOf("(");
return str.substr(len+1,str.indexOf(")")-len -1).replace(/ /g,"").split(',')
}

HBP's answer is what most people are looking for, but if you're the one defining the function, you can also assign a property to the function object. For example,
a.arguments = ['foo', 'bar', 'baz']
function a(foo, bar, baz) {
// do stuff
}
This is debatably more clear, but you'll have to write your arguments twice.

Now when you say outside the body of the function I can only imagine that you want to know what the names of the parameters are? Because as far as the values go, you already know what arguments you are passing. Other answers have said you can get the length of the function, which is the number of parameters it explicitly declares. Now if you want to know the names outside the function, how about the toString hack?
Consider
function f(oh, hi, there) {
return hi + there / oh;
}
Then
alert(f);
What do you see? RIght, just regex them out! Okay, SORRY to bring this up. Perhaps it is not standard ECMAScript, but it, uh, works in Chrome....

args = f => f.toString ()
.replace( /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,'')
.replace(/(\r\n\t|\n|\r\t)/gm,"")
.trim()
.match (/(?:\w*?\s?function\*?\s?\*?\s*\w*)?\s*(?:\((.*?)\)|([^\s]+))/)
.slice (1,3)
.join ('').replace(/\s/g, '').
split (/\s*,\s*/);
/*Test*/
console.log(args((a,b)=>a+b));
console.log(args(function(c,d){return c+d;}));
console.log(args(async function(a,b,c){/**/}));
console.log(args(function* (a,b,c,d){/**/}));
console.log(args(function name(s1,s2){}));
console.log(args(function name(/*comment 1*/ s3/*comment2*/,s4//
){}));
console.log(args(async function* name(/*comment1*/ s5/*comment2*/,s6){}));
console.log(args(async function * name(/*comment1*/ s7/*comment2*/,s8){}));
console.log(args(async function *name(/*comment1*/ s9/*comment2*/,s10){}));

JavaScript is a dialects of ECMAScript, according to ECMAScript standard, a function is also a object, and when a function is called, function can access arguments object, this arguments is array-like object, it has length property, so you can use arguments.length to traverse all arguments passed to this function.
visit http://interglacial.com/javascript_spec/a-13.html#a-13.2.1 for more details.

Related

Array spread alternative for Internet Explorer [duplicate]

I'd like to call a function using an array as parameters:
const x = ['p0', 'p1', 'p2'];
call_me(x[0], x[1], x[2]); // I don't like it
function call_me (param0, param1, param2 ) {
// ...
}
Is there a better way of passing the contents of x into call_me()?
const args = ['p0', 'p1', 'p2'];
call_me.apply(this, args);
See MDN docs for Function.prototype.apply().
If the environment supports ECMAScript 6, you can use a spread argument instead:
call_me(...args);
Why don't you pass the entire array and process it as needed inside the function?
var x = [ 'p0', 'p1', 'p2' ];
call_me(x);
function call_me(params) {
for (i=0; i<params.length; i++) {
alert(params[i])
}
}
In ES6 standard there is a new spread operator ... which does exactly that.
call_me(...x)
It is supported by all major browsers except for IE.
The spread operator can do many other useful things, and the linked documentation does a really good job at showing that.
Assuming that call_me is a global function, so you don't expect this to be set.
var x = ['p0', 'p1', 'p2'];
call_me.apply(null, x);
As #KaptajnKold had answered
var x = [ 'p0', 'p1', 'p2' ];
call_me.apply(this, x);
And you don't need to define every parameters for call_me function either.
You can just use arguments
function call_me () {
// arguments is a array consisting of params.
// arguments[0] == 'p0',
// arguments[1] == 'p1',
// arguments[2] == 'p2'
}
While using spread operator we must note that it must be the last or only parameter passed. Else it will fail.
function callMe(...arr){ //valid arguments
alert(arr);
}
function callMe(name, ...arr){ //valid arguments
alert(arr);
}
function callMe(...arr, name){ //invalid arguments
alert(arr);
}
If you need to pass an array as the starting argument you can do:
function callMe(arr, name){
let newArr = [...arr];
alert(newArr);
}
Function arguments may also be Arrays:
function foo([a,b,c], d){
console.log(a,b,c,d);
}
foo([1,2,3], 4)
of-course one can also use spread:
function foo(a, b, c, d){
console.log(a, b, c, d);
}
foo(...[1, 2, 3], 4)
Note this
function FollowMouse() {
for(var i=0; i< arguments.length; i++) {
arguments[i].style.top = event.clientY+"px";
arguments[i].style.left = event.clientX+"px";
}
};
//---------------------------
html page
<body onmousemove="FollowMouse(d1,d2,d3)">
<p><div id="d1" style="position: absolute;">Follow1</div></p>
<div id="d2" style="position: absolute;"><p>Follow2</p></div>
<div id="d3" style="position: absolute;"><p>Follow3</p></div>
</body>
can call function with any Args
<body onmousemove="FollowMouse(d1,d2)">
or
<body onmousemove="FollowMouse(d1)">
you can use the spread syntax
for example:
function print(...inpu){
console.log(...inpu)
}
var arry = ['p0','p1','p2']
print(...arry)
here is the link: modzilla spread syntax refrence document
you can use spread operator in a more basic form
[].concat(...array)
in the case of functions that return arrays but are expected to pass as arguments
Example:
function expectArguments(...args){
return [].concat(...args);
}
JSON.stringify(expectArguments(1,2,3)) === JSON.stringify(expectArguments([1,2,3]))
The answer was already given, but I just want to give my piece of cake. What you want to achieve is called method borrowing in the context of JS, that when we take a method from an object and call it in the context of another object. It is quite common to take array methods and apply them to arguments. Let me give you an example.
So we have "super" hashing function which takes two numbers as an argument and returns "super safe" hashed string:
function hash() {
return arguments[0]+','+arguments[1];
}
hash(1,2); // "1,2" whoaa
So far so good, but we have little problem with the above approach, it is constrained, only works with two numbers, that is not dynamic, let's make it work with any number and plus you do not have to pass an array (you can if you still insist). Ok, Enough talk, Let's fight!
The natural solution would be to use arr.join method:
function hash() {
return arguments.join();
}
hash(1,2,4,..); // Error: arguments.join is not a function
Oh, man. Unfortunately, that won’t work. Because we are calling hash(arguments) and arguments object is both iterable and array-like, but not a real array. How about the below approach?
function hash() {
return [].join.call(arguments);
}
hash(1,2,3,4); // "1,2,3,4" whoaa
The trick is called method borrowing.
We borrow a join method from a regular array [].join. And use [].join.call to run it in the context of arguments.
Why does it work?
That’s because the internal algorithm of the native method arr.join(glue) is very simple.
Taken from the specification almost “as-is”:
Let glue be the first argument or, if no arguments, then a comma ",".
Let result be an empty string.
Append this[0] to result.
Append glue and this[1].
Append glue and this[2].
…Do so until this.length items are glued.
Return result.
So, technically it takes this and joins this[0], this[1] …etc together. It’s intentionally written in a way that allows any array-like this (not a coincidence, many methods follow this practice). That’s why it also works with this=arguments.
There's a better way using JSON not an Array!
// Call a function with a Json Key / Value Pair:
sendMail({param1: p1, param2: p2});
// Function definition and usage of value pairs:
function sendMail(data){
var parameter1 = data.param1;
var parameter2 = data.param2;
}

JavaScript can return argument?

I have two functions:
function g(data, i){}
function f(data, i){}
I want to call them like:
var myVar = f(g(data, i));
So g() should return (data, i).
Of course, it doesn't work. Is there some way to do this?
Returning multiple values is not supported by Javascript (at least not in the implementations that most browsers have).
Do the following:
function g(data, i) {
// some code
return {
data: data,
i: i
};
}
var o = g(data, i);
f(o.data, o.i);
Note:
As mentioned by Phil in the Question's comments, if i is not modified in g(), then you can just call f(g(data, i), i) without modifying function g.
there is, if you use apply and make your functions return arrays of values.
function g(a, b) { return [a, b]; }
function f(c, d) { return [c, d]; }
console.log(f.apply(null, g("thing", "cow")));
This is, however, a great sign of a bad programmer if used for this purpose, so you don't want to do this. If you have functions that take two arguments, pass two arguments:
function g(a, b) { return [a, b]; }
function f(c, d) { return [c, d]; }
var result = g("thing", "cow");
f(result[0], result[1]);
because you already know it takes, and generates, a pair of values, or use the object pattern (rather than an array) that Der Flatulator shows you.
Not exactly like you have it, however you can trick it since arguments is "array-like" ... behold :D
var g = function(a,b) {return [a,b]};
var f = function(a, b) {console.log("f evaluating", a, b)};
f.apply(null,g(1,2));
gives you:
bar 1 2
Alternatively you could do something hacky in your (f) function to split the values of the first argument if there is only one argument passed in, but I like the apply better, especially cuz this is horrible if for example your a argument is an array by itself.
Works for any number of argument length
function g(){
return [].slice.call(arguments);
}
function f(data, i){
}
f.apply(undefined, g(data, i));

Set undefined parameters via arguments

I'm trying to write a function that corrects the arguments of a function based on previously specified optional parameters. I've come to a problem though. It seems that I can't set variables via the arguments array unless they have been defined in any way before. The code below shows an example of the problem I'm facing.
function foo(a, b, c) {
arguments[0] = "lorem";
arguments[1] = "ipsum";
arguments[2] = "dolor";
console.log([a, b, c]);
}
foo(null); // ["lorem", undefined, undefined]
foo(null, null); // ["lorem", "ipsum", undefined]
foo(null, null, null); // ["lorem", "ipsum", "dolor"]
When logging arguments the result is always ["lorem", "ipsum", "dolor"] though.
Is there any way to solve this problem ?
I can't set a, b and c directly because a function called in foo wouldn't have access to these names.
My goal would look like something like this:
function foo(a, b, c) {
var rules = [];
// Rule for optional parameter 1 (b)
// If it equals true the value of b is shifted to the next parameter (c)
rules[1] = function(val) { return val !== "ipsum"; };
optionalize(rules);
console.log([a, b, c]);
}
foo("lorem", "dolor"); // ["lorem", undefined, "dolor"];
The arguments array isn't really an array but an "array-like" object. You can't change its length.
What you try to do is usually done using
a = a || "lorem";
or, if you don't want to replace any "falsy" argument, using
if (typeof a === "undefined") a = "lorem";
It is a bit peculiar, but is this what you had in mind?
function optionalize(fn, options) {
var i, key, rule;
for(i = 0; i < options.length; i += 1) {
key = fn.placement[i];
rule = fn.ruleset[key];
// If the rule exists and returns true, shift value to the right.
if(rule && rule(options[i])) {
options[i+1] = options[i];
options[i] = undefined;
}
// Assign the numeric index to an alphabet key.
// Depending on your use case, you may want to substitute a plain Object here and return that, instead of adding non-numeric properties to an Array.
options[key] = options[i];
}
}
// Test function
function foo(a, opts) {
opts = opts || [];
optionalize(foo, opts);
console.log([a, opts.b, opts.c]);
}
// Optional argument names, in the order that they would be received.
foo.placement = ['b', 'c'];
// Optionalize rules
foo.ruleset = {
b: function (val) { return val !== "ipsum"; }
};
// Demonstration
foo('lorem');
foo('lorem', []);
foo('lorem', ['dolor']);
foo('lorem', ['ipsum', 'dolor']);
As dystroy's answer has already indicated, the arguments variable isn't a real Array, and changing it may not be a good idea. I have provided a solution which does not rely on arguments and fulfills the criteria as far as could be possible using simple JavaScript.
The function foo is specified with a required argument a, followed by an Array of optional arguments named opts. An optionalize specification is set onto foo, through the placement and ruleset properties. The optionalize function takes this information and transforms the array's indices into usable name keys, applying the rules as necessary.
I'm not sure what you're trying to do but something like arguments[0] = a ? a : "lorem" and so on ?
You can convert arguments to an array
function foo () {
var arguments = Array.prototype.slice.call(arguments);
arguments.push(4);
console.log(arguments);
}
foo(1,2,3);
​

Passing an array as a function parameter in JavaScript

I'd like to call a function using an array as parameters:
const x = ['p0', 'p1', 'p2'];
call_me(x[0], x[1], x[2]); // I don't like it
function call_me (param0, param1, param2 ) {
// ...
}
Is there a better way of passing the contents of x into call_me()?
const args = ['p0', 'p1', 'p2'];
call_me.apply(this, args);
See MDN docs for Function.prototype.apply().
If the environment supports ECMAScript 6, you can use a spread argument instead:
call_me(...args);
Why don't you pass the entire array and process it as needed inside the function?
var x = [ 'p0', 'p1', 'p2' ];
call_me(x);
function call_me(params) {
for (i=0; i<params.length; i++) {
alert(params[i])
}
}
In ES6 standard there is a new spread operator ... which does exactly that.
call_me(...x)
It is supported by all major browsers except for IE.
The spread operator can do many other useful things, and the linked documentation does a really good job at showing that.
Assuming that call_me is a global function, so you don't expect this to be set.
var x = ['p0', 'p1', 'p2'];
call_me.apply(null, x);
As #KaptajnKold had answered
var x = [ 'p0', 'p1', 'p2' ];
call_me.apply(this, x);
And you don't need to define every parameters for call_me function either.
You can just use arguments
function call_me () {
// arguments is a array consisting of params.
// arguments[0] == 'p0',
// arguments[1] == 'p1',
// arguments[2] == 'p2'
}
While using spread operator we must note that it must be the last or only parameter passed. Else it will fail.
function callMe(...arr){ //valid arguments
alert(arr);
}
function callMe(name, ...arr){ //valid arguments
alert(arr);
}
function callMe(...arr, name){ //invalid arguments
alert(arr);
}
If you need to pass an array as the starting argument you can do:
function callMe(arr, name){
let newArr = [...arr];
alert(newArr);
}
Function arguments may also be Arrays:
function foo([a,b,c], d){
console.log(a,b,c,d);
}
foo([1,2,3], 4)
of-course one can also use spread:
function foo(a, b, c, d){
console.log(a, b, c, d);
}
foo(...[1, 2, 3], 4)
Note this
function FollowMouse() {
for(var i=0; i< arguments.length; i++) {
arguments[i].style.top = event.clientY+"px";
arguments[i].style.left = event.clientX+"px";
}
};
//---------------------------
html page
<body onmousemove="FollowMouse(d1,d2,d3)">
<p><div id="d1" style="position: absolute;">Follow1</div></p>
<div id="d2" style="position: absolute;"><p>Follow2</p></div>
<div id="d3" style="position: absolute;"><p>Follow3</p></div>
</body>
can call function with any Args
<body onmousemove="FollowMouse(d1,d2)">
or
<body onmousemove="FollowMouse(d1)">
you can use the spread syntax
for example:
function print(...inpu){
console.log(...inpu)
}
var arry = ['p0','p1','p2']
print(...arry)
here is the link: modzilla spread syntax refrence document
you can use spread operator in a more basic form
[].concat(...array)
in the case of functions that return arrays but are expected to pass as arguments
Example:
function expectArguments(...args){
return [].concat(...args);
}
JSON.stringify(expectArguments(1,2,3)) === JSON.stringify(expectArguments([1,2,3]))
The answer was already given, but I just want to give my piece of cake. What you want to achieve is called method borrowing in the context of JS, that when we take a method from an object and call it in the context of another object. It is quite common to take array methods and apply them to arguments. Let me give you an example.
So we have "super" hashing function which takes two numbers as an argument and returns "super safe" hashed string:
function hash() {
return arguments[0]+','+arguments[1];
}
hash(1,2); // "1,2" whoaa
So far so good, but we have little problem with the above approach, it is constrained, only works with two numbers, that is not dynamic, let's make it work with any number and plus you do not have to pass an array (you can if you still insist). Ok, Enough talk, Let's fight!
The natural solution would be to use arr.join method:
function hash() {
return arguments.join();
}
hash(1,2,4,..); // Error: arguments.join is not a function
Oh, man. Unfortunately, that won’t work. Because we are calling hash(arguments) and arguments object is both iterable and array-like, but not a real array. How about the below approach?
function hash() {
return [].join.call(arguments);
}
hash(1,2,3,4); // "1,2,3,4" whoaa
The trick is called method borrowing.
We borrow a join method from a regular array [].join. And use [].join.call to run it in the context of arguments.
Why does it work?
That’s because the internal algorithm of the native method arr.join(glue) is very simple.
Taken from the specification almost “as-is”:
Let glue be the first argument or, if no arguments, then a comma ",".
Let result be an empty string.
Append this[0] to result.
Append glue and this[1].
Append glue and this[2].
…Do so until this.length items are glued.
Return result.
So, technically it takes this and joins this[0], this[1] …etc together. It’s intentionally written in a way that allows any array-like this (not a coincidence, many methods follow this practice). That’s why it also works with this=arguments.
There's a better way using JSON not an Array!
// Call a function with a Json Key / Value Pair:
sendMail({param1: p1, param2: p2});
// Function definition and usage of value pairs:
function sendMail(data){
var parameter1 = data.param1;
var parameter2 = data.param2;
}

Destructuring assignment in JavaScript

As can be seen in the Mozilla changlog for JavaScript 1.7 they have added destructuring assignment. Sadly I'm not very fond of the syntax (why write a and b twice?):
var a, b;
[a, b] = f();
Something like this would have been a lot better:
var [a, b] = f();
That would still be backwards compatible. Python-like destructuring would not be backwards compatible.
Anyway the best solution for JavaScript 1.5 that I have been able to come up with is:
function assign(array, map) {
var o = Object();
var i = 0;
$.each(map, function(e, _) {
o[e] = array[i++];
});
return o;
}
Which works like:
var array = [1,2];
var _ = assign[array, { var1: null, var2: null });
_.var1; // prints 1
_.var2; // prints 2
But this really sucks because _ has no meaning. It's just an empty shell to store the names. But sadly it's needed because JavaScript doesn't have pointers. On the plus side you can assign default values in the case the values are not matched. Also note that this solution doesn't try to slice the array. So you can't do something like {first: 0, rest: 0}. But that could easily be done, if one wanted that behavior.
What is a better solution?
First off, var [a, b] = f() works just fine in JavaScript 1.7 - try it!
Second, you can smooth out the usage syntax slightly using with():
var array = [1,2];
with (assign(array, { var1: null, var2: null }))
{
var1; // == 1
var2; // == 2
}
Of course, this won't allow you to modify the values of existing variables, so IMHO it's a whole lot less useful than the JavaScript 1.7 feature. In code I'm writing now, I just return objects directly and reference their members - I'll wait for the 1.7 features to become more widely available.
You don't need the dummy "_" variable. You can directly create "global" variables by using the window object scope:
window["foo"] = "bar";
alert(foo); // Gives "bar"
Here are few more points:
I wouldn't name this function
"assign" because that's too generic
a term.
To more closely resemble JS
1.7 syntax, I'd make the function take the destination as the first
argument and the source as the
second argument.
Using an object literal to pass the destination variables is cool but can be confused with JS 1.7 destructuring where the destination is actually an object and not an array. I prefer just using a comma delimited list of variable names as a string.
Here's what I came up with:
function destructure(dest, src) {
dest = dest.split(",");
for (var i = 0; i < src.length; i++) {
window[dest[i]] = src[i];
}
}
var arr = [42, 66];
destructure("var1,var2", arr);
alert(var1); // Gives 42
alert(var2); // Gives 66
Here's what I did in PHPstorm 10:
File -> Settings -> Languages & Frameworks -> ...
... set JavaScript language version to e.g. JavaScript 1.8.5...
-> click Apply.
In standard JavaScript we get used to all kinds of ugliness, and emulating destructuring assignment using an intermediate variable is not too bad:
function divMod1(a, b) {
return [ Math.floor(a / b), a % b ];
}
var _ = divMod1(11, 3);
var div = _[0];
var mod = _[1];
alert("(1) div=" + div + ", mod=" + mod );
However I think the following pattern is more idomatic:
function divMod2(a, b, callback) {
callback(Math.floor(a / b), a % b);
}
divMod2(11, 3, function(div, mod) {
alert("(2) div=" + div + ", mod=" + mod );
});
Note, that instead of returning the two results as an array, we pass them as arguments to a callback function.
(See code running at http://jsfiddle.net/vVQE3/ )

Categories

Resources