I have a function that takes an array and then a number of arguments. In that function I want to use the filter() method on the array, but I want to use arguments[i]. As an example:
function destroyer(arr) {
var test = arr.filter(function(value){
return value != arguments[1];
});
return test;
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
Calling arguments[1] there doesn't give me what I want, because I'm guessing the anonymous function has its own arguments that filter() is using. One solution I thought of was creating an array and copying the arguments to that. But I was wondering if there was a way to pass the arguments to filter? I know filter has a thisArg value, but I'm not sure how to use that to get what I want.
Edit: I have tried googling for a solution, and most of the solutions involve using bind or apply. However, I couldn't find anything directly related to filter() or similar methods like forEach, reduce, map, etc. I've tried using "this" as the thisArg value, but that doesn't seem to do anything. I've tried using bind and apply with it, but I can't seem to get the correct syntax for it.
If you have access to an ES6 environment, you can use "spread parameters" (...) to represent your variable-length argument list. Along with arrow functions, and Array#includes, your code could be quite compact:
function destroyer(arr, ...destroy) {
return arr.filter(value => !destroy.includes(value));
}
I have a working solution (with copying the arguments to an array), but I'm more interested in trying to figure how to pass the arguments to filter() to better understand how filter() and other similar methods work.
You don't need to pass the arguments to filter(). The arguments can simply be made available in the scope of the filter callback. However, arguments is special; it refers to the arguments of the current function. There is no way to refer to the arguments of some other function, such as the outer function. Therefore, there is no alternative but to save the arguments under a different variable name (such as args) in the outer function; then you can refer to that variable from within the callback, because the callback "closes" over that variable.
If you're really intent on "passing" the list of numbers to be destroyed to the inner function, instead of just letting it access them by referring to a variable created in the outer scope, then yes, in theory you could use thisArg:
function destroyer(arr/*, number to destroy*/) {
return arr.filter(do_not_destroy, arguments);
^^^^^^^^^ PASS thisArg
}
Now we can define do_not_destroy as
function do_not_destroy(elt) {
return [].slice.call(this, 1).indexOf(elt) === -1;
^^^^ REFER TO thisArg (arguments)
}
Note two things here. First, we refer to the list of arguments as this, because we passed them to filter using thisArg. Second, we cannot write this.slice..., and instead must write [].slice.call(this... , because this is an arguments object, which is not a "real" array and does not have the slice method.
To make do_not_destroy independent of the fact that its parameter is a special arguments object, with an extra parameter (the array) at the beginning, we could do the conversion to an array within the destroyer function:
function destroyer(arr/*, number to destroy*/) {
return arr.filter(do_not_destroy, [].slice.call(arguments, 1));
}
Now we can define do_not_destroy as
function do_not_destroy(elt) {
return this.indexOf(elt) === -1;
}
But now we are basically back to where we started. Instead of "passing" the list of values to be omitted by merely letting the inner function (callback) refer to a variable set in the outer scope, we are passing them to the callback using the back-door thisArg mechanism. I don't really see the point in this.
Related
https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/#tidier-event-binding-with-queryselectorall demonstrates an approach of adding events to a NodeList:
var unboundForEach = Array.prototype.forEach,
forEach = Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('.klasses'), function (el) {
el.addEventListener('click', someFunction);
});
I don't quite understand two things:
Why does it need to bind the unboundForEach?
Why does it use Function.prototype.call.bind instead of Function.prototype.bind?
What .bind does is it creates a function similar to the original function, except:
The first argument is the this value to be used inside the function
The second, third, fourth, etc arguments passed to the bound function are passed as the first, second, and third etc arguments to the function
Using .bind here is one way of allowing for the creation a function that can take a collection as the first parameter (the this) and a callback as the second parameter. That said, as you've noticed, the way they're constructing this is pretty confusing.
var unboundForEach = Array.prototype.forEach,
forEach = Function.prototype.call.bind(unboundForEach);
To unpack this - when .call is used, it requires a this of the function to be invoked. For example
function fn() {
console.log(this.foo);
}
fn.call({ foo: 'foo' });
The above invokes Function.prototype.call with a this value of fn. The argument passed in is then used as the this value when fn is called. Equivalently, you could do
function fn() {
console.log(this.foo);
}
const invokeFnWithAThisOfTheArgument = Function.prototype.call.bind(fn);
invokeFnWithAThisOfTheArgument({ foo: 'foo' });
Your code's Function.prototype.call.bind(unboundForEach); is doing the same thing - .bind is used so that .call's this is bound to the prototype method, so that the prototype method is what gets invoked when the function returned by .call gets invoked.
Why does it use Function.prototype.call.bind instead of Function.prototype.bind?
Using only .bind
forEach = Function.prototype.bind(unboundForEach);
would mean that you'd eventually be invoking Function.prototype with a this value of unboundForEach. But Function.prototype doesn't do anything with its arguments or parameters - it's a no-op.
Now, while you can use the .call.bind approach, it's pretty hard to decipher. I'd highly recommend against it, and instead do something like
document.querySelectorAll('.klasses').forEach(
or, for the more general case, write out a higher-order function
const bindToNew = (fn) => (thisToPassToFn, ...args) => {
return fn.apply(thisToPassToFn, args);
};
const forEach = bindToNew(Array.prototype.forEach);
forEach(document.querySelectorAll('.klasses'), function (el) {
Stand alone forEach
The posted forEach function uses the syntax
forEach( arrrayLikeObject, callback)
similar to jQuery's $.forEach utility function, excepting that the callback arguments are those used by Array.prototype.forEach and the function won't iterate over enumerable keys of objects that are not array like - Array.prototype.forEach is intentionally generic and can be called on objects that are not instances of Array.
Calling the function with a node list argument serves as an example of calling it with an array-like argument.
Using Array.prototype.forEach
One of the ways to call forEach on an array-like object is
Array.prototype.forEach.call(arrayLike, callback)
where
Array.prototype.forEach is a function object to be called which needs an array like object as its this value ,
Function.prototype.call is a function method to call its this value, a function object, using the this value supplied as its first argument and passing any remaining argument values to the called function.
Normally call is inherited through the prototype chain of whatever function it is called on, and sees the function it is called on as its this value.
Looking closely at forEach.call,
forEach inherits call from Function.prototype on which forEach is prototyped.
call can be directly accessed as Function.prototype.call
call has a this value of forEach because its called on the forEach (function) object.
The this value of call can be set in an exotic function created by Function.prototype.bind
Hence to set the this value of call to forEach we can use
const callForEach = Function.prototype.call.bind( Array.prototype.forEach);
Note the order of method calls: bind is being called on call, and forEach becomes the bound function's this value.
All that's left now is to write the stand-alone forEach function using the callForEach trick:
function forEach( arrayLikeOject, callback) {
const callForEach = Function.prototype.call.bind( Array.prototype.forEach);
callForEach( arrayLikeObject, callBack);
}
This is equivalent to the posted code.
Answers
Q1 Why does it need to bindunboundForEach?
A1 It doesn't. It binds Function.prototype.call using Array.prototype.forEach as its this value.
Q2 Why does it use Function.prototype.call.bind instead of Function.prototype.bind?
A2 It does use Function.prototype.bind - but it's being inherited by call, the function object being bound.
Variable names
It would seem the biggest problem here is a variable name chosen by the original authors: unboundForEach makes readers infer there is a bound version of forEach somewhere. There isn't.
I'm learning functional programming and node.js, and I came across this odd problem when using Function.prototype.apply and .bind.
function Spy(target, method) {
var obj = {count: 0};
var original = target[method]
target[method] = function (){//no specified arguments
obj.count++
original.apply(this, arguments)//only arguments property passed
}
return obj;
}
module.exports = Spy
This code works, it successfully spies on target.method.
//same code here
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
//same code here
This code, however, does not. It gives an error message: TypeError: CreateListFromArrayLike called on non-object.
And then the biggest surprise is, this method works perfectly fine.
//same code here
target[method] = function (args){
obj.count++
original.bind(this, args)
}
//same code here
So why exactly do I get this error? Is it because function arguments are not necessarily objects? Or is it because apply has a stricter description than bind?
In this version:
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
Here you are not taking all the arguments but just one, named args. Since apply expects an array like object you cannot use args since it is only the first argument passed to the original target.
You can change it to:
target[method] = function (arg){ //only one argument specified
obj.count++
original.apply(this,[arg]) //one argument passed here
}
Now it works, but you can only spy on one argument functions. Using call would be better since you only have one extra argument:
target[method] = function (arg){ //only one argument specified
obj.count++
original.call(this,arg) //one argument passed here
}
Now bind is a totally different animal. It partial applies functions, thus return functions. Imagine you need to send a callback that takes no arguments but calls a function with some arguments you have when making it. You see code like:
var self = this;
return function() {
self.method(a, b);
}
Well. bind does this for you:
return this.method.bind(this, a, b);
When calling either of these returned functions the same happens. The method method is called with the arguments a and b. So calling bind on a function returns a partial applied version of that function and does not call it like call or apply does.
bind is called the same way as call is, even though they do very different things.
If you really wanted to use bind in this way. You could use the spread operator (ES2015) to expand the arguments 'array' to individual arguments:
original.bind(null, ...args);
That will bind the original function with the array values as individual arguments.
I was trying to solve https://twitter.com/secoif/status/730207047892017153 when I got an error message I don't understand. I get the error when running this code
const fns = [
function () {
console.log(1)
},
function () {
console.log(2)
},
function () {
console.log(3)
}
]
fns.map(Function.prototype.call.bind)
Chrome tells me "Bind must be called on a function", which I don't understand. The following line, which should be equivalent, does not throw the same error.
fns.map((x) => Function.prototype.call.bind(x))
To solve the JS pop quiz, you can do this:
for (var x in fns) fns[x]();
However, I realize that's not what you're asking :).
There are a few things I don't understand in your approach:
1) Why are you using .map()? Map is used for returning another array, which is not needed, so why not forEach() instead?
2) I'm not sure why you are using bind. When using map(), the callback is passed 3 parameters: the current function, the index of the function in the array, and the array itself. When you look at the syntax for bind(), you'll notice the first param for bind is the 'this' object, followed by the parameters to be passed in the function being bound to. In this case, 'this' will be set to the current function, index and array will be passed as parameters to the function.
3) Using bind on call. call() will take the same parameters a bind(), where the first one is the 'this' and the rest are parameters to be passed into the function being called. When you used .bind(), it will set the 'this' object as function and the first param will be the index. So from the perspective of .call(), you're setting it's 'this' to the function, and passing the index as the first param to call(), which is call's 'this', which then passes the whole array as the first parameter to the function.
Long story short, you're getting your values all mixed up and your overcomplicating this.
from the docs mdn docs for map
thisArg Optional. Value to use as this when executing callback.
Default value is the Window object
As marked before, you can map:
fns.map(Function.prototype.call.bind, Function.prototype.call.bind)
If you call:
fns.map(Function.prototype.call.bind)
Bind apply on object not function! and error raised, because object has not bind method.
Consider the code below, which works fine. It is the outcome of some debugging. It appears to work because I have inlcluded selectedRowNum not only in bind but it seems I am also required to include selectedRowNum as a parameter to the anonymous callback that is run by .end
Does this make sense? If I bind a variable, must I also include it as a param to the function I am binding it to?
for (var i = selectedRows.length; i--;) {
var selectedRowNum = selectedRows[i];
console.log('outer selectedRowNum');
console.log(selectedRowNum);
var url = urlbase + '/' + this.state.data[selectedRowNum].id;
request
.del(url)
.end(function(selectedRowNum, err, res) {
var data = this.state.data.slice();
data.splice(selectedRowNum, 1);
this.setState({data: data});
this.forceUpdate();
}.bind(this, selectedRowNum));
};
Yes, you need to.
The args values passed to the bind() will be prepended to the called functions param list, so you need to receive it in the target function as arguments.
A simple example will be
function x(p1, p2, p3) {
console.log(p1, p2, p3)
}
var fn = x.bind(window, 1);
fn(2, 3);
fn('a', 'b');
where we are passing an additional param 1 to the bind and when the binded function is called we are passing 2 and 3, now when we receive it in the method we need to have 3 parameters there.
How else do you expect to reference the bound value that you're trying to pass in?
function(selectedRowNum, err, res) { here selectedRowNum is simply a reference to the first argument that's passed in.
Well, technically the answer is no you don't need to. You could not list it as an argument, and refer to it as arguments[0]
Arguments are not absolutely required to be in the anonymous function declaration because you can access arguments via the arguments object as in arguments[0] and arguments[1].
But it is best to declare them as named arguments because it makes your code easier to read and write. If you don't declare them as named arguments in the anonymous declaration, then there is no symbolic name by which to refer to them and you are forced to use the arguments object to access them.
This is an important aspect of Javascript. Declaring named function arguments in a function declaration (whether anonymous or not) just gives you name by which you can refer to that specific argument. Since having a symbolic name is an important part of creating readable code, it is generally considered a good practice to follow.
When you use .bind() with arguments in addition to just the first argument, you are prepending more arguments to the actual function call. This will shift any other arguments later in the argument list. In order to access the correct argument, the anonymous function declaration must use the variables from the right spot in the argument list. If you don't include the extra variables that are added via .bind(), then your other arguments will be out of position and will have the wrong values.
I'm reading Javascript: The Definitive Guide 6th Edition. It teaches ECMAscript 5. Anyway, it doesn't explain certain things thoroughly, like the call() function for example. This is about the extent of the book's definition:
Any arguments to call() after the first invocation context argument are the values that are passed to the function that is invoked. For example, to pass two numbers to the function f() and invoke it as if it were a method of the object o, you could use code like this:
f.call(o, 1, 2);
In the next section the author builds a map function. I've been studying Ruby so I know how map works. My question is about the implementation using the call() function. It looks like this:
var map = function(a,f, o) {
var results = [];
for(var i = 0, len = a.length; i < len; i++) {
if (i in a)
results[i] = f.call(o || null, a[i], i, a);
}
return results;
};
It then defines a square function and puts map to use:
function square(x){
return x*x;
}
var array = [1,2,3,4,5];
var results = map(array, square);
What is the purpose of the i, and a parameters in the call() function? If I remove them I get the same results.
Array.prototype.map is defined to pass the index and the array to the callback, just in case you need them. For example, instead of square(x), you could use Math.pow(base, exponent):
var results = [1, 2, 3, 4, 5].map(Math.pow);
console.log(results); // [1, 2, 9, 64, 625]
This map behaves in the same way. You don’t have to use the arguments if you don’t need them in a particular case.
Function.call allows you to call a function as though it were a method attached to an object.
What this means is you can have a function that is defined somewhere unrelated to an object, and then you can call that function as though it was a part of that object. This is a long way of saying that when you use Function.call, you are telling the JS engine to use the first parameter whenever you use 'this' inside the function.
So:
function set_field_value(name, value) {
// do stuff
this[name] = value;
}
makes no sense by itself, because the special variable 'this' is not set to anything (meaningful)
But, if you use call, you can set it to whatever you want:
// if my_object = some object:
set_field_value.call(my_object, 'firstname', 'bob');
console.log(my_object.firstname); // prints 'bob'
The only important argument to call is the first one, (in the above case, my_object) because the first argument becomes 'this' inside the function. The rest of the arguments are passed 'as is' to the function.
So - in your example, the i and a arguments are there to make the map function look like other map functions, which provide the array (a) and index (i) that are being worked on.
Hope that helps,
Jay
PS - I strongly recommend the book 'Javascript: the good parts' - it makes a lot more sense than the definitive guide.
f.call in this example equals to square.call, and square requires only one parameter(x), so i and a are totally redundant here (and not used). Only a[i] is used by the function.
However, since you can pass in any function you want as the second parameter of the map function, chances are there will be another function instead of square coming up in the book, and that function would require those additional two parameters as well. Or you can make one example yourself to try it.
function threeParams(a, b, c) {
return [a, b, c]; // simply puts the three parameters in an array and returns it
}
var array = [1,2,3,4,5];
var results = map(array, threeParams);
Your main confusion is not really about the call method. It's more about how javascript treats function arguments.
Forget about call for a moment and let's look at a regular function to minimize the number of things under consideration.
In javascript, functions are allowed to be called with more arguments than is specified. This is not considered an error. The arguments may be accessed via the arguments object:
function foo (arg1) {
alert('second argument is: ' + arguments[1]);
}
foo('hello','world'); // this is not an error
Javascript also allows functions to be called with fewer arguments than specified. Again, this is not considered an error. The unpassed arguments are simply given the value undefined:
function foo (arg1,arg2, arg3) {
alert('third argument is: ' + arg3);
}
foo('hello'); // this is not an error
That's all there is to it. When the function passed to map() is defined to accept one argument but map() calls it with three the remaining two arguments are essentially ignored.