I saw this script somewhere else, and it will check every single checkboxes:
[].forEach.call(document.querySelectorAll('input[type="checkbox"]'),function(el){
el.checked=true;
}
);
I know how to use forEach:
[0,1,2].forEach(function(num){
console.log(num);
});
//0
//1
//2
But now, it is [].forEach, and there is nothing inside. So why does it still work? Why can't I do this instead?
document.querySelectorAll('input[type="checkbox"]').forEach(function(el){
el.checked=true;
}
);
JavaScript has first-class functions; that is, they're treated like objects, and can have their own properties and methods. The built in Function.call method takes a this parameter for the function as its first argument, and the rest of the arguments are passed to the function itself. The array [] is not used, except as a means to access the (less concise, so less used) Array.prototype.forEach method.
It's basically a rebinding of Array.forEach for use on something that is not an array, in this case a NodeList. If NodeLists offered a forEach method, then it would be equivalent to, and you may read it as, this:
document.querySelectorAll('input[type="checkbox"]').forEach(function(el) {
el.checked = true;
});
So, a little more in depth. call will execute a function with a different context. forEach iterates over the context and calls the function it is passed as its argument. So a someFunc.call(thisArg, otherArg) will be executed as if it were in the context of thisArg, like thisArg.someFunc(otherArg). Here's the simplest example:
function callMe(something) {
return something + this;
}
callMe('Hello'); // Hellonull or Hello[object Window] or something
callMe.call({}, 'World'); // World[object Object]
apply() works the same way, but you pass an array of arguments as the second argument.
This is just using [] to get the forEach function. Once it has the function, it uses .call to call it as if it was a method of document.querySelectorAll('input[type="checkbox"]').
This is useful because the result of document.querySelectorAll is not an Array but behaves just like one, so we can reuse the standard Array methods.
When you use .call, the first argument is used as the this value. That is, in this particular snippet, every time this is encountered inside the source of forEach, it is set to document.querySelectorAll('input[type="checkbox"]').
You can't just call document.querySelectorAll('input[type="checkbox"]').forEach(... directly because querySelectorAll does not return an Array object and so does not have a forEach method. The whole .call thing is just a way to get around this by calling forEach as if it was a method of NodeList, which is what is actually returned.
Notice the .call. It applies (sets this to) the results of document.querySelectorAll to forEach, so that it iterates over those results rather than the (empty) array on the left-hand side of the dot.
Related
I have been refreshing my JavaScript knowledge of call() and map() usage on NodeList.
It was fairly easy to google out, what call() should be doing and there are resources with examples of how it works with map().
However, as I noticed on MDN, the map() function can also take a second argument, which should be setting the this keyword for map() - or at least that is what I think it should be doing.
I have tried to check it myself with simple arrays:
var numbers = [1, 2, 3];
var letters = ['a', 'b', 'c'];
..and with a simple function, that is about to be given as a parameter to map():
var effector = function (x) {
console.log(x);
}
Now, what I do not understand, is why these two function calls have different results:
numbers.map(effector, letters);
numbers.map.call(letters, effector);
I expect them to both output letters to the console, as both this keywords should be referencing to them (respectively to their objects).
I did some further research, and tried with this modified effector function:
var effector = function () {
console.log(this);
}
..again on:
numbers.map(effector, letters);
numbers.map.call(letters, effector);
Assuming, that we are in "use strict", the first call logs letters and the second logs undefined.
But again, I would expect both these calls to produce the same output.
What am I missing?
EDIT:
I was reading, how .map can be polyfilled, if you check it on MDN, there you see, how the second parameter of .map is used in callback.call().
I suppose, that in the end, even that callback.call() should have the same this as was in .map.
MDN - map
There are two bindings of the this reference at play here:
this for the execution context of the map function
this for the execution context of the callback function
They don't relate to each other.
The second argument of map dictates what this will be for the callback. If it is not provided, the default is undefined (not the array).
The first argument of map.call dictates what this will be for map -- and by consequence which array will be iterated.
This is also reflected in the polyfill provided on mdn: it is perfectly in line with these specifications: O gets the value of this for the map function, and T gets the value of this for the callback. They are generally different.
map versus forEach
Unrelated to your question, but worth mentioning: don't use map when you are not actually mapping anything. map is intended for creating a new array, one in which every value has been mapped by calling the callback function on the original value at that same index.
When however you just need to iterate the array values, without any intent to perform such mapping, then use the forEach method, or a for...of loop. Both these work on NodeList out of the box, without the need to .call.
The difference between numbers.map(effector, letters) and numbers.map.call(letters, effector) lies in which function the this is set to letters.
In the first, as MDN explains, the second argument is the value to use as this inside the callback - ie the argument supplied to map. That is, in numbers.map(effector, letters), the this inside effector will be letters. Since effector (in the first version) doesn't care what this is, that argument essentially has no effect, and the contents of numbers are logged.
In numbers.map.call(letters, effector), on the other hand, it is numbers.map that has its this set to letters. Which effectively means the map method, although called on numbers, is "hijacked" into logging the entries of letters instead.
The second example works similarly - the crucial thing is which function the this is set in: effector in one case, numbers.map in the other.
I'm trying to understand how to read the code below (taken from MDN's article on Array.prototype.slice) to understand what happens when it runs.
function list() {
return Array.prototype.slice.call(arguments);
}
My understanding is that the return statement gets a reference to the Array.protoype.slice method. This leads to my first question, "if this is a reference to the slice method, why doesn't it need to be invoked, e.g. Array.prototype.slice().call(arguments)?"
Assuming that this is a call to the slice method, and since there is no argument being immediately passed into it, my second question is "is JS 'seeing' the call method chained to slice and then trying to resolve a value to pass to slice from the call(arguments) method?"
If this is the case, is this method chaining and is this how JS performs chaining operations: from left to right and when there is no argument explicity passed to a method, it tries to resolve a value from a subsequent method to return implicitily to the "empty" callee on the left?
Thanks.
Why doesn't it need to be invoked? — because, as you say, it's a reference, and that's all that's desired. The code wants a reference to the function, not a result returned from calling the function.
Is JS 'seeing' the call method chained to slice and then trying to resolve a value to pass to slice from the call(arguments) method? — well I'm not sure what that means. The reference to the .slice() function is used to get access to the .call() method (inherited from the Function prototype). That function (slice.call) is invoked and passed the arguments object as its first parameter. The result is that slice will be invoked as if it were called like arguments.slice() — which is not possible directly, as the .slice() function isn't available that way.
Overall, what the code is doing is "borrowing" the .slice() method from the Array prototype and using it as if the arguments object were an array.
Sometimes you'll see that written like this:
return [].slice.call(arguments);
It's a little shorter, and it does the same thing (at the expense of the creation of an otherwise unused array instance).
call in javascript is a way of invoking a method within the function, which hard binds the context of this within a function to the parameter passed to it..For more detail regarding callgo through the link below
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
If you want to know more about this and why call is being used, I highly recommend you to go through this github repo:
https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/ch1.md
Secondly, by default every regular function expression in JS has an arguments object, which is an iterable which is nothing but the list of parameters passed to that function.
function foo()
{
console.log(arguments); //1,2
}
foo(1,2)
More about arguments
https://www.google.co.in/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=arguments%20in%20javascript
And if you want to learn JS properly, blindly go through this repo:
https://github.com/getify/You-Dont-Know-JS
Have a look at the docs for Function.prototype.call():
The call() method calls a function with a given this value and arguments provided individually.
Since .call is a part of Function.prototype, every function has it as a property, including Array.prototype.slice (or [].slice).
According to the docs for Array.prototype.slice:
If begin is undefined, slice begins from index 0. If end is omitted, slice extracts through the end of the sequence (arr.length).
It's getting a reference to the Array.prototype.slice property (function) and running its .call function. The 1st parameter to call is the context (or this value) and any other parameters are passed to the chained method. .slice() doesn't require any parameters and without any it just returns the elements as an array.
So, what's happening is that it's taking arguments - which is not actually an array, just an "array-like" object (with numeric properties and a .length property) - and running .slice() on it like it was an array. This makes it r"convert" arguments into an array.
When I reading this answer, find var g = f.call.bind(f);. I can't understand this with my first sight.
So does it has some direct meaning, and has some appropriate usage scenarios?
And further when you using call(or apply) or bind or both in chaining, what will happen? Is there some laws?
var g = f.call.bind(f);. I can't understand this with my first sight.
I assume you're familar with both the .call() and .bind() Function methods? Well, it binds the f.call method to the function f.
Notice that f.call is just Function.prototype.call, it doesn't matter that we access it as a property on f because we don't call it here.
So does it has some direct meaning?
It might become more obvious when we look at the ES6 equivalent:
(Function.prototype.call).bind(f) // or
(f.call).bind(f) // is basically
(...args) => f.call(...args) // or more clear
(ctx, ...args) => f.call(ctx, ...args)
Does it have some appropriate usage scenarios?
Well, now that you know what it does, yes. It can take a prototype method and transforms it to a static function that takes the instance as the first argument. An example:
function Example(n) { this.name = n; }
Example.prototype.display = function() { console.log(this.name); }
Example.display = Function.call.bind(Example.prototype.display);
var e = new Example;
e.display(); // is the same as
Example.display(e);
Are there any laws for further chaining call/apply/bind?
Yes: as always, only the last property in the chain is actually called as a method. In the above example, theres no difference between f.call.bind(…), Function.prototype.call.bind(…) or Function.call.apply.bind.call.bind(…) - it always calls bind on call.
However, by passing them as arguments to each other, you can do some crazy things which are more or less useful.
Good question. Let's start off by considering an example that's come up on StackOverflow before: mapping all the strings in an array to lowercase. Of course I can write
strings . map(function(string) { return string.toLowerCase(); })
but that seems a bit verbose. I'd rather write
strings . map(CALL_LOWERCASE_WITH_ELT_AS_THIS)
So I might try
strings . map(String.prototype.toLowerCase)
or, to use the shorter idiom some prefer
strings . map(''.toLowerCase)
because ''.toLowerCase is exactly equal to String.prototype.toLowerCase.
But this won't work, of course, because map passes each element to the specified function as its first argument, not as its this. Therefore, we need somehow to specify a function whose first argument is used to call some other function as its this. That, of course, is exactly what Function.call does:
function.call(context)
The first argument to call ("context") is used as the this when calling function.
So, problem solved? We ought to be able to just say:
strings . map(''.toLowerCase.call)
and people have tried this and then wonder why it didn't work. The reason is that even though we are passing call of toLowerCase as the callback to map, map still has no idea that the callback is supposed to be called with a this of ''.toLowerCase. We need to explicitly tell map which this to use to call the function, which in the case of map we can do with its second "context" argument:
strings . map(''.toLowerCase.call, ''.toLowerCase)
Actually, since call is the same on any function object, we can simplify this to just
strings . map(Function.call, ''.toLowerCase)
This works and gets the job done beautifully.
However, whereas map provides this second "context" argument to specify the this to call the callback with, that is not something we can depend on being available in all situations. We need a more general way to say "make a function which calls Function.call with some particular function as this".
That is exactly what bind does. It says "take a function and make another function which calls it with a particular this":
function.bind(context)
In our case, what we want to do is to "take the function Function.call and make another function which calls it with a this of ''.toLowerCase. That is simply
Function.call.bind(''.toLowerCase)
Now we can pass this to map without having to use the second argument:
strings . map(Function.call.bind(''.toLowerCase))
That works exactly the same as strings . map(Function.call, ''.toLowerCase), because in general map(fn, ctxt) is precisely equal to map(fn.bind(ctxt)).
The following breaks this down into a readable form, step by step:
Function . // From the Function object
call . // take the `call` method
bind( // and make a new function which calls it with a 'this' of
''.toLowerCase // `toLowerCase`
)
When this construct is specified as a callback, such as to map, it means:
Invoke call with the first argument passed in and ''.toLowerCase as this, which by virtue of the definition of call, means to call toLowerCase with that argument as this.
Some people prefer to simplify this a bit by saying
var call = Function.call;
var toLowerCase = ''.toLowerCase;
strings . map(call.bind(toLowerCase))
or, using the second argument provided by map, just
strings . map(call, toLowerCase)
which is almost readable as English: "map each string to the result of calling toLowerCase.
Another common, related use case would be specifying the callback in a then on a promise. Consider the following code:
promise . then(function(result) { result.frombulate(); })
That's fine, but it's a bit verbose. And then has no way to pass in a context to be used as this when invoking the success or failure handler. But with the above, we can now write:
promise . then(call.bind(frombulate))
There are other use cases for the call.bind idiom, but this is one of the most common ones: define a callback whose effect is to invoke some function with the parameter passed to the callback as its this.
With ES6 fat arrow functions, of course, I can write
promise . then(result => result.frombulate())
so there is relatively less advantage in the shorthand offered by call.bind(frombulate), and it is hard to deny that the fat-arrow version is more readable than that using bind.
The following question might be of interest too: Array.map and lifted functions in Javascript.
m.call.bind(m)
can be used as shorthand for:
function(x){return m.bind(x)()}
The former is the "point-free" form of the latter,
arguments are implicit. It would be useful with
list-operations like map(), making them shorter.
You can write stuff like:
let m = "".toUpperCase;
let fun = m.call.bind(m);
let see = ['a','b'].map(fun);
Is there a way in javascript to get a handle on a object prototype function with the this guaranteed to be the object in question? I particularly got to a PITA situation when I wanted to map an array of objects of the proper type to such a function. This ended up being Window. Yes I did pull the function itself from the proper place but internally it refers to 'this'. I know about call and apply but not how you might use them to handle this sort of situation.
Is there a way short of an explicit loop instead of using map? Oddly it apparently does not do the same thing re 'this' as
object_with_the_prototype.some_prototype_function()
would do if instead I have
[object_with_the_prototype].map(this.some_prototype_function)
How come?
I think you are looking for this:
[object_with_the_prototype].map(function(thisObject) {
thisObject.some_prototype_function();
} );
Ah, I see it. The problem is that in the array case I was mistakenly assuming that the normal walk from the object up its prototype chain would happen although I was giving it an explicit function to map to. I fixed it by defining a function taking an element from the array and calling the desired prototype function through that element. I map to that function instead.
function do_one(element) {element.some_prototype_function()}
[object_with_prototype].map(do_one)
if your method expects one argument, then it's easy. If you need more than one argument, or more importantly, not a number as a 2nd argument (think .toString), then you can use bind to curry this into the 1st argument:
"a,b,c".split(",").map( Function.call.bind( "".big ) );
in your code:
[object_with_the_prototype]
.map( Function.call.bind( thisObject.some_prototype_function ) );
if your method calls like x(a,b,c), the bound function calls like x(this, a, b, c)
There's an example in Secrets of the JavaScript Ninja that provides the following code to get around JavaScript's Math.min() function, which requires a variable-length list.
Example: Math.min(1,2,3,4,5);
But, there's a problem if you have a list: [1,2,3,4,5], since you'd rather not have to loop through the list, keeping track of the current min.
The book says to use the following code to solve this issue.
function smallest(arr) {
return Math.min.apply(Math, arr);
}
alert(smallest([1,2,3,4,5,6,-33]));
What happens with Math.min.apply(Math, arr) under the hood?
The .apply method "Calls a function with a given this value and arguments provided as an array (or an array like object)."
So basically it works as if you'd passed each item of the array as a separate parameter when calling the function directly.
The first parameter to .apply() sets the value of this to be used by the function, so your example passes Math so that it works the same as if you called it directly as Math.min(...).
Note that you can do the same thing with any function:
function someFunc(a,b,c,d) { ... }
someFunc(1,2,3,4);
// or
someFunc.apply(null, [1,2,3,4]);
Per the MDN documentation for Function.prototype.apply:
Calls a function with a given this value and arguments provided as an array (or an array like object).
The apply allows you to invoke a method on a given instance and provide the arguments as an array. So when you write
Math.min.apply(Math, arr);
You are invoking the min method on the Math class and passing arr as arguments.