I'm newbie in Javascript. And I faced the following question.
As stated here, almost all ES5 Array functions (forEach, map, filter, every, some) can take additional second argument.
If it specified the function is invoked as if it is a method of this second argument. That is, the second argument you pass becomes the value of the this keyword inside of the function you pass.
array.forEach(function(currentValue, index, arr), thisValue)
array.map(function(currentValue, index, arr), thisValue)
On the contrary:
array.reduce(callback, [initialValue])
array.reduceRight(callback, [initialValue])
Note that neither reduce() nor reduceRight() accepts an optional argument that specifies the this value on which the reduction function is to be invoked. See the Function.bind() method if you need your reduction function invoked as a method of a particular object.
What does it mean: "to be invoked as a method of a particular object"? Could anyone provide some example how it may affect on my code?
Thanks in advance.
It's simple. In the callback function, the this value will be the second argument passed to the array method. If you won't use this, then the argument is irrelevant and you don't need to pass it.
"use strict";
[0].forEach(function(currentValue, index, arr) {
console.log(this); // 1234
}, 1234);
Note that in sloppy mode, the this value is converted to an object. So if you omit the argument or use undefined, you will get the global object instead.
If you need something similar with reduce, then use bind:
"use strict";
[0, 1].reduce(function(prevValue, currValue, index, arr) {
console.log(this); // 1234
}.bind(1234));
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.
I'm going through John Resig's JavaScript ninja tutorial and on #51 I see this:
// Find the largest number in that array of arguments
var largestAllButFirst = Math.max.apply( Math, allButFirst );
allButFirst is just a small array of integers. I believe I understand what apply does, but I can't understand why Math is being passed as an argument to apply.
The first parameter of the .apply is the context. Inside the function body the this keyword will reference that value.
Example:
function sum(a){ return this + a; }
sum.apply(1, [1]); // will return 2
// or with .call
sum.call(1, 1); // also returns 2
By default if you call Math.max the context (the this keyword) is automatically set to Math. To keep this behavior Math is passed as the first parameter in apply.
Passing it Math is not necessary, anything will work here. Math indicates the context of the operation, however max does not require a context. This means that Math.max.apply(undefined, allButFirst) will also work. See this answer.
From Mozilla docs:
fun.apply(thisArg, [argsArray])
thisArg: The value of this provided for the call to fun. Note that this
may not be the actual value seen by the method: if the method is a
function in non-strict mode code, null and undefined will be replaced
with the global object, and primitive values will be boxed.
So, in your example, Math is being used as the context for the function (if the keyword this is used inside).
If no thisArg is used, then the default is the global object. So, it is good practice to give some context if possible.
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.
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.