I've observed this in Firefox-3.5.7/Firebug-1.5.3 and Firefox-3.6.16/Firebug-1.6.2
When I fire up Firebug:
var x = new Array(3)
console.log(x)
// [undefined, undefined, undefined]
var y = [undefined, undefined, undefined]
console.log(y)
// [undefined, undefined, undefined]
console.log( x.constructor == y.constructor) // true
console.log(
x.map(function() { return 0; })
)
// [undefined, undefined, undefined]
console.log(
y.map(function() { return 0; })
)
// [0, 0, 0]
What's going on here? Is this a bug, or am I misunderstanding how to use new Array(3)?
I had a task that I only knew the length of the array and needed to transform the items.
I wanted to do something like this:
let arr = new Array(10).map((val,idx) => idx);
To quickly create an array like this:
[0,1,2,3,4,5,6,7,8,9]
But it didn't work because:
(see Jonathan Lonowski's answer)
The solution could be to fill up the array items with any value (even with undefined) using Array.prototype.fill()
let arr = new Array(10).fill(undefined).map((val,idx) => idx);
console.log(new Array(10).fill(undefined).map((val, idx) => idx));
Update
Another solution could be:
let arr = Array.apply(null, Array(10)).map((val, idx) => idx);
console.log(Array.apply(null, Array(10)).map((val, idx) => idx));
It appears that the first example
x = new Array(3);
Creates an array with a length of 3 but without any elements, so the indices [0], [1] and [2] is not created.
And the second creates an array with the 3 undefined objects, in this case the indices/properties them self are created but the objects they refer to are undefined.
y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];
As map runs on the list of indices/properties, not on the set length, so if no indices/properties is created, it will not run.
With ES6, you can do [...Array(10)].map((a, b) => a) , quick and easy!
From the MDC page for map:
[...] callback is invoked only for indexes of the array which have assigned value; [...]
[undefined] actually applies the setter on the index(es) so that map will iterate, whereas new Array(1) just initializes the index(es) with a default value of undefined so map skips it.
I believe this is the same for all iteration methods.
ES6 solution:
[...Array(10)]
Doesn't work on typescript (2.3), though
The arrays are different. The difference is that new Array(3) creates an array with a length of three but no properties, while [undefined, undefined, undefined] creates an array with a length of three and three properties called "0", "1" and "2", each with a value of undefined. You can see the difference using the in operator:
"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true
This stems from the slightly confusing fact that if you try to get the value of a non-existent property of any native object in JavaScript, it returns undefined (rather than throwing an error, as happens when you try to refer to a non-existent variable), which is the same as what you get if the property has previously been explictly set to undefined.
For reasons thoroughly explained in other answers, Array(n).map doesn't work. However, in ES2015 Array.from accepts a map function:
let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5
let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10
In ECMAScript 6th edition specification.
new Array(3) only define property length and do not define index properties like {length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Step 9.
[undefined, undefined, undefined] will define index properties and length property like {0: undefined, 1: undefined, 2: undefined, length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Step 5.
methods map, every, some, forEach, slice, reduce, reduceRight, filter of Array will check the index property by HasProperty internal method, so new Array(3).map(v => 1) will not invoke the callback.
for more detail, see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map
How to fix?
let a = new Array(3);
a.join('.').split('.').map(v => 1);
let a = new Array(3);
a.fill(1);
let a = new Array(3);
a.fill(undefined).map(v => 1);
let a = new Array(3);
[...a].map(v => 1);
I think the best way to explain this is to look at the way that Chrome handles it.
>>> x = new Array(3)
[]
>>> x.length
3
So what is actually happening is that new Array() is returning an empty array that has a length of 3, but no values. Therefore, when you run x.map on a technically empty array, there is nothing to be set.
Firefox just 'fills in' those empty slots with undefined even though it has no values.
I don't think this is explicitly a bug, just a poor way of representing what is going on. I suppose Chrome's is "more correct" because it shows that there isn't actually anything in the array.
Not a bug. That's how the Array constructor is defined to work.
From MDC:
When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array. The following code creates an array of five elements:
var billingMethod = new Array(5);
The behavior of the Array constructor depends on whether the single parameter is a number.
The .map() method only includes in the iteration elements of the array that have explicitly had values assigned. Even an explicit assignment of undefined will cause a value to be considered eligible for inclusion in the iteration. That seems odd, but it's essentially the difference between an explicit undefined property on an object and a missing property:
var x = { }, y = { z: undefined };
if (x.z === y.z) // true
The object x does not have a property called "z", and the object y does. However, in both cases it appears that the "value" of the property is undefined. In an array, the situation is similar: the value of length does implicitly perform a value assignment to all the elements from zero through length - 1. The .map() function therefore won't do anything (won't call the callback) when called on an array newly constructed with the Array constructor and a numeric argument.
Just ran into this. It sure would be convenient to be able to use Array(n).map.
Array(3) yields roughly {length: 3}
[undefined, undefined, undefined] creates the numbered properties:
{0: undefined, 1: undefined, 2: undefined, length: 3}.
The map() implementation only acts on defined properties.
If you are doing this in order to easily fill up an array with values, can't use fill for browser support reasons and really don't want to do a for-loop, you can also do x = new Array(3).join(".").split(".").map(... which will give you an array of empty strings.
Quite ugly I have to say, but at least the problem and intention are quite clearly communicated.
Since the question is why, this has to do with how JS was designed.
There are 2 main reasons I can think of to explain this behavior:
Performance: Given x = 10000 and new Array(x) it is wise for the constructor to avoid looping from 0 to 10000 to fill the array with undefined values.
Implicitly "undefined": Give a = [undefined, undefined] and b = new Array(2), a[1] and b[1] will both return undefined, but a[8] and b[8] will also return undefined even if they're out of range.
Ultimately, the notation empty x 3 is a shortcut to avoid setting and displaying a long list of undefined values that are undefined anyway because they are not declared explicitly.
Note: Given array a = [0] and a[9] = 9, console.log(a) will return (10) [0, empty x 8, 9], filling the gap automatically by returning the difference between the two values declared explicitly.
Here's a simple utility method as a workaround:
Simple mapFor
function mapFor(toExclusive, callback) {
callback = callback || function(){};
var arr = [];
for (var i = 0; i < toExclusive; i++) {
arr.push(callback(i));
}
return arr;
};
var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Complete Example
Here's a more complete example (with sanity checks) which also allows specifying an optional starting index:
function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
from = arguments[0];
toExclusive = arguments[1];
callback = arguments[2];
} else if (arguments.length == 2) {
if (typeof arguments[1] === 'function') {
from = 0;
toExclusive = arguments[0];
callback = arguments[1];
} else {
from = arguments[0];
toExclusive = arguments[1];
}
} else if (arguments.length == 1) {
from = 0;
toExclusive = arguments[0];
}
callback = callback || function () {};
var arr = [];
for (; from < toExclusive; from++) {
arr.push(callback(from));
}
return arr;
}
var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Counting Down
Manipulating the index passed to the callback allows counting backwards:
var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
return count - 1 - i;
});
// arr = [2, 1, 0]
Related
I'm creating a clone array from an array that contain some empty slots. But after cloning it is being replaced with undefined. If the source array contain some empty slots then clone array should also contain same number and at exact same position empty slots. I don't get the reason. I'm using spread syntax to clone array as:
const arr = [1, "", , null, undefined, false, , 0];
console.log('arr => ', arr);
const clone = [...arr];
console.log('clone => ', clone)
Output is as below in chrome console
Using spread syntax will invoke the object's iterator if it has one. The array iterator will:
a. Let index be 0.
b. Repeat
Let len be ? LengthOfArrayLike(array).
iii. If index ≥ len, return NormalCompletion(undefined).
(...)
1. Let elementKey be ! ToString(𝔽(index)).
2. Let elementValue be ? Get(array, elementKey).
(yield elementValue)
vi. Set index to index + 1.
And the length of a sparse array is still the index of the last element plus one:
const arr = [];
arr[5] = 'a';
console.log(arr.length);
So, even with sparse arrays, spreading them will result in the new array containing values of:
arr[0]
arr[1]
arr[2]
// ...
arr[arr.length - 1]
even when the original array has empty slots in between 0 and arr.length - 1.
If you want empty slots, spreading will only work if you delete the undesirable indices afterwards - or iterate over the array manually, only assigning indices you need.
const arr = [1, "", , null, undefined, false, , 0];
console.log('arr => ', arr);
const clone = [];
for (let i = 0; i < arr.length; i++) {
if (arr.hasOwnProperty(i)) {
clone[i] = arr[i];
}
}
console.log('clone => ', clone)
But you could also consider restructuring your code to avoid sparse arrays entirely - they're not very intuitive.
Let's take a step back
let x;
console.log(x); // undefined
console.log(typeof x); // undefined
If you don't define a variable, it is un-defined.
Let's see now an empty array:
let x = [,]; // even [] would work but I thought this one is clearer for some
console.log(x[0]); // undefined
console.log(typeof x[0]); //undefined
Why is that? Simply because
If you don't define a variable, it is un-defined.
above answers already made it pretty clear why you getting undefiend.
Just to add more If you log arr[2] you will get undefined, i haven't read it anywhere but from what i know spread operator spread the values of array/obj that is why arr[2] value is undefiend
I've observed this in Firefox-3.5.7/Firebug-1.5.3 and Firefox-3.6.16/Firebug-1.6.2
When I fire up Firebug:
var x = new Array(3)
console.log(x)
// [undefined, undefined, undefined]
var y = [undefined, undefined, undefined]
console.log(y)
// [undefined, undefined, undefined]
console.log( x.constructor == y.constructor) // true
console.log(
x.map(function() { return 0; })
)
// [undefined, undefined, undefined]
console.log(
y.map(function() { return 0; })
)
// [0, 0, 0]
What's going on here? Is this a bug, or am I misunderstanding how to use new Array(3)?
I had a task that I only knew the length of the array and needed to transform the items.
I wanted to do something like this:
let arr = new Array(10).map((val,idx) => idx);
To quickly create an array like this:
[0,1,2,3,4,5,6,7,8,9]
But it didn't work because:
(see Jonathan Lonowski's answer)
The solution could be to fill up the array items with any value (even with undefined) using Array.prototype.fill()
let arr = new Array(10).fill(undefined).map((val,idx) => idx);
console.log(new Array(10).fill(undefined).map((val, idx) => idx));
Update
Another solution could be:
let arr = Array.apply(null, Array(10)).map((val, idx) => idx);
console.log(Array.apply(null, Array(10)).map((val, idx) => idx));
It appears that the first example
x = new Array(3);
Creates an array with a length of 3 but without any elements, so the indices [0], [1] and [2] is not created.
And the second creates an array with the 3 undefined objects, in this case the indices/properties them self are created but the objects they refer to are undefined.
y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];
As map runs on the list of indices/properties, not on the set length, so if no indices/properties is created, it will not run.
With ES6, you can do [...Array(10)].map((a, b) => a) , quick and easy!
From the MDC page for map:
[...] callback is invoked only for indexes of the array which have assigned value; [...]
[undefined] actually applies the setter on the index(es) so that map will iterate, whereas new Array(1) just initializes the index(es) with a default value of undefined so map skips it.
I believe this is the same for all iteration methods.
ES6 solution:
[...Array(10)]
Doesn't work on typescript (2.3), though
The arrays are different. The difference is that new Array(3) creates an array with a length of three but no properties, while [undefined, undefined, undefined] creates an array with a length of three and three properties called "0", "1" and "2", each with a value of undefined. You can see the difference using the in operator:
"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true
This stems from the slightly confusing fact that if you try to get the value of a non-existent property of any native object in JavaScript, it returns undefined (rather than throwing an error, as happens when you try to refer to a non-existent variable), which is the same as what you get if the property has previously been explictly set to undefined.
For reasons thoroughly explained in other answers, Array(n).map doesn't work. However, in ES2015 Array.from accepts a map function:
let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5
let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10
In ECMAScript 6th edition specification.
new Array(3) only define property length and do not define index properties like {length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Step 9.
[undefined, undefined, undefined] will define index properties and length property like {0: undefined, 1: undefined, 2: undefined, length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Step 5.
methods map, every, some, forEach, slice, reduce, reduceRight, filter of Array will check the index property by HasProperty internal method, so new Array(3).map(v => 1) will not invoke the callback.
for more detail, see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map
How to fix?
let a = new Array(3);
a.join('.').split('.').map(v => 1);
let a = new Array(3);
a.fill(1);
let a = new Array(3);
a.fill(undefined).map(v => 1);
let a = new Array(3);
[...a].map(v => 1);
I think the best way to explain this is to look at the way that Chrome handles it.
>>> x = new Array(3)
[]
>>> x.length
3
So what is actually happening is that new Array() is returning an empty array that has a length of 3, but no values. Therefore, when you run x.map on a technically empty array, there is nothing to be set.
Firefox just 'fills in' those empty slots with undefined even though it has no values.
I don't think this is explicitly a bug, just a poor way of representing what is going on. I suppose Chrome's is "more correct" because it shows that there isn't actually anything in the array.
Not a bug. That's how the Array constructor is defined to work.
From MDC:
When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array. The following code creates an array of five elements:
var billingMethod = new Array(5);
The behavior of the Array constructor depends on whether the single parameter is a number.
The .map() method only includes in the iteration elements of the array that have explicitly had values assigned. Even an explicit assignment of undefined will cause a value to be considered eligible for inclusion in the iteration. That seems odd, but it's essentially the difference between an explicit undefined property on an object and a missing property:
var x = { }, y = { z: undefined };
if (x.z === y.z) // true
The object x does not have a property called "z", and the object y does. However, in both cases it appears that the "value" of the property is undefined. In an array, the situation is similar: the value of length does implicitly perform a value assignment to all the elements from zero through length - 1. The .map() function therefore won't do anything (won't call the callback) when called on an array newly constructed with the Array constructor and a numeric argument.
Just ran into this. It sure would be convenient to be able to use Array(n).map.
Array(3) yields roughly {length: 3}
[undefined, undefined, undefined] creates the numbered properties:
{0: undefined, 1: undefined, 2: undefined, length: 3}.
The map() implementation only acts on defined properties.
If you are doing this in order to easily fill up an array with values, can't use fill for browser support reasons and really don't want to do a for-loop, you can also do x = new Array(3).join(".").split(".").map(... which will give you an array of empty strings.
Quite ugly I have to say, but at least the problem and intention are quite clearly communicated.
Since the question is why, this has to do with how JS was designed.
There are 2 main reasons I can think of to explain this behavior:
Performance: Given x = 10000 and new Array(x) it is wise for the constructor to avoid looping from 0 to 10000 to fill the array with undefined values.
Implicitly "undefined": Give a = [undefined, undefined] and b = new Array(2), a[1] and b[1] will both return undefined, but a[8] and b[8] will also return undefined even if they're out of range.
Ultimately, the notation empty x 3 is a shortcut to avoid setting and displaying a long list of undefined values that are undefined anyway because they are not declared explicitly.
Note: Given array a = [0] and a[9] = 9, console.log(a) will return (10) [0, empty x 8, 9], filling the gap automatically by returning the difference between the two values declared explicitly.
Here's a simple utility method as a workaround:
Simple mapFor
function mapFor(toExclusive, callback) {
callback = callback || function(){};
var arr = [];
for (var i = 0; i < toExclusive; i++) {
arr.push(callback(i));
}
return arr;
};
var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Complete Example
Here's a more complete example (with sanity checks) which also allows specifying an optional starting index:
function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
from = arguments[0];
toExclusive = arguments[1];
callback = arguments[2];
} else if (arguments.length == 2) {
if (typeof arguments[1] === 'function') {
from = 0;
toExclusive = arguments[0];
callback = arguments[1];
} else {
from = arguments[0];
toExclusive = arguments[1];
}
} else if (arguments.length == 1) {
from = 0;
toExclusive = arguments[0];
}
callback = callback || function () {};
var arr = [];
for (; from < toExclusive; from++) {
arr.push(callback(from));
}
return arr;
}
var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Counting Down
Manipulating the index passed to the callback allows counting backwards:
var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
return count - 1 - i;
});
// arr = [2, 1, 0]
I've observed this in Firefox-3.5.7/Firebug-1.5.3 and Firefox-3.6.16/Firebug-1.6.2
When I fire up Firebug:
var x = new Array(3)
console.log(x)
// [undefined, undefined, undefined]
var y = [undefined, undefined, undefined]
console.log(y)
// [undefined, undefined, undefined]
console.log( x.constructor == y.constructor) // true
console.log(
x.map(function() { return 0; })
)
// [undefined, undefined, undefined]
console.log(
y.map(function() { return 0; })
)
// [0, 0, 0]
What's going on here? Is this a bug, or am I misunderstanding how to use new Array(3)?
I had a task that I only knew the length of the array and needed to transform the items.
I wanted to do something like this:
let arr = new Array(10).map((val,idx) => idx);
To quickly create an array like this:
[0,1,2,3,4,5,6,7,8,9]
But it didn't work because:
(see Jonathan Lonowski's answer)
The solution could be to fill up the array items with any value (even with undefined) using Array.prototype.fill()
let arr = new Array(10).fill(undefined).map((val,idx) => idx);
console.log(new Array(10).fill(undefined).map((val, idx) => idx));
Update
Another solution could be:
let arr = Array.apply(null, Array(10)).map((val, idx) => idx);
console.log(Array.apply(null, Array(10)).map((val, idx) => idx));
It appears that the first example
x = new Array(3);
Creates an array with a length of 3 but without any elements, so the indices [0], [1] and [2] is not created.
And the second creates an array with the 3 undefined objects, in this case the indices/properties them self are created but the objects they refer to are undefined.
y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];
As map runs on the list of indices/properties, not on the set length, so if no indices/properties is created, it will not run.
With ES6, you can do [...Array(10)].map((a, b) => a) , quick and easy!
From the MDC page for map:
[...] callback is invoked only for indexes of the array which have assigned value; [...]
[undefined] actually applies the setter on the index(es) so that map will iterate, whereas new Array(1) just initializes the index(es) with a default value of undefined so map skips it.
I believe this is the same for all iteration methods.
ES6 solution:
[...Array(10)]
Doesn't work on typescript (2.3), though
The arrays are different. The difference is that new Array(3) creates an array with a length of three but no properties, while [undefined, undefined, undefined] creates an array with a length of three and three properties called "0", "1" and "2", each with a value of undefined. You can see the difference using the in operator:
"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true
This stems from the slightly confusing fact that if you try to get the value of a non-existent property of any native object in JavaScript, it returns undefined (rather than throwing an error, as happens when you try to refer to a non-existent variable), which is the same as what you get if the property has previously been explictly set to undefined.
For reasons thoroughly explained in other answers, Array(n).map doesn't work. However, in ES2015 Array.from accepts a map function:
let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5
let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10
In ECMAScript 6th edition specification.
new Array(3) only define property length and do not define index properties like {length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Step 9.
[undefined, undefined, undefined] will define index properties and length property like {0: undefined, 1: undefined, 2: undefined, length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Step 5.
methods map, every, some, forEach, slice, reduce, reduceRight, filter of Array will check the index property by HasProperty internal method, so new Array(3).map(v => 1) will not invoke the callback.
for more detail, see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map
How to fix?
let a = new Array(3);
a.join('.').split('.').map(v => 1);
let a = new Array(3);
a.fill(1);
let a = new Array(3);
a.fill(undefined).map(v => 1);
let a = new Array(3);
[...a].map(v => 1);
I think the best way to explain this is to look at the way that Chrome handles it.
>>> x = new Array(3)
[]
>>> x.length
3
So what is actually happening is that new Array() is returning an empty array that has a length of 3, but no values. Therefore, when you run x.map on a technically empty array, there is nothing to be set.
Firefox just 'fills in' those empty slots with undefined even though it has no values.
I don't think this is explicitly a bug, just a poor way of representing what is going on. I suppose Chrome's is "more correct" because it shows that there isn't actually anything in the array.
Not a bug. That's how the Array constructor is defined to work.
From MDC:
When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array. The following code creates an array of five elements:
var billingMethod = new Array(5);
The behavior of the Array constructor depends on whether the single parameter is a number.
The .map() method only includes in the iteration elements of the array that have explicitly had values assigned. Even an explicit assignment of undefined will cause a value to be considered eligible for inclusion in the iteration. That seems odd, but it's essentially the difference between an explicit undefined property on an object and a missing property:
var x = { }, y = { z: undefined };
if (x.z === y.z) // true
The object x does not have a property called "z", and the object y does. However, in both cases it appears that the "value" of the property is undefined. In an array, the situation is similar: the value of length does implicitly perform a value assignment to all the elements from zero through length - 1. The .map() function therefore won't do anything (won't call the callback) when called on an array newly constructed with the Array constructor and a numeric argument.
Just ran into this. It sure would be convenient to be able to use Array(n).map.
Array(3) yields roughly {length: 3}
[undefined, undefined, undefined] creates the numbered properties:
{0: undefined, 1: undefined, 2: undefined, length: 3}.
The map() implementation only acts on defined properties.
If you are doing this in order to easily fill up an array with values, can't use fill for browser support reasons and really don't want to do a for-loop, you can also do x = new Array(3).join(".").split(".").map(... which will give you an array of empty strings.
Quite ugly I have to say, but at least the problem and intention are quite clearly communicated.
Since the question is why, this has to do with how JS was designed.
There are 2 main reasons I can think of to explain this behavior:
Performance: Given x = 10000 and new Array(x) it is wise for the constructor to avoid looping from 0 to 10000 to fill the array with undefined values.
Implicitly "undefined": Give a = [undefined, undefined] and b = new Array(2), a[1] and b[1] will both return undefined, but a[8] and b[8] will also return undefined even if they're out of range.
Ultimately, the notation empty x 3 is a shortcut to avoid setting and displaying a long list of undefined values that are undefined anyway because they are not declared explicitly.
Note: Given array a = [0] and a[9] = 9, console.log(a) will return (10) [0, empty x 8, 9], filling the gap automatically by returning the difference between the two values declared explicitly.
Here's a simple utility method as a workaround:
Simple mapFor
function mapFor(toExclusive, callback) {
callback = callback || function(){};
var arr = [];
for (var i = 0; i < toExclusive; i++) {
arr.push(callback(i));
}
return arr;
};
var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Complete Example
Here's a more complete example (with sanity checks) which also allows specifying an optional starting index:
function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
from = arguments[0];
toExclusive = arguments[1];
callback = arguments[2];
} else if (arguments.length == 2) {
if (typeof arguments[1] === 'function') {
from = 0;
toExclusive = arguments[0];
callback = arguments[1];
} else {
from = arguments[0];
toExclusive = arguments[1];
}
} else if (arguments.length == 1) {
from = 0;
toExclusive = arguments[0];
}
callback = callback || function () {};
var arr = [];
for (; from < toExclusive; from++) {
arr.push(callback(from));
}
return arr;
}
var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Counting Down
Manipulating the index passed to the callback allows counting backwards:
var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
return count - 1 - i;
});
// arr = [2, 1, 0]
I've observed this in Firefox-3.5.7/Firebug-1.5.3 and Firefox-3.6.16/Firebug-1.6.2
When I fire up Firebug:
var x = new Array(3)
console.log(x)
// [undefined, undefined, undefined]
var y = [undefined, undefined, undefined]
console.log(y)
// [undefined, undefined, undefined]
console.log( x.constructor == y.constructor) // true
console.log(
x.map(function() { return 0; })
)
// [undefined, undefined, undefined]
console.log(
y.map(function() { return 0; })
)
// [0, 0, 0]
What's going on here? Is this a bug, or am I misunderstanding how to use new Array(3)?
I had a task that I only knew the length of the array and needed to transform the items.
I wanted to do something like this:
let arr = new Array(10).map((val,idx) => idx);
To quickly create an array like this:
[0,1,2,3,4,5,6,7,8,9]
But it didn't work because:
(see Jonathan Lonowski's answer)
The solution could be to fill up the array items with any value (even with undefined) using Array.prototype.fill()
let arr = new Array(10).fill(undefined).map((val,idx) => idx);
console.log(new Array(10).fill(undefined).map((val, idx) => idx));
Update
Another solution could be:
let arr = Array.apply(null, Array(10)).map((val, idx) => idx);
console.log(Array.apply(null, Array(10)).map((val, idx) => idx));
It appears that the first example
x = new Array(3);
Creates an array with a length of 3 but without any elements, so the indices [0], [1] and [2] is not created.
And the second creates an array with the 3 undefined objects, in this case the indices/properties them self are created but the objects they refer to are undefined.
y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];
As map runs on the list of indices/properties, not on the set length, so if no indices/properties is created, it will not run.
With ES6, you can do [...Array(10)].map((a, b) => a) , quick and easy!
From the MDC page for map:
[...] callback is invoked only for indexes of the array which have assigned value; [...]
[undefined] actually applies the setter on the index(es) so that map will iterate, whereas new Array(1) just initializes the index(es) with a default value of undefined so map skips it.
I believe this is the same for all iteration methods.
ES6 solution:
[...Array(10)]
Doesn't work on typescript (2.3), though
The arrays are different. The difference is that new Array(3) creates an array with a length of three but no properties, while [undefined, undefined, undefined] creates an array with a length of three and three properties called "0", "1" and "2", each with a value of undefined. You can see the difference using the in operator:
"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true
This stems from the slightly confusing fact that if you try to get the value of a non-existent property of any native object in JavaScript, it returns undefined (rather than throwing an error, as happens when you try to refer to a non-existent variable), which is the same as what you get if the property has previously been explictly set to undefined.
For reasons thoroughly explained in other answers, Array(n).map doesn't work. However, in ES2015 Array.from accepts a map function:
let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5
let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10
In ECMAScript 6th edition specification.
new Array(3) only define property length and do not define index properties like {length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Step 9.
[undefined, undefined, undefined] will define index properties and length property like {0: undefined, 1: undefined, 2: undefined, length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Step 5.
methods map, every, some, forEach, slice, reduce, reduceRight, filter of Array will check the index property by HasProperty internal method, so new Array(3).map(v => 1) will not invoke the callback.
for more detail, see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map
How to fix?
let a = new Array(3);
a.join('.').split('.').map(v => 1);
let a = new Array(3);
a.fill(1);
let a = new Array(3);
a.fill(undefined).map(v => 1);
let a = new Array(3);
[...a].map(v => 1);
I think the best way to explain this is to look at the way that Chrome handles it.
>>> x = new Array(3)
[]
>>> x.length
3
So what is actually happening is that new Array() is returning an empty array that has a length of 3, but no values. Therefore, when you run x.map on a technically empty array, there is nothing to be set.
Firefox just 'fills in' those empty slots with undefined even though it has no values.
I don't think this is explicitly a bug, just a poor way of representing what is going on. I suppose Chrome's is "more correct" because it shows that there isn't actually anything in the array.
Not a bug. That's how the Array constructor is defined to work.
From MDC:
When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array. The following code creates an array of five elements:
var billingMethod = new Array(5);
The behavior of the Array constructor depends on whether the single parameter is a number.
The .map() method only includes in the iteration elements of the array that have explicitly had values assigned. Even an explicit assignment of undefined will cause a value to be considered eligible for inclusion in the iteration. That seems odd, but it's essentially the difference between an explicit undefined property on an object and a missing property:
var x = { }, y = { z: undefined };
if (x.z === y.z) // true
The object x does not have a property called "z", and the object y does. However, in both cases it appears that the "value" of the property is undefined. In an array, the situation is similar: the value of length does implicitly perform a value assignment to all the elements from zero through length - 1. The .map() function therefore won't do anything (won't call the callback) when called on an array newly constructed with the Array constructor and a numeric argument.
Just ran into this. It sure would be convenient to be able to use Array(n).map.
Array(3) yields roughly {length: 3}
[undefined, undefined, undefined] creates the numbered properties:
{0: undefined, 1: undefined, 2: undefined, length: 3}.
The map() implementation only acts on defined properties.
If you are doing this in order to easily fill up an array with values, can't use fill for browser support reasons and really don't want to do a for-loop, you can also do x = new Array(3).join(".").split(".").map(... which will give you an array of empty strings.
Quite ugly I have to say, but at least the problem and intention are quite clearly communicated.
Since the question is why, this has to do with how JS was designed.
There are 2 main reasons I can think of to explain this behavior:
Performance: Given x = 10000 and new Array(x) it is wise for the constructor to avoid looping from 0 to 10000 to fill the array with undefined values.
Implicitly "undefined": Give a = [undefined, undefined] and b = new Array(2), a[1] and b[1] will both return undefined, but a[8] and b[8] will also return undefined even if they're out of range.
Ultimately, the notation empty x 3 is a shortcut to avoid setting and displaying a long list of undefined values that are undefined anyway because they are not declared explicitly.
Note: Given array a = [0] and a[9] = 9, console.log(a) will return (10) [0, empty x 8, 9], filling the gap automatically by returning the difference between the two values declared explicitly.
Here's a simple utility method as a workaround:
Simple mapFor
function mapFor(toExclusive, callback) {
callback = callback || function(){};
var arr = [];
for (var i = 0; i < toExclusive; i++) {
arr.push(callback(i));
}
return arr;
};
var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Complete Example
Here's a more complete example (with sanity checks) which also allows specifying an optional starting index:
function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
from = arguments[0];
toExclusive = arguments[1];
callback = arguments[2];
} else if (arguments.length == 2) {
if (typeof arguments[1] === 'function') {
from = 0;
toExclusive = arguments[0];
callback = arguments[1];
} else {
from = arguments[0];
toExclusive = arguments[1];
}
} else if (arguments.length == 1) {
from = 0;
toExclusive = arguments[0];
}
callback = callback || function () {};
var arr = [];
for (; from < toExclusive; from++) {
arr.push(callback(from));
}
return arr;
}
var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Counting Down
Manipulating the index passed to the callback allows counting backwards:
var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
return count - 1 - i;
});
// arr = [2, 1, 0]
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.