I found an implementation using using prototypes. This is a simplification to show the structure:
function Thingie(){
this.content = [];
}
Thingie.prototype = {
push: function(arg) {
this.content.push(arg);
},
pop: function() {
return this.content.pop();
}
};
var t = new Thingie();
forEach([10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5],
method(t, "push"));
What is "method" in the example on the last line? I've never seen this construct. I use t.push like everyone else.
I tried find how "method()" is defined online, but it is impossible to search for a function called "method" using any possible set of search terms. All you get are how functions and methods are defined and used. There also seems to be no information when I look at forEach documentation.
Does this make sense to anybody?
method(t, "push")
would be defined as:
function method(obj, name) {
return obj[name].bind(obj);
}
That forEach looks like UnderscoreJS's function _.each
_.each(list, iteratee, [context]) Alias: forEach
example:
_.each([1,2,3], function(item) { console.log(item); });
// console output:
// 1
// 2
// 3
That method probably looks like this (Beware: wild guess!) , giving you the function to use as iteratee paramater
function method(obj, name){
if(typeof(obj[name]) != "function")
throw new Error("Not a function");
return obj[name];
}
A function like that is lodash.bindKey, which does exactly what you want. Also, the forEach method could be lodash.forEach, or simply Array.prototype.forEach, which is built-in.
[1,2,3].forEach(_.bindKey(t, 'push'));
However, this works because Thinghie#push expects only one argument. If the same call would be made on an array, the result would not be as expected, since forEach methods take 3 arguments: value, index, array, and [].push can handle multiple arguments. So, the code
var array = [];
[1,2].forEach(_.bindKey(array, 'push'));
console.log(array); // outputs [1, 0, [1, 2], 2, 1, [1, 2]]
In this case (and in any case when we would like the function returned by method applied on only one argument), I guess the solution is to write
function method(obj, name) {
return function(arg) { return obj[name](arg); }
}
Related
So I have my function uniteUnique. It should get the arguments and concatonate them to a single array. If there would be a specific amount of arguments for example 3, i would implement it like the function bellow
function uniteUnique(arr) {
var args = [];
var newArgs;
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
newArgs = args[0].concat(args[1], args[2]);
}
return newArgs ;
}
uniteUnique([1, 3, 2], [1, [5]], [2, [4]]);
But what if uniteUnique function would have 2 arguments or any other number.
uniteUnique([1, 2, 3], [5, 2, 1])
How to make my function know how many arguments it should concatonate, and how would it be implemented inside the concat() function ?
EDIT:
Output should look like this:
uniteUnique([1, 3, 2], [1, [5]], [2, [4]]) should return [1,3,1,1,[5],2,[4]]and uniteUnique([1, 2, 3], [5, 2, 1]) should return [1,2,3,5,2,1]
I'm not sure if I fully understand your question, but: what about this simple solution?
function uniteUnique() {
return Array.prototype.slice.call(arguments);
}
The arguments object is not really an Array instance, and does not have any of the Array methods. So, arguments.slice(...) will not work because the arguments object does not have the slice method.
Instead, Arrays do have this method, and because the arguments object is very similar (...) to an array, the two are compatible. This means that we can use array methods with the arguments object. And array methods will return arrays rather than other argument objects.
For a more throughtful explanation please see this SO answer ...
UPDATE (to answer OP comment):
If you need deep merging, you can do:
function uniteUnique() {
return Array.prototype.concat.apply([], arrays);
}
or even:
function uniteUnique() {
return [].concat.apply([], arrays);
}
This should work since the dafaule value of Symbol.isConcatSpreadable is false, so concat() acts deeply...
According to your examples you want to flatten arguments array. In ES6 you can use Rest parameters to get arguments array and Spread syntax to flatten it:
function uniteUnique(...args) {
return [].concat(...args);
}
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/arguments
You can access the arguments variable to loop through all arguments passed to a given function.
Was able to solve this like that,
function uniteUnique(arr) {
var myNewArray = [].concat.apply([], Array.prototype.slice.call(arguments));
return myNewArray;
}
uniteUnique([1, 3, 2], [1, [5]], [2, [4]]) ;
Relating to the following mock code (_ relates to the lodash library):
var containerFunction = function () {
var opts = {data: value};
_.map(lines, functionForEach);
}
var functionForEach = function (line) {
Do something for each line passed in from the maps function.
Need to access opts in this scope.
return value;
}
The argument line is received from the map function, but what would be the best way of passing the opts argument to the functionForEach function while keeping the rough pattern above (if possible).
I thought something like:
_.map(lines, functionForEach(opts))
or something similar might work but obviously doesn't.
Any suggestions?
You have three alternatives:
Put functionForEach inside containerFunction. If you are not going to use functionForEach elsewhere, this makes the most sense.
Write it as:
var containerFunction = function () {
var opts = {data: value};
_.map(lines, function(elt) { return functionForEach(elt, opts); });
}
var functionForEach = function (line, opts) {
Do something for each line passed in from the maps function.
Need to access opts in this scope.
return value;
}
If you must, pass opts as the third (thisArg) argument to _.map and access it using this inside functionForEachvar.
Lodash has good utilities for function composition, including partially-applying arguments. However, this isn't actually so straightforward. Let's say we want to use partialRight():
function functionForEach(item, i) { return item * i; }
_.map([1, 2, 3], _.partialRight(functionForEach, 2));
// → [0, 2, 6]
Oops. That's not correct. The reason is that we're not getting the arguments passed to functionForEach() as we expect them. We want the i argument to always have a value of 2 and the item is the mapped collection value.
Let's try something else:
_.map([1, 2, 3], _.ary(_.partialRight(functionForEach, 2), 1));
// → [2, 4, 6]
That's better. The problem is that map() passes the item as the first argument, along with 2 other arguments (the index and the collection itself). Since we don't care about these latter two arguments, we can use ary() to compose a new function that ignores them.
Here's another approach:
_.map([1, 2, 3], _.partial(_.rearg(functionForEach, 1, 0), 2));
// → [2, 4, 6]
This time, we're using rearg() to change the order of our partial() function arguments. This approach I find to be less intuitive than simple going right for partialRight().
One final example:
_.map([1, 2, 3], _.flow(_.identity, _.partialRight(functionForEach, 2)));
// → [2, 4, 6]
Here, we're using the flow() higher-order function to compose our callback. This function takes the arguments supplied to it, and chains together the functions we pass to it - the output of the last function is the input to the next. We're using the identity() function here because it simply returns the first argument passed to it, which is exactly what we want.
This last approach is similar to the ary() approach - it's doing the same thing. The flow() approach is superior if we ever wanted to build on this behavior by passing in more functions, before or after functionForEach().
Take a look at _.partial. Docs:
partial
var containerFunction = function() {
var opts = { data: value };
_.map(lines, _.partial(functionForEach, opts));
};
var functionForEach = function (opts, line) { ... };
_.partial returns a new function with the arguments prepended to the functionForEach. In the example above, _.partial returns the following function signature:
function (line) { ... }
Calling the return function with a line calls functionForEach with line and opts defaulted to whatever you passed in as the argument in _.partial.
After I asked this question and realized that Backbone.Collections are strictly for Backbone.Models, I'm a little disappointed.
What I was hoping for:
Make underscore's methods more object oriented:
_.invoke(myCollection, 'method'); ==> myCollection.invoke('method');
I'll admit, minor difference, yet still it seems nice.
What problems will I run into if I use Backbone.Collection for non-Backbone.Models?
Are there any existing implementations, or a simple way to make a generic underscore collection class?
While you can't use a Backbone Collection without using models, I came up with a clever way to mix in underscore into the Array prototype:
// This self-executing function pulls all the functions in the _ object and sticks them
// into the Array.prototype
(function () {
var mapUnderscoreProperty = function (prp) {
// This is a new function that uses underscore on the current array object
Array.prototype[prp] = function () {
// It builds an argument array to call with here
var argumentsArray = [this];
for (var i = 0; i < arguments.length; ++i) {
argumentsArray.push(arguments[i]);
}
// Important to note: This strips the ability to rebind the context
// of the underscore call
return _[prp].apply(undefined, argumentsArray);
};
};
// Loops over all properties in _, and adds the functions to the Array prototype
for (var prop in _) {
if (_.isFunction(_[prop])) {
mapUnderscoreProperty(prop);
}
}
})();
Here is an example of how to use the new Array prototypes:
var test = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(test.filter(function (item) {
return item > 2 && item < 7;
})); // Logs [3, 4, 5, 6]
console.log(test); // Logs the entire array unchanged
This solution might add more to the Array prototype than is actually useful, but it gets you the bulk of the functions. Another solution would be to only add functions that have an iterator argument, but this is a start for you.
The Underscore.js documentation says:
_.each(list, iterator, [context])
Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is passed. Each invocation of iterator is called with three arguments: (element, index, list). If list is a JavaScript object, iterator's arguments will be (value, key, list). Delegates to the native forEach function if it exists.
_.each([1, 2, 3], alert);
=> alerts each number in turn...
_.each({one : 1, two : 2, three : 3}, alert);
=> alerts each number value in turn...
What does the bolded text above mean? Can someone provide an example that would explain it?
It means that, inside your iterator function, the value of this will be what you pass as the context argument.
For example:
var arr = [1, 2, 3];
function iterator(el, i, list) {
console.log(this)
}
_.each(arr, iterator, arr); // will log the whole array 3 times
This is useful if you want to pass an object method as the iterator, and that method uses this. Example:
var arr = [1, 2, 3];
var myObj = {
foo : 5,
addFoo : function(el, i, lst) {
console.log(el + this.foo)
}
};
// This will log NaN 3 times, because 'this' inside the function
// will evaluate to window, and there's no window.foo. So this.foo
// will be undefined, and undefined + 1 is NaN
_.each(arr, myObj.addFoo);
// This, on the other hand, works as intended. It will get the value
// of foo from myObj, and will log 6, then 7, then 8
_.each(arr, myObj.addFoo, myObj);
http://jsfiddle.net/KpV5k/
All you know that arguments is a special object that holds all the arguments passed to the function.
And as long as it is not an array - you cannot use something like arguments.slice(1).
So the question - how to slice everything but first element from arguments?
UPD:
seems like there is no way without converting it to an array with
var args = Array.prototype.slice.call(arguments);
If someone posts another solution it would be great, if not - I'll check the first one with the line above as an answer.
Q. How to slice everything but first element from arguments?
The following will return an array containing all arguments except the first:
var slicedArgs = Array.prototype.slice.call(arguments, 1);
You don't have to convert arguments to an array first, do it all in one step.
Meddling with array functions is not actually necessary.
Using rest parameter syntax ...rest is cleaner and more convenient.
Example
function argumentTest(first, ...rest) {
console.log("First arg:" + first);
// loop through the rest of the parameters
for (let arg of rest) {
console.log("- " + arg);
}
}
// call your function with any number of arguments
argumentTest("first arg", "#2", "more arguments", "this is not an argument but a contradiction");
...Rest
See the example Fiddle
See MDN Documentation page
From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments:
You should not slice on arguments because it prevents optimizations in
JavaScript engines (V8 for example). Instead, try constructing a new
array by iterating through the arguments object.
So Paul Rosiana's answer above is correct
This can be a way:
var args = Array.from(arguments).slice(1);
You can "slice without slicing" by procedurally walking the arguments object:
function fun() {
var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
return args;
}
fun(1, 2, 3, 4, 5); //=> [2, 3, 4, 5]
You can use the method [].slice.call(arguments, 1)
[].slice will return you the slice function object and you can call it as the arguments and 1 are the parameters
You can use ...rest within the function to separate the first and the rest of the arguments:
function foo(arr) {
const [first, ...rest] = arguments;
console.log(`first = ${first}`);
console.log(`rest = ${rest}`);
}
//Then calling the function with 3 arguments:
foo(1,2,3)
you can use this too
function arg(myArr) {
let arg = Object.values(arguments).slice(2, 4);
console.log(arg);
return arg;
};
arg([1, 2, 3], 4, [5,6], 7)
see here for reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
...
Arguments type is iterable, so using the ES6 (ES2015) spread ... operator, then use Array.slice([start], [end]) method, such as
function omitFirstAndLastArgument(value) {
const args = arguments.length > 2 ? [...arguments].slice(1, -1) : [];
return args;
}
omitFirstAndLastArgument(1, 2, 3, 4, 5, 6); // [2, 3, 4, 5]