This question already has answers here:
JavaScript "new Array(n)" and "Array.prototype.map" weirdness
(14 answers)
Closed 5 years ago.
consider I declare two variables like this (done within REPL, with node v7.7.2), which I expect to be arrays:
var x = Array(4)
var y = Array.from({length: 4})
then the following should work identically, but it doesn't:
x.map(Math.random)
[ , , , ]
y.map(Math.random)
[ 0.46597917021676816,
0.3348459056304458,
0.2913995519428412,
0.8683430009997699 ]
in looking, it seems x and y are both identical:
> typeof x
'object'
> typeof y
'object'
> Array.isArray(x)
true
> Array.isArray(y)
true
> x.length
4
> y.length
4
> typeof x[0]
'undefined'
> typeof y[0]
'undefined'
so why the difference?
Actually, it should not have the same results fot both cases. See the manual here about Array:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
If the only argument passed to the Array constructor is an integer
between 0 and 232-1 (inclusive), this returns a new JavaScript array
with its length property set to that number (Note: this implies an
array of arrayLength empty slots, not slots with actual undefined
values). If the argument is any other number, a RangeError exception
is thrown.
And here about the Array.from() method
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/from
Check the following example on the page linked above:
// Generate a sequence of numbers
// Since the array is initialized with `undefined` on each position,
// the value of `v` below will be `undefined`
Array.from({length: 5}, (v, i) => i);
// [0, 1, 2, 3, 4]
For the first three outputs, Array#map works.
It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
The ECMA 262 standard to Array.from describes a construction with length for a new array (point 7 ff).
var x = Array(4),
y = Array.from({ length: 4 }),
arrayX = x.map(Math.random),
arrayY = y.map(Math.random),
z = Array.from({ length: 4 }, Math.random);
console.log(x);
console.log(y);
console.log(arrayX);
console.log(arrayY);
console.log(z);
The Array created with Array(4) is not iterated over with .map(), whereas the Array.from({ length: 4 }) is iterated over. A fuller explanation can be found at JavaScript "new Array(n)" and "Array.prototype.map" weirdness, you can test this with..
x.map((f,i) => console.log(i)) vs. y.map((f,i) => console.log(i))
Related
The code below is modified version of a code taken from the book Professional JavaScript for Web Developers.
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
function numElements(typedArrayConstructor, ...typedArrays) {
// Count the total elements in all arrays
return typedArrays.reduce((x,y) => (x.length || x) + y.length);
}
console.log(numElements(Int32Array, Int8Array.of(1, 2, 3), Int16Array.of(4, 5, 6), Float32Array.of(7, 8, 9)));
My question is what does the (x.length || x) do? Why do we need to perform an or operation on x.length and x?
A little more explanation to go with Pointy's answer:
The || in JavaScript isn't just a logical OR operation, it deals with "truthy/falsey" values, not just booleans.
undefined is falsey. When the first operand of || is falsey, the second operand is evaluated, and becomes the result of the expression. Thus undefined || 0 equals 0.
In your sample code, this means when x is 0, you add 0, and get a proper numeric result. If you try to add to undefined to another number, all of your calculations turn into NaN after that.
When .reduce() is invoked with only one argument, the very first iteration uses element 0 as the first callback parameter and element 1 as the second.
In your case, that means that on the first iteration, x will be one of the arrays (note that y is always an array). Thus that little expression differentiates between when it's the first iteration and when it's a subsequent iteration by taking advantage of the fact that
someNumber.length
is always undefined. (As correctly noted in another answer, it's critical to recall that (undefined || x) will always be x, whatever its value may be.) On subsequent iterations therefore x is the running total of the array lengths.
The .reduce() could have been written as follows instead:
return typedArrays.reduce((x,y) => x + y.length, 0);
By passing the second argument (0) to .reduce(), the first callback invocation will be the same as the others, and x will always be a number.
If x has any elements and x exists then use the length in the sum. Otherwise if length is undefined then return the current element x
Example 1: - happens on first iteration of reduce loop
x is array [1, 2, 3]
x.length || x -> returns the length of array or current total
// summation of code would do the following
firsthArrayLength + secondArrayLength = newTotal
Example 2: - happens on rest of iterations of reduce loop
x is integer 5
x.length || x -> returns x since length of integer is undefined
// summation of code would do the following
currentTotal + curLength = newTotal
NOTE: Keep in mind that with this example if any of the arrays is null or undefined then it will throw since we cannot access property length of undefined or null
So sad that is taken from a book. Using reduce and || like that is reckless and unprofessional -
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
const numElements = (constructor, ...arrs) =>
new constructor(arrs.reduce((r, a) => r + a.length, 0))
const result =
numElements
( Int32Array
, Int8Array.of(1, 2, 3)
, Int16Array.of(4, 5, 6)
, Float32Array.of(7, 8, 9)
)
console.log(result.constructor)
console.log(result)
// Int32Array
// { 0, 0, 0, 0, 0, 0, 0, 0, 0 }
The description of the function says it should concat the other arrays, not just initialise an empty typed array. Here's what that might look like -
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
const concatMixed = (constructor, ...arrs) =>
{ const r = new constructor(arrs.reduce((r, a) => r + a.length, 0))
let i = 0
for (const a of arrs)
for (const val of a)
r[i++] = val
return r
}
const result =
concatMixed
( Int32Array
, Int8Array.of(1, 2, 3)
, Int16Array.of(4, 5, 6)
, Float32Array.of(7.1, 8.2, 9.3)
)
console.log(result.constructor)
console.log(result)
// Int32Array
// { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
This question already has answers here:
Does JavaScript have a method like "range()" to generate a range within the supplied bounds?
(88 answers)
Closed 4 years ago.
Is it any way to create an array a selected size(for example 10) and immediately on the spot fill in it with some numbers specified pattern (1, 2, 3, OR 2, 4, 6 and so on) in one statement in JS?
let arr = new Array(10).fill( (a) => {a = 1; return a++; } );
console.log(arr); // Should be 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
In other words can I pass a some patern or function to method fill insted of a single value.
Actually there's much nicer idiom to create ranges: a spreaded Array constructor:
[...Array(n)]
creates an array of n undefined values. To get 0..n-1, just pick its keys:
[...Array(n).keys()]
for arbitrary ranges, feed it to map:
r = [...Array(10)].map((_, i) => i + 1)
console.log(r)
see here: Does JavaScript have a method like "range()" to generate an array based on supplied bounds?
old answer:
yep, you're looking for Array.from:
r = Array.from({length: 10}, (_, i) => i + 1);
console.log(r)
How does this work? .from inspects its first argument (which can be any object) and picks its .length property. Then, it iterates from 0 to length - 1 and invokes the second argument with two parameters - a value (which is undefined unless the first argument contains a corresponding numeric property) and an index, which takes values from 0...length-1. In this example, we have a function that just returns an index + 1, thus giving us 1..length.
Here's a useful wrapper for the above:
let range = (from, to) => Array.from({length: to - from + 1}, _ => from++);
console.log(range(1, 10))
For ES5, the idiom is Array.apply(null, {length:N}):
r = Array.apply(null, {length: 10}).map(function(_, i) {
return i + 1
})
This question already has answers here:
How to compare arrays in JavaScript?
(55 answers)
Closed 5 years ago.
I have an array of two nested arrays.
var array = [ [a,b,c], [a,b,c] ]
Despite the array elements being identical, the following code returns true:
if (array[0] !== array[1]) {
console.log(array[0])
console.log(array[1])
}
// [a,b,c]
// [a,b,c]
And the following code returns false:
if (array[0] === array[1]) {
console.log(array[0])
console.log(array[1])
}
It seems to be comparing the indices instead of the elements.
What is going on here?
Sometimes I will be comparing 3 or possibly even 4 nested arrays to each other. For instance, if ( array[0] === array[1] || array[0] === array[2] || array[1] === array[2] ) // do this. Notably, a and c will always be references to actual HTML elements, whereas b will be a number. Is there not a simple way to accomplish this nowadays?
You are comparing object references, not object values. The pointers to memory are different, and as a result the comparison is false.
Here is a simple example using html elements in the arrays.
var a1 = document.querySelectorAll('div');
var a2 = document.querySelectorAll('div');
var a3 = document.querySelectorAll('div');
var array = [Array.from(a1),Array.from(a2),Array.from(a3)];
console.log(array[0].every((v,i) => array.slice(1).every(ai => v == ai[i])));
<div>1</div><div>2</div><div>3</div>
This question already has answers here:
JavaScript "new Array(n)" and "Array.prototype.map" weirdness
(14 answers)
Closed 7 years ago.
According to the MDN documentation for new Array(length) I can initialize an array with a given length as such:
var a = new Array(10);
a.length; // => 10
a; // => [undefined x 10]
However, apparently I can't use methods such as map(...) on the new array, even though arrays constructed in other ways work fine:
a.map(function() { return Math.random(); });
// => [undefined x 10] -- wtf?
[undefined, undefined].map(function() { return Math.random(); });
// => [0.07192076672799885, 0.8052175589837134]
Why is this the case?
I understand from this experience (and searching the web) that the array constructor with length is a black hole of unexplained behavior, but does the ECMA 262 specification offer an explanation?
new Array(10) doesn't return an array filled with 10 undefineds. Instead, it returns an array with no elements, and with a length property of 10.
See the difference:
var arr1 = new Array(1),
arr2 = [undefined];
arr1.length === arr2.length; // Both are `1`
arr1[0] === arr2[1]; // Both are `undefined`
arr1.hasOwnProperty(0); // false
arr2.hasOwnProperty(0); // true
Therefore, ECMAScript 5 array methods skip those non-existing properties. Specifically, when they iterate from 0 to length, they check the [[HasProperty]] internal method of the array.
You can fix it easily with ECMAScript 6 Array.prototype.fill (which can be polyfilled):
new Array(10).fill().map(Math.random);
This question already has answers here:
Undefined values in Array(len) initializer
(5 answers)
Closed 7 years ago.
I am confused by the results of mapping over an array created with new:
function returnsFourteen() {
return 14;
}
var a = new Array(4);
> [undefined x 4] in Chrome, [, , , ,] in Firefox
a.map(returnsFourteen);
> [undefined x 4] in Chrome, [, , , ,] in Firefox
var b = [undefined, undefined, undefined, undefined];
> [undefined, undefined, undefined, undefined]
b.map(returnsFourteen);
> [14, 14, 14, 14]
I expected a.map(returnsFourteen) to return [14, 14, 14, 14] (the same as b.map(returnsFourteen), because according to the MDN page on arrays:
If the only argument passed to the Array constructor is an integer
between 0 and 2**32-1 (inclusive), a new JavaScript array is created
with that number of elements.
I interpret that to mean that a should have 4 elements.
What am I missing here?
When you create an array like so:
var arr1 = new Array( 4 );
you get an array that has a length of 4, but that has no elements. That's why map doesn't tranform the array - the array has no elements to be transformed.
On the other hand, if you do:
var arr2 = [ undefined, undefined, undefined, undefined ];
you get and array that also has a length of 4, but that does have 4 elements.
Notice the difference between having no elements, and having elements which values are undefined. Unfortunately, the property accessor expression will evaluate to the undefined value in both cases, so:
arr1[0] // undefined
arr2[0] // undefined
However, there is a way to differentiate these two arrays:
'0' in arr1 // false
'0' in arr2 // true
var a = new Array(4);
This defines a new array object with an explicit length of 4, but no elements.
var b = [undefined, undefined, undefined, undefined];
This defines a new array object with an implicit length of 4, with 4 elements, each with the value undefined.
From the docs:
callback is invoked only for indexes of the array which have assigned
values; it is not invoked for indexes which have been deleted or which
have never been assigned values.
For array a, there are no elements that have been assigned values, so it does nothing.
For array b, there are four elements that have been assigned values (yes, undefined is a value), so it maps all four elements to the number 14.
new Array(len) creates an empty array, and does something different than filling it with undefined values: It sets its length to len. So, it translates to this code:
var newArr = [];
newArr.length = len;
Let's have some fun with newArr (assuming that len = 4):
newArr.length; //4
newArr[1] === undefined; //true
newArr.hasOwnProperty(1); //false
This is because while the is 4 items long, it does not contain any of these 4 items. Imagine an empty bullet-clip: It has space for, say, 20 bullets, but it doesn't contain any of them. They weren't even set to the value undefined, they just are...undefined (which is a bit confusing.)
Now, Array.prototype.map happily walks along your first array, chirping and whistling, and every time it sees an array item, it calls a function on it. But, as it walks along the empty bullet-clip, it sees no bullets. Sure, there are room for bullets, but that doesn't make them exist. In here, there is no value, because the key which maps to that value does not exist.
For the second array, which is filled with undefined values, the value is undefined, and so is the key. There is something inside b[1] or b[3], but that something isn't defined; but Array.prototype.map doesn't care, it'll operate on any value, as long as it has a key.
For further inspection in the spec:
new Array(len) : http://es5.github.com/#x15.4.2.2
Array.prototype.map : http://es5.github.com/#x15.4.4.19 (pay close attention to step 8.b)
One additional answer on the behavior of console.log. Plain simple, the output is sugar and technically wrong.
Lets consider this example:
var foo = new Array(4),
bar = [undefined, undefined, undefined, undefined];
console.log( Object.getOwnPropertyNames(bar) );
console.log( Object.getOwnPropertyNames(foo) );
As we can see in the result, the .getOwnPropertyNames function returns
["length"]
for the foo Array/Object, but
["length", "0", "1", "2", "3"]
for the bar Array/Object.
So, the console.log just fools you on outputting Arrays which just have a defined .length but no real property assignments.