I have a function like this:
let showNotification = function(a,b,c,d,e,f){
console.log(a,b,c,d,e,f);
}
While calling this function, I know I need to follow order, something like this (if I dont want to pass param for c,d,e):
showNotification(1,2,,,,6);
But this forces me to make sure I pass all parameter and it should be in order..
I am looking for a better way to pass param in this case.
I know that I can create an object of param instead where instead of passing the values. something like this:
let showNotification = function(objectParam){
console.log(objectParam.a, objectParam.b, objectParam.c, objectParam.d, objectParam.e, objectParam.f)
}
then call it:
showNotification({a: 1, b: 2, e:6});
I can pass whole object but this is not what I am looking. I do not want to create object every time for this.
Thinking loud, if there is a way to pass the string values, without taking care of the order.
I see some dead SO posts regarding this, but no one has solution.
#T,J. Crowder answer said better than this. In other way, you can do this by using spread Operators. As you said the thing is here all are optional, except the first param. So you should pass first parameter value. then you don't need to pass other value unless you want.But you should mention the position of parameter value as empty with , in an array. In my case you should create an object or array.
For example . If you have a dynamic array with values(whatever but position should be mandatory )and position [1,,,3]. then spread operator helps to merge that dynamic array with your function parameters.
let showNotification = function(a,b,c,d,e,f){
console.log(a,b,c,d,e,f);
};
let parts = [2,3,,,6];//Think it is a dynamic array on run time or compile time
showNotification(1,...parts);
And here the #Felix Kling answers could help by using Named Parameters
var parameterfy = (function() {
var pattern = /function[^(]*\(([^)]*)\)/;
return function(func) {
// fails horribly for parameterless functions ;)
var args = func.toString().match(pattern)[1].split(/,\s*/);
return function() {
var named_params = arguments[arguments.length - 1];
if (typeof named_params === 'object') {
var params = [].slice.call(arguments, 0, -1);
if (params.length < args.length) {
for (var i = params.length, l = args.length; i < l; i++) {
params.push(named_params[args[i]]);
}
return func.apply(this, params);
}
}
return func.apply(null, arguments);
};
};
}());
var foo = parameterfy(function(a, b, c) {
console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);
});
foo(1, 2, 3); // a is 1 | b is 2 | c is 3
foo(1, {b:2, c:3}); // a is 1 | b is 2 | c is 3
foo(1, {c:3}); // a is 1 | b is undefined | c is 3
foo({a: 1, c:3}); // a is 1 | b is undefined | c is 3
More examples:
Pass a value to a specific parameter without caring about the position of the parameter
Passing the argument name while calling function in javascript
JavaScript: Get Argument Value and NAME of Passed Variable
Bhushan Babar hasn't posted his suggestion as a an answer, so I'll post it as a community wiki:
You can prepend or append(concat) tokens for respective parameter into your strings , e.g if you want to send parameter which represents parameter d , and string you want to pass is "myString", then you can decide format of your token like $&d&$ , so your parameter will look like "myString$&d&$"
TL;DR I know you didn't want to use an object, but modern JavaScript engines are really fast at creating and disposing of objects, and with parameter defaults and parameter destructuring, I think an options object is your best option. See "Notes on passing in an object (defaults, destructuring)" below for details.
You've covered two of the main options (I'm going to make some notes on the passing-in-an-object version below). A third option is to adopt the a variant of builder pattern, but it involves using an object too. You've said you don't want to do that, but haven't said why not. Note that modern JavaScript engines create and dispose of objects really, really fast if that's your concern.
Marker strings
A fourth option occurred to me when reading Bhushan Babar's append/prepend idea: Instead of doing that, you could use marker strings in the arguments list to indicate what the next argument is, e.g.:
showNotification("the required parameter", "a", value_for_a, "c", value_for_c);
On first glance, that doesn't involve creating an object, but on modern JavaScript engines, processing it will create an object that isn't otherwise created: arguments, the pseudo-array of passed-in arguments. Because that (or a rest parameter, which also creates an object) is the only reasonable way you could consume such a thing.
Variant on Builder
In this approach, the main function returns a builder object with setters for the various options, and then a final "Yep, we're ready" call that starts the process (where in the builder pattern that final call usually builds the final object). Using it would look something like this:
showNotification("the required param")
.withA(value_for_a)
.withC(value_for_c)
.go();
Implementing this is complicated relative to other approaches, but not difficult.
Notes on passing in an object (defaults, destructuring)
If you do use an object (despite not wanting to), you can use default parameters and destructuring to make the object more convenient to work with:
let showNotification = function({a = 1, b = 2, c = 3, d = 4, e = 5, f = 6} = {/*default if no object at all*/a: "foo"}){
console.log(a,b,c,d,e,f);
};
showNotification();
showNotification({});
showNotification({a:42});
In a comment you've said:
the thing is here all are optional, except the first param
That sounds like you probably want the first param and then an options object:
let showNotification = function(firstThing, {a = "default_a", b = "default_b"/*etc.*/} = {}) {
console.log(firstThing, a, b);
};
Related
Is there a way to allow "unlimited" vars for a function in JavaScript?
Example:
load(var1, var2, var3, var4, var5, etc...)
load(var1)
Sure, just use the arguments object.
function foo() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
In (most) recent browsers, you can accept variable number of arguments with this syntax:
function my_log(...args) {
// args is an Array
console.log(args);
// You can pass this array as parameters to another function
console.log(...args);
}
Here's a small example:
function foo(x, ...args) {
console.log(x, args, ...args, arguments);
}
foo('a', 'b', 'c', z='d')
=>
a
Array(3) [ "b", "c", "d" ]
b c d
Arguments
0: "a"
1: "b"
2: "c"
3: "d"
length: 4
Documentation and more examples here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters
Another option is to pass in your arguments in a context object.
function load(context)
{
// do whatever with context.name, context.address, etc
}
and use it like this
load({name:'Ken',address:'secret',unused:true})
This has the advantage that you can add as many named arguments as you want, and the function can use them (or not) as it sees fit.
I agree with Ken's answer as being the most dynamic and I like to take it a step further. If it's a function that you call multiple times with different arguments - I use Ken's design but then add default values:
function load(context) {
var defaults = {
parameter1: defaultValue1,
parameter2: defaultValue2,
...
};
var context = extend(defaults, context);
// do stuff
}
This way, if you have many parameters but don't necessarily need to set them with each call to the function, you can simply specify the non-defaults. For the extend method, you can use jQuery's extend method ($.extend()), craft your own or use the following:
function extend() {
for (var i = 1; i < arguments.length; i++)
for (var key in arguments[i])
if (arguments[i].hasOwnProperty(key))
arguments[0][key] = arguments[i][key];
return arguments[0];
}
This will merge the context object with the defaults and fill in any undefined values in your object with the defaults.
It is preferable to use rest parameter syntax as Ramast pointed out.
function (a, b, ...args) {}
I just want to add some nice property of the ...args argument
It is an array, and not an object like arguments. This allows you to apply functions like map or sort directly.
It does not include all parameters but only the one passed from it on. E.g. function (a, b, ...args) in this case args contains
argument 3 to arguments.length
Yes, just like this :
function load()
{
var var0 = arguments[0];
var var1 = arguments[1];
}
load(1,2);
As mentioned already, you can use the arguments object to retrieve a variable number of function parameters.
If you want to call another function with the same arguments, use apply. You can even add or remove arguments by converting arguments to an array. For example, this function inserts some text before logging to console:
log() {
let args = Array.prototype.slice.call(arguments);
args = ['MyObjectName', this.id_].concat(args);
console.log.apply(console, args);
}
Although I generally agree that the named arguments approach is useful and flexible (unless you care about the order, in which case arguments is easiest), I do have concerns about the cost of the mbeasley approach (using defaults and extends). This is an extreme amount of cost to take for pulling default values. First, the defaults are defined inside the function, so they are repopulated on every call. Second, you can easily read out the named values and set the defaults at the same time using ||. There is no need to create and merge yet another new object to get this information.
function load(context) {
var parameter1 = context.parameter1 || defaultValue1,
parameter2 = context.parameter2 || defaultValue2;
// do stuff
}
This is roughly the same amount of code (maybe slightly more), but should be a fraction of the runtime cost.
While #roufamatic did show use of the arguments keyword and #Ken showed a great example of an object for usage I feel neither truly addressed what is going on in this instance and may confuse future readers or instill a bad practice as not explicitly stating a function/method is intended to take a variable amount of arguments/parameters.
function varyArg () {
return arguments[0] + arguments[1];
}
When another developer is looking through your code is it very easy to assume this function does not take parameters. Especially if that developer is not privy to the arguments keyword. Because of this it is a good idea to follow a style guideline and be consistent. I will be using Google's for all examples.
Let's explicitly state the same function has variable parameters:
function varyArg (var_args) {
return arguments[0] + arguments[1];
}
Object parameter VS var_args
There may be times when an object is needed as it is the only approved and considered best practice method of an data map. Associative arrays are frowned upon and discouraged.
SIDENOTE: The arguments keyword actually returns back an object using numbers as the key. The prototypal inheritance is also the object family. See end of answer for proper array usage in JS
In this case we can explicitly state this also. Note: this naming convention is not provided by Google but is an example of explicit declaration of a param's type. This is important if you are looking to create a more strict typed pattern in your code.
function varyArg (args_obj) {
return args_obj.name+" "+args_obj.weight;
}
varyArg({name: "Brian", weight: 150});
Which one to choose?
This depends on your function's and program's needs. If for instance you are simply looking to return a value base on an iterative process across all arguments passed then most certainly stick with the arguments keyword. If you need definition to your arguments and mapping of the data then the object method is the way to go. Let's look at two examples and then we're done!
Arguments usage
function sumOfAll (var_args) {
return arguments.reduce(function(a, b) {
return a + b;
}, 0);
}
sumOfAll(1,2,3); // returns 6
Object usage
function myObjArgs(args_obj) {
// MAKE SURE ARGUMENT IS AN OBJECT OR ELSE RETURN
if (typeof args_obj !== "object") {
return "Arguments passed must be in object form!";
}
return "Hello "+args_obj.name+" I see you're "+args_obj.age+" years old.";
}
myObjArgs({name: "Brian", age: 31}); // returns 'Hello Brian I see you're 31 years old
Accessing an array instead of an object ("...args" The rest parameter)
As mentioned up top of the answer the arguments keyword actually returns an object. Because of this any method you want to use for an array will have to be called. An example of this:
Array.prototype.map.call(arguments, function (val, idx, arr) {});
To avoid this use the rest parameter:
function varyArgArr (...var_args) {
return var_args.sort();
}
varyArgArr(5,1,3); // returns 1, 3, 5
Use the arguments object when inside the function to have access to all arguments passed in.
Be aware that passing an Object with named properties as Ken suggested adds the cost of allocating and releasing the temporary object to every call. Passing normal arguments by value or reference will generally be the most efficient. For many applications though the performance is not critical but for some it can be.
Use array and then you can use how many parameters you need. For example, calculate the average of the number elements of an array:
function fncAverage(sample) {
var lenghtSample = sample.length;
var elementsSum = 0;
for (var i = 0; i < lenghtSample; i++) {
elementsSum = Number(elementsSum) + Number(sample[i]);
}
average = elementsSum / lenghtSample
return (average);
}
console.log(fncAverage([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // results 5.5
let mySample = [10, 20, 30, 40];
console.log(fncAverage(mySample)); // results 25
//try your own arrays of numbers
Coming from C, every function needs a name that is used to call that specific function. but in the JavaScript code below. There is no name at all. So how does function(func) { func(); } calls the something.push(function() ?
Not to mention that something.push(function() doesn't even have an argument, which from C perspective, this is wrong
var something = [],
object = {
a: true,
b: true,
c: true
}
for (let key in object){
something.push(function() {
console.log(key);
});
}
something.forEach(function(func) { func(); }):
Coming from C, every function needs a name that is used to call that specific function. but in the JavaScript code below, there is no name at all.
Well in JavaScript, that's just not the case – functions can indeed be anonymous (nameless) functions. Sometimes you'll hear them called "lambdas" or redundantly called "lambda functions".
But more importantly, functions are first class data members in JavaScript, meaning:
They can be assigned to variables
let y = x => x + 5
y(3)
// 8
They can be passed as arguments to other functions
let xs = [1,2,3]
let y = x => 2 * x
xs.map(y)
// [2,4,6]
They can be returned from a function
let add = x => {
return y => x + y
}
let f = add(1)
f(2)
// 3
They can be included in data structures
let pikachu = {
number: 25,
shock: enemy => enemy.damage(30),
growl: enemy => enemy.sadden(40)
}
pikachu.growl(meowth)
// "It's very effective!"
So what's the take away here? Well, in JavaScript, you need to think of functions as being no different than any other value (eg) 1, "foo", or [{user: 'bob'}, {user: 'alice'}] – conceptually, they are just data and they're all first class
How to understand your code
Here's your code affectionally reformatted by me. Since you originally asked about ES6, I'm going to substitute your lambdas with ES6 arrow functions
let something = []
let object = { a: true, b: true, c: true }
for (let key in object) {
something.push(() => console.log(key))
}
something.forEach(func => func())
// a
// b
// c
Using high level descriptors, the nature of this code is
iterate thru each key of object, add some value to something
iterate thru each value of something, do something with that value
In your case, some value is a function, but I want to show you what your code would look like if we used another kind of value
let something = []
let object = { a: true, b: true, c: true }
for (let key in object) {
// this time push a string, instead of an anonymous function
something.push("the key is: " + key)
}
something.forEach(str => console.log(str))
// the key is: a
// the key is: b
// the key is: c
So now we can see how the code works when a different value type (String) is used. Aside from using a string, the only other thing we did was change
// from
func => func()
// to
str => console.log(str)
The reasoning here is
in your original code, something is an array of functions, so for each of those functions, func, we call func()
in the modified code, something is an array of strings, so for each of those strings, str, we call console.log(str)
Dissection of Array.prototype.forEach
The last remaining bit to understand is the super-powered forEach function. Remember point number 2 in the first class capabilities: first class data members can be passed as arguments to other functions.
forEach expects its argument to be a function. So forEach is a function that accepts another function. This kind of function is called a higher-order function.
Don't let these fancy names distract you from their underlying simplicity. Making our own higher-order functions like forEach is actually really easy. Always just remember that functions are like any other value – below, we'll write our own version of forEach so you can see how it works
let something = []
let object = { a: true, b: true, c: true }
// forEach accepts an array and some function, f
let forEach = (arr, f) => {
// iterate thru each value of arr
for (let value of arr) {
// call f on each value
f(value)
}
}
for (let key in object) {
something.push("the key is: " + key)
}
forEach(something, str => console.log(str))
// the key is: a
// the key is: b
// the key is: c
So now we see the string array working with our own forEach function. Let's make sure it works with your original lambda array, too.
let something = []
let object = { a: true, b: true, c: true }
let forEach = (arr, f) => {
for (let value of arr) {
f(value)
}
}
for (let key in object) {
something.push(() => console.log(key))
}
forEach(something, func => { func() })
// a
// b
// c
Remarks
And that's it! Functions aren't special snowflakes in JavaScript. They're just like all the datums.
Coming from a C perspective, it might take awhile to think about functions in this way. But really, you should leave everything you understand about any language at the door when you're walking into the home of any new programming language. Each language has its own way of expressing itself yielding a unique set of strengths and weaknesses. By bringing in a "strength" of some language A to another language B, you might be substituting one of B's own strengths for one of it's weaknesses.
JavaScript is a multi-paradigm language making it rather suitable for imperative, object-oriented, and functional programming styles. Because of its jack-of-all-trades nature, it's probably not the best oop or fp language, but it's still rather impressive that it can express a particular solution to a problem in a wide variety of ways.
The something array is storing references to functions, but not invoking them. In the forEach loop, each function within the array is being invoked.
To be fair, the naming is confusing. You could just as easily write:
something.forEach(function(storedFunction) { storedFunction(); }):
since it's a temporary variable
In JavaScript, functions are treated as objects. And in C you must be aware that not every object (value) needs to have a name, or doesn't need to be stored in a variable. Just like you can have sum (4, 5);. 4 and 5 are values not stored in variables.
To answer your question about how they will be called if they have no name, the function is passed as an argument to the push function. There the formal parameter must have some name. So it can be called by that name. In this case push doesn't call it but stores it in an array to be called later. It can be called by indexing into the array.
JavaScript is more abstract language than C. In JavaScript interpretter internally saves types and pointers of every variable. When you declare a function, it is internally saved somewhere in memory and pointer of that function is passed to whatever you passed it (here to something.forEach).
But, this is not pointer like in C, you cannot access its value (function address) nor you can change it. In JavaScript these pseudo-pointers are called "references". Simply, when you try to call something, interpretter engine will check its type. If its type type is a function, thread will do some preparations and then execute jump to memory location which function pointer is pointing to.
So, what's happening here is in the loop, we are pushing a function to something on each iteration. Assuming something is an array in this case.
Then, we call the collection's foreach() function. The first argument in this function is always the current item over which we are iterating. In this case, we're iterating over the list of functions we built in the loop. Since these are functions, we can call them, which is what's happening for each one.
Here's an example to help illustrate:
// declare some functions
function a(){console.log('hi');}
function b(){console.log('there');}
function c(){console.log('friend');}
const ar = [];
// push the functions to the array
ar.push(a);
ar.push(b);
ar.push(c);
ar.push(a);
ar.forEach(function(func) {func();});
// console output:
// hi
// there
// friend
// hi
EDIT: I highly recommend you read Mozilla's documentation of the function type. If you go there and thoroughly read through it I think you'll have a very solid grasp of this concept.
What you see is the usage of an anonymous function and a closure. It's pretty Javascript in a nutshell.
There I describe the code block per block :
We create an array (like in C) :
var something = [],
We create an object (which is a collection of key=value)
object = {
a: true,
b: true,
c: true
}
We iterate through the elements of the object object
for (let key in object) {
There the show is on :
something.push(function() {
Each object has a push function that allow to put elements dynamically inside of it.
What you see here is a anonymous function being used as an argument; it means that for each executions of this push function, a function that do :
console.log(key);
will be put inside the something array;
There after, the only thing that is done is a callback with func() :
something.forEach(function(func) { func(); }):
This means forEach elements of something, you will treat it as a function resulting in calling the precedent function stored with push()
I come from a Java background, where everything is Object Oriented. While Getting into a bit more of Javascript (more into the class areas of javascript), I've noticed complete changes. The biggest for me is getting used to the prototyping of the so-called "classes" javascript has. So, my question is if you need to intialize the varialbes you pass into your class function constructor method-thing. For example:
function Foo(a, b, c) {
this.a = a;
var b = b;
this.c = "";
this.d = a + b;
}
Now In javascript is this necessary? Cause in Java, you have to show that the variable type definition in the argument itself: Foo(int a, int b, string c) Now how does the method in Javascript know what type of data structure it is being passed too? Like what if they passed in an array for a, and then my code tried to add the integer and the array together? That won't push the int too the array will it?
Sorry for being a bit questiony, I've been looking for an answer for a while on the Google... And it's getting late here.
Thanks for any help
Uneveris
So, my question is if you need to intialize the varialbes you pass into your class function constructor method-thing.
Do they need initializing, no. Javascript is a loosely typed language and declared variables can be of any type.
You do not need to declare a variable type for the arguments, they can be anything. Also note the vars are private variables in the scope of the constructor.
Now how does the method in Javascript know what type of data structure it is being passed too?
As a result of loose types, javascript has a type typeof to help work out what a variables type actually is if strong typing is required.
if (typeof this.a !== 'function')
throw "Expected a function, received a " + typeof this.a;
Verbose, but it fulfils its purpose.
typeof reference
Like what if they passed in an array for a, and then my code tried to add the integer and the array together? That won't push the int too the array will it?
Have you tried to do this?
var a = new Array();
var b = 1;
var c = a + b;
console.log(typeof c);
>> string
In Node.js the output is a string with the array values concatenated and the integer appended as a string on the end.
It is important when expecting a specific data structure that data you have been passed is what you are expecting. In JS, this is by conditionally checking.
If you are writing these classes purely for your self, duck typing can be useful. If it looks like a duck, quacks like a duck then it is a duck. This is to do with semantics when working in a loosely typed language like JS. Two assumptions === true.
What is duck typing
Hope this helps answer your questions.
You can't be sure what parameter types are being passed to your method. That is a main Javascript language trait which can be used for both good and bad.
So, what happens if there are wrong parameter types? Javascript will try to silently convert them to something common.
For example:
var a = [100,2,3];
var b = 5;
var c = a + b;
Adding an array and an integer (as well as string) will result in a string "100,2,35". Note that array is first converted to String, then 5 is simply appended to the end. This behaviour closely resembles the Java's one, that calls a toString() method of any object whenever it needs to concatenate.
You can avoid the wrong types in two ways. First, convert them yourself. Like,
a = Number.parseInt(a, 10); // now a is of type number
Second, if your method is important and highly dependent on the data correctness, you should not convert the params but avoid using them at all if they are of wrong type:
if (typeof a != "number") throw "Param must be int";
Finally, you can see this JS framework that supports strict typing:
http://betterjs.org/
JavaScript happy times fun land
// make a method
var happy = function(a, b, c) {
console.log(a, b, c);
};
// store method to variable
var b = happy;
// bind a context and some arguments
b.bind(happy, 1, 2, 3);
// call the method without additional arguments
b();
Output. Yay!
1 2 3
In Ruby
# make a method
def sad a, b, c
puts a, b, c
end
# store method to variable
b = method(:sad)
# i need some way to bind args now
# (this line is an example of what i need)
b.bind(1, 2, 3)
# call the method without passing additional args
b.call
Desired output
1, 2, 3
For what it's worth, I know JavaScript can change the context of the binding with the first argument passed to .bind. In Ruby, I'd be 99% happy even if I couldn't change the context. I primarily need to simply bind parameters to the method.
Question
Is there a way to bind parameters to an instance of a Ruby Method such that when I call method.call without additional parameters, the bound parameters are still passed to the method?
Goal
This is a common JavaScript idiom and I think it would be useful in any language. The goal is to pass a method M to a receiver R where R does not need (or have) intrinsic knowledge of which (or how many) parameters to send to M when R executes the method.
A JavaScript demonstration of how this might be useful
/* this is our receiver "R" */
var idiot = function(fn) {
console.log("yes, master;", fn());
};
/* here's a couple method "M" examples */
var calculateSomethingDifficult = function(a, b) {
return "the sum is " + (a + b);
};
var applyJam = function() {
return "adding jam to " + this.name;
};
var Item = function Item(name) {
this.name = name;
};
/* here's how we might use it */
idiot(calculateSomethingDifficult.bind(null, 1, 1));
// => yes master; the sum is 2
idiot(applyJam.bind(new Item("toast")));
// => yes master; adding jam to toast
Normally, rebinding methods isn't something you do in Ruby. Instead, you use blocks:
# This is our receiver "R"
def idiot(&block)
puts("yes, master; #{block.call}")
end
# Here's a couple method "M" examples
def calculateSomethingDifficult(a, b)
return "the sum is #{a + b}"
end
def applyJam(object)
return "adding jam to " + object.name
end
class Item
attr_reader :name
def initialize(name)
#name = name
end
end
# Here's how we might use it
idiot do
calculateSomethingDifficult(1, 1)
end
#=> yes master; the sum is 2
# You *can* change calling context too (see instance_exec), but I'd
# discourage it. It's probably better to just pass the object as a
# parameter.
idiot do
applyJam(Item.new("toast"))
end
#=> yes master; adding jam to toast
If you really want to "bind" methods like you do in JavaScript it's definitely possible though:
class Method
def bind *args
Proc.new do |*more|
self.call *(args + more)
end
end
end
That should make your example work almost as you originally described:
# make a method
def sad a, b, c
puts a, b, c
end
# store method to variable
b = method(:sad)
# Get a "bound" version of the method
b = b.bind(1, 2, 3)
# call the method without passing additional args
b.call
If you need it exact, you can probably define Object#bindable_method to return some BindableMethod class that does what you want. For most cases though I think the above should work for you.
Proc#curry in Ruby is similar to bind in JavaScript.
def happy(a, b, c, d = 100)
puts a, b, c, d
end
proc = method(:happy).to_proc.curry # proc is now a curried Proc
b = proc.call(1,2) # b is a curried Proc with 1 and 2 bound as the first arguments
b.call(3) # Call the proc, providing the 3rd argument
You can't exactly duplicate your example code because when the curried proc is called with the necessary arguments, it returns the result of the proc --- in other words, you cannot bind ALL of the arguments and then call the proc later --- you have to leave at least one argument unbound.
This isn't necessarily a better option than the code provided by Ajedi32, but I think it's worth mentioning because it's built-in to Ruby.
See the docs here: http://ruby-doc.org/core-2.2.0/Proc.html#method-i-curry
I am getting more in to javascript development, and want to ensure I am following popular conventions.
Currently I have a library which consists of functions that can be passed either 1 model to operate on, or many models.
Given the climate that a few javascript libraries are very popular, I am curious; would I be conforming to the 'defacto standard' by achieving my 'single-item or list-of' requirement, by enumerating the arguments variable, or by allowing one of the arguments to be an array?
Scenario 1: argument enumeration
// passing a single entity to my function
sendMail( email, recipient1 );
// passing multiple entities to my function
sendMail( email, recipient1, recipient2 );
Scenario 2: entity argument is either single instance, or array
// pass a single entity
sendMail( email, recipient1 );
// passing multiple entities
sendMail( email, [recipient1, recipient2] );
I have seen areas of jQuery which use 'scenario 2', but I would still like to ask - which approach is the most popular, and why?
Thanks
[EDIT]
A couple of comments have followed the same vein, of using an arguments object - which is similar to 'scenario 2' - but I feel it introduces unnecessary complexity - the elements dont need to be named, because they are just a variable length list. I thought I would just add that here in case my question wasn't clear enough.
[EDIT]
I see code like this all through jQuery-1-7.js
queue: function( elem, type, data ) {
var q;
if ( elem ) {
type = ( type || "fx" ) + "queue";
q = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
if ( !q || jQuery.isArray(data) ) {
q = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
q.push( data );
}
}
return q || [];
}
}
[EDIT]
After some discussion with JP, I came up with this - which I'm not saying is the right choice, but it is very flexible...
lastArgumentAsParams: function()
{
var callerArgs = jQuery.makeArray(this.lastArgumentAsParams.caller.arguments);
// return empty set if caller has no arguments
if ( callerArgs.length == 0 )
return [];
callerArgs.splice(0, callerArgs.length - 1)
// remove all but the last argument
if ( callerArgs.length == 1 && jQuery.isArray(callerArgs[0]))
return callerArgs[0];
else
return callerArgs;
}
If you call this function at the beginning of any function - it will treat the last arg in the caller as a 'variable length argument' - supporting any of the conventions.
For example, I can use it like this
function sendEmail( body, recipients )
{
recipients = lastArgumentAsParams();
// foreach( recipient in recipients )...
}
Now, I can call 'sendEmail' in any of the following ways and it will work as expected
sendEmail('hello world', "bill#microsoft.com" );
sendEmail('hello world', "bill#microsoft.com", "steve#apple.com" );
sendEmail('hello world', ["bill#microsoft.com", "steve#apple.com"] );
I personally prefer using object literals for arguments to support named params, like this:
var myfunc = function(params){ //same as: function myfunc(params){....
alert(params.firstName);
alert(params.lastName);
};
myfunc({firstName: 'JP', lastName: 'Richardson'});
I think that it makes code very readable and order won't matter.
OR
You can also access the arguments object. Note, it's not an array, but it's "array-like". You can read about it here: http://javascriptweblog.wordpress.com/2011/01/18/javascripts-arguments-object-and-beyond/
Edit:
You seem to have a misunderstanding here. You're using the phrase "arguments object" and are thinking that it's the same as object literal notation. They are not.
The arguments object allows you to do this:
function myfunc(){
alert(arguments[0]); //JP
alert(arguments[1]); //Richardson
}
myfunc('JP', 'Richardson');
Does that help?
Another common way is to use object literal as variables:
myFunction(true, {option: value, option2: value});
I personally prefer this method for it is more verbose, and with javascript loose types, it gives you a better hint for what this variables is, and ignores order.
Backbone.js is using this as the preferred method.
To expand on the other answers, there are two main alternatives I usually see: optional arguments and keyword arguments. I don't remember seeing any good examples of the "array-using" idiom and it is kind of obsolete given how the arguments array is always available anyway.
Anyway, my rule of thumb is.
If I have many arguments, or the argument list is likely to change, or if the arguments don't have a good natural order, use the named arguments pattern
My favorite part about this style is that it is really flexible and future proof, while also being kind of self-documenting (in a smalltalk style).
foo({x1:'1', x2:'2', x3:'3'});
function foo(kwargs){
//I try to always copy the arguments back into variables.
//Its a little verbose but it helps documentation a lot and also
// lets me mutate the variables if I want to
var x1 = kwargs.x1,
x2 = kwargs.x2,
x3 = kwargs.x3;
}
If I have few arguments, that are not likely to change, and have a natural order to them, use a plain function (with the optional arguments last in the order)
foo(x1, x2);
foo(x1, x2, x3);
There are three main variations I can think right now of how to handle the optional arguments in the function:
var foo = function(x1, x2, x3){
//variation 1: truthy/falsy
// Short, but I tend to only use it when the variable stands
// for an object or other always-truthy kind of value
x3 = x3 || 'default_value';
//variation 2: using a special placeholder value for blank arguments.
// Usually this is null or undefined. (and undefined works if the arg is not passed too)
if(typeof x3 === 'undefined'){ x3 = 'default_value'; }
//variation 3: explicitly check the number of arguments
// I really like this one since it makes clear if the argument was passed or not.
if(arguments.length < 3){ x3 = 'default_value'; }
}
Also, there are so things I try to avoid:
Don't have functions that receive a large argument list. It can become a mess if they start becoming optional and you forget the order
foo(1, 2, null, null, 3, null, null); //ugh
Don't use fixed-length arrays to be tricky. They are redundant with no arrays at all and when I see an array I usually expect it to 1) be homogeneous and 2) be able to be as long as I want to
foo(true, [1, 2]); //should be foo(true, 1, 2)