I am reading info about array destructuring and the spread syntax from MDN and I have stumbled upon the following example that left me a bit cold, despite going over the material progressively.
The code giving me trouble is this:
function myFunction(v, w, x, y, z) { }
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);
I get that v==-1; w==0; x==1; and y==2... but what is ...[3]?
The example is from the spread syntax hyperlink above.
The example you've found is a bit contrived, and you probably wouldn't see it in real code. It's just meant to illustrate that the spread argument syntax ... works with any iterable expression, including standard array literals like [1, 2, 3]. z will be 3 because
myFunction(-1, ...args, 2, ...[3]);
is equivalent to
myFunction(-1, ...args, 2, 3);
The ...[ and ] essentially have no effect in this case; the values are pulled out of the array so it's as though you'd just written them directly in the argument list. As another example,
myFunction(-1, ...args, 2, ...[3, 4, 5]);
is equivalent to
myFunction(-1, ...args, 2, 3, 4, 5);
although z would still be 3 (4 and 5 would be ignored because they're unexpected extra arguments).
Let's break this down a bit: the behaviour of the spread/rest syntax ... in an argument list is defined in section 12.3.6.1 "Runtime Semantics: ArgumentListEvaluation" of ECMAScript 6. To paraphrase, it essentially says "when you see ...X in an argument list, evaluate X, then go through the values it contains and add each of them as a separate argument".
So, when JavaScript sees , 3 in an argument list, it says "add 3 to the argument list".
But when JavaScript sees , ...[3] in an argument list, it says "create a new array containing 3, then go through each of its values (only 3) and add them to the argument list".
You're doing the same thing in both cases, but the simpler way is probably faster.
Related
(() => console.log(arguments))(1,2,3);
// Chrome, FF, Node give "1,2,3"
// Babel gives "arguments is not defined" from parent scope
According to Babel (and from what I can tell initial TC39 recommendations), that is "invalid" as arrow functions should be using their parent scope for arguments. The only info I've been able to find that contradicts this is a single comment saying this was rejected by TC39, but I can't find anything to back this up.
Just looking for official docs here.
Chrome, FF, and node seem to be wrong here, Babel is correct:
Arrow functions do not have an own arguments binding in their scope; no arguments object is created when calling them.
looking for official docs here
Arrow function expressions evaluate to functions that have their [[ThisMode]] set to lexical, and when such are called the declaration instantiation does not create an arguments object. There is even a specifc note (18 a) stating that "Arrow functions never have an arguments objects.".
As noted by Bergi, arrow functions do not have their own arguments variable.
However, if you do want to capture the args for your arrow function, you can simply use a rest parameter
const myFunc = (...args) =>
console.log ("arguments", args)
myFunc (1, 2, 3)
// arguments [1, 2, 3]
Rest parameters can be combined with other positional parameters, but must always be included as the last parameter
const myFunc = (a, b, c, ...rest) =>
console.log (a, b, c, rest)
myFunc (1, 2, 3, 4, 5, 6, 7)
// 1 2 3 [ 4, 5, 6, 7 ]
If you make the mistake of writing a rest parameter in any other position, you will get an Error
const myFunc = (...rest, a, b, c) =>
console.log (a, b, c, rest)
myFunc (1, 2, 3, 4, 5, 6, 7)
// Error: Rest parameter must be last formal parameter
I saw code like this:
let a = 5;
[...Array(a).keys()].map(x => console.log(x));
I really want to know how all these things work. I understood that it's mapping over an array and showing output in the console. But I'm confused about what's happening inside this []. How rest operator is working and then finding the corresponding key?
Thanks in advance.
... This is not the rest operator, this is spread operator (in this situation).
The Array(5) creates an empty array with length 5.
[...Array(5)] creates this [undefined, undefined, undefined, undefined, undefined].
At last [...Array(5).keys()] creates
[0, 1, 2, 3, 4]
Because the keys of any array in JavaScript (except if the keys have been explicitly modified) are the numbers 0, 1, 2....
I encountered strange behavior of Array.prototype.includes in one edge case.
Given that Array.prototype.includes works on bound context, one might use it like this (which is working)
expect(Array.prototype.includes.call([1, 2], 1))).toBe(true)
simply put, we bound array [1, 2] and test 1 for inclusion.
Then consider, that many Array.prototype methods are able to bound context to provided callback, so for example Array.prototype.some can be combined with Object.prototype.hasOwnProperty like this
expect(["foo", "bar"].some(Object.prototype.hasOwnProperty, { foo: 0 })).toBe(true)
Here, .some accepts two parameters, (callback, [thisArg]), where optional thisArg, when provided, is bound to callback, thus previous example binds { foo: 0 } to callback Object.prototype.hasOwnProperty and then tests all items in ["foo", "bar"] if at least one is own property of { foo: 0 }. This example is also working.
But something strange happen, if you try to use Array.prototype.includes as callback.
[0, 1].some(Array.prototype.includes, [1]) // => false
here we bind array [1] to Array.prototype.includes and we test every item of [0, 1] if at least one is included. But this case returns false, which is against our expectation.
Strangely, if bound array contains other number than 1 or contains more than one item, the test passes
[0, 1].some(Array.prototype.includes, [0]) // => true
[0, 1].some(Array.prototype.includes, [1, 1]) // => true
// but
[0, 1].some(Array.prototype.includes, [1]) // => false
It seems like array [1] is handled improperly.
Tested in
Node v.11.11.0
Node v.8.11.3
and Chrome 73
I tested basically just V8 engine. Can anyone report output in Chakra?
It's not a bug in includes. :-)
The problem is that includes accepts an optional second parameter, which is the index at which to start searching, and some provides three arguments to its callback: The item, its index, and the object being searched.
So with
[0, 1].some(Array.prototype.includes, [1])
These calls are made (effectively):
[1].includes(0, 0) - false, the array doesn't contain 0
[1].includes(1, 1) - false, the array contains 1 at index 0, but the search starts at index 1
This comes up periodically. For instance, when trying to use parseInt as a callback directly to convert an array of strings into an array of numbers, because of parseInt's second parameter (the number base, or radix, to use):
console.log(["6", "9", "7"].map(parseInt));
The 9 and 7 fail becaue the calls are (effectively):
parseInt("6", 0) - works because parseInt ignores the invalid radix 0.
parseInt("9", 1) - NaN because parseInt always returns NaN for radix 1
parseInt("7", 2) - NaN because "7" is not a valid digit in base 2 (binary)
The moral of the story: Remember the not-commonly-used arguments that map, some, forEach, and various other methods provide to callbacks. :-)
In one codebase I was working in, they had a clamp function that accepted a function and ensured that, regardless of how many arguments it was called with, it would only pass on the desired number of arguments. If you were using includes like this a lot, you could create a clamped includes:
function clamped(fn, count) {
return function(...args) {
return fn.apply(this, args.slice(0, count));
}
}
const includes = clamped(Array.prototype.includes, 1);
console.log([0, 1].some(includes, [1])); // true
console.log([0, 1].some(includes, [3])); // false
The handy thing about that is that that includes is reusable.
Or of course, just use a wrapper function:
console.log([0, 1].some(function(entry) {
return this.includes(entry);
}, [1])); // true
console.log([0, 1].some(function(entry) {
return this.includes(entry);
}, [3])); // false
These are all meant to be general solutions, of course. If you specifically want to know if array a contains any of the entries in array b, there are more specific implementations you can build to handle that efficiently depending on the characteristics of a and b.
In JavaScript, some methods make a copy of the object that invoked it while others do not.
For example:
var numbers = [1, 2, 3, 4, 5];
numbers.map(function(x) { return x + 1 });
console.log(numbers); // [1, 2, 3, 4, 5];
It makes a copy of "numbers" that you have to set to another variable.
Whereas:
var numbers = [1, 2, 3, 4, 5];
numbers.reverse();
console.log(numbers); // [5, 4, 3, 2, 1];
It changes the "numbers" directly. Could anyone please explain why?
This is due to the difference in time of incorporation of methods into the JavaScript.
Method reverse was there from the first version of ECMAScript.
The map was added relatively recently in the 5th version.
There is a trend to be more functional nowadays among languages. One of the main principles in functional languages is immutability of data. Therefore, these new methods of the array (namely map, filter etc) are functional and do not the change source array.
The array methods in javascript is broadly classified into three
- Mutator methods
- Accessor methods
- Iteration methods
Mutator methods - Ex : reverse(), push () etc : modify the array . As the name suggest these methods mutates the array on which they are called upon.
Accessor methods - Ex : include(), concat() etc : - do not modify the array and return some representation of the array.i.e a new array is returned which is modified array.
Iteration methods -Ex : filter(), map()- take as arguments functions to be called back while processing the array. In these methods the length of the array is already sampled/copied and the callback is performed on this arrary.
Generic methods (non-standard) - EX: join() These methods are generic in nature and is applicable to objects which “look like” Arrays.
The detailed explanation on this can be found in the below link :
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype
Hope this helps!!!
(() => console.log(arguments))(1,2,3);
// Chrome, FF, Node give "1,2,3"
// Babel gives "arguments is not defined" from parent scope
According to Babel (and from what I can tell initial TC39 recommendations), that is "invalid" as arrow functions should be using their parent scope for arguments. The only info I've been able to find that contradicts this is a single comment saying this was rejected by TC39, but I can't find anything to back this up.
Just looking for official docs here.
Chrome, FF, and node seem to be wrong here, Babel is correct:
Arrow functions do not have an own arguments binding in their scope; no arguments object is created when calling them.
looking for official docs here
Arrow function expressions evaluate to functions that have their [[ThisMode]] set to lexical, and when such are called the declaration instantiation does not create an arguments object. There is even a specifc note (18 a) stating that "Arrow functions never have an arguments objects.".
As noted by Bergi, arrow functions do not have their own arguments variable.
However, if you do want to capture the args for your arrow function, you can simply use a rest parameter
const myFunc = (...args) =>
console.log ("arguments", args)
myFunc (1, 2, 3)
// arguments [1, 2, 3]
Rest parameters can be combined with other positional parameters, but must always be included as the last parameter
const myFunc = (a, b, c, ...rest) =>
console.log (a, b, c, rest)
myFunc (1, 2, 3, 4, 5, 6, 7)
// 1 2 3 [ 4, 5, 6, 7 ]
If you make the mistake of writing a rest parameter in any other position, you will get an Error
const myFunc = (...rest, a, b, c) =>
console.log (a, b, c, rest)
myFunc (1, 2, 3, 4, 5, 6, 7)
// Error: Rest parameter must be last formal parameter