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.
Related
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.
Calling reduce on an empty array throws TypeError which is perfectly understandable and helps catching bugs. But when I call it on an array with a single item inside, the behavior confuses me:
var arr = ["a"];
arr.reduce(function(a,b){
return [a,b]
}); //returns "a"
I know that reduce is not meant to be used on such an array, but I find that returning just the element without invoking the callback or throwing an error is at least strange.
Furthermore, the MDN documentation states that the callback is a "Function to execute on each value in the array, taking four arguments:".
Can someone explain the reasoning behind this behaviour?
The callback is supposed to be a "binary function" (i.e. one that takes two arguments to operate on, plus the additional two arguments that hold the currentIndex and the original array).
If only one element is supplied, you would be passing an undefined value to the callback for either currentValue or previousValue, potentially causing runtime errors.
The design therefore assumes that given only one value (whether that be an empty array, and an initialValue, or an array with one value and no initialValue) it's better not to invoke the callback at all.
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.
numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(i){
return (i > 2);
});
I don't understand how this works. if I omit the i as a function argument it breaks the function but the i isn't tied to anything so why does it need to be there?
.filter (Array.prototype.filter) calls the supplied function with 3 arguments:
function(element, index, array) {
...
element is the particular array element for the call.
index is the current index of the element
array is the array being filtered.
You can use any or all of the arguments.
In your case, i refers to the element and is used in the body of your function:
function(i){
return (i > 2);
}
In other words, "filter elements where element is greater than 2".
i is a reference to the current object in the set when inside that closure. It could be named anything as it is just a variable, but then would have to have the same name inside the closure. Instead of using function(){} you could use a callback which is how filter was designed.
The reference is done implicitly by the definition of .filter, you can read more here: http://msdn.microsoft.com/en-us/library/ff679973(v=vs.94).aspx
The i is actually very important. It tells gives the filter function information about the elements it's acting on. In fact it's used right here (i > 2).
This keeps elements whose value is greater than 2.
That i is the formal parameter for the function you are supplying to .filter(). If you do not insert it, the function will not have any way¹ to refer to the argument it's being passed (the i inside the function body will then refer to some other entity that might not even be defined -- window.i would be typical).
¹ that is technically a lie, but consider it true for the purposes of this discussion
An old thread indeed, but just filling in what remains unsaid.
The parentheses are there for you the programmer to insert whatever variable name makes sense for your specific program.
If you choose 'i', most other (beginner) programmers might think 'Oh, i means index'. Which would be wrong.
If you use one argument instead of three, I'd choose 'el' to represent the element, or if your array contains flavors of soda, I'd choose 'flavor'.
That's ES5 notation and maybe if you see it in ES6 notation you would understand why the "i" is a must:
numbers.filter(i => i > 2);
A variable must always be used to refer to the item of the array that you process in each iteration (in this case "i"). It has to be passed as argument to the entry point of the function (in ES6 that goes before the arrow).
I'm currently reading through this jquery masking plugin to try and understand how it works, and in numerous places the author calls the slice() function passing no arguments to it. For instance here the _buffer variable is slice()d, and _buffer.slice() and _buffer seem to hold the same values.
Is there any reason for doing this, or is the author just making the code more complicated than it should be?
//functionality fn
function unmaskedvalue($input, skipDatepickerCheck) {
var input = $input[0];
if (tests && (skipDatepickerCheck === true || !$input.hasClass('hasDatepicker'))) {
var buffer = _buffer.slice();
checkVal(input, buffer);
return $.map(buffer, function(element, index) {
return isMask(index) && element != getBufferElement(_buffer.slice(), index) ? element : null; }).join('');
}
else {
return input._valueGet();
}
}
The .slice() method makes a (shallow) copy of an array, and takes parameters to indicate which subset of the source array to copy. Calling it with no arguments just copies the entire array. That is:
_buffer.slice();
// is equivalent to
_buffer.slice(0);
// also equivalent to
_buffer.slice(0, _buffer.length);
EDIT: Isn't the start index mandatory? Yes. And no. Sort of. JavaScript references (like MDN) usually say that .slice() requires at least one argument, the start index. Calling .slice() with no arguments is like saying .slice(undefined). In the ECMAScript Language Spec, step 5 in the .slice() algorithm says "Let relativeStart be ToInteger(start)". If you look at the algorithm for the abstract operation ToInteger(), which in turn uses ToNumber(), you'll see that it ends up converting undefined to 0.
Still, in my own code I would always say .slice(0), not .slice() - to me it seems neater.
array.slice() = array shallow copy and is a shorter form of array.slice()
Is there any reason for doing this, or is the author just making the code more complicated than it should be?
Yes there may be a reason in the following cases (for which we do not have a clue, on whether they apply, in the provided code):
checkVal() or getBufferElement() modify the content of the arrays passed to them (as second and first argument respectively). In this case the code author wants to prevent the global variable _buffer's content from being modified when calling unmaskedvalue().
The function passed to $.map runs asynchronously. In this case the code author wants to make sure that the passed callback will access the array content as it was during unmaskedvalue() execution (e.g. Another event handler could modify _buffer content after unmaskedvalue() execution and before $.map's callback execution).
If none of the above is the case then, yes, the code would equally work without using .slice(). In this case maybe the code author wants to play safe and avoid bugs from future code changes that would result in unforeseen _buffer content modifications.
Note:
When saying: "prevent the global variable _buffer's content from being modified" it means to achieve the following:
_buffer[0].someProp = "new value" would reflect in the copied array.
_buffer[0] = "new value" would not reflect in the copied array.
(For preventing changes also in the first bullet above, array deep clone can be used, but this is out of the discussed context)
Note 2:
In ES6
var buffer = _buffer.slice();
can also be written as
var buffer = [..._buffer];