Javascript Array map "appears" to execute callback on missing elements - javascript

Map doesn't get executed on the following array.
Array(100).map(function(e,i){return i+1;});
console.log(Array(100).map(function(e, i) {
return i + 1;
}));
I assume because all elements of the array are 'missing': https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
However, map gets executed on all elements in the following:
Array.apply(null,Array(100)).map(function(e,i){return i+1;});
console.log(Array.apply(null, Array(100)).map(function(e, i) {
return i + 1;
}));
How is it in the second example, the elements of the array change from 'missing' to 'undefined'? (at least I assume that is what is happening.)

In your call
Array.apply(null,Array(100)).map(function(e,i){return i+1;});
the Array function is called with 100 arguments (in fact, the length of Array(100) is 100). But when accessing the arguments, all of them are undefined.
If you would call some arbitrary function func(a, b) like this:
func.apply(null, Array(2))
The parameters a and b will be undefined and the length of arguments will be 2.
map() iterates over the elements in the array but there are actually no elements! However, the array has length 100. This is weird but this is the way arrays behave in JS. If you use the array as an argument list for a function (via .apply()), the arguments are accessed and become undefined. The original array does not change, but accessing an index in the empty array yields undefined.

Let's see how MDN defines "missing":
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).
Internally, this is done with [[HasProperty]]. You can use the in operator to check [[HasProperty]] manually.
And now see the difference:
var arr = Array(100);
'0' in arr; // false
var arr = Array.apply(null, Array(100));
'0' in arr; // true

The main question is "Why Array(100) is an empty array with length equal 100 and Array.apply(null, Array(100)) is an array with 100 empty values?"
To answer that we need to go to the standard. Precisely, Standard ECMA-262, sections 22.1.1.2, 22.1.1.3, 19.2.3.1 and 7.3.17. I'll quote it here:
22.1.1.2 Array (len)
This description applies if and only if the Array constructor is
called with exactly one argument.
1. Let numberOfArgs be the number of arguments passed to this function call.
2. Assert: numberOfArgs = 1.
3. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
4. Let proto be GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%").
5. ReturnIfAbrupt(proto).
6. Let array be ArrayCreate(0, proto).
7. If Type(len) is not Number, then
Let defineStatus be CreateDataProperty(array, "0", len).
Assert: defineStatus is true.
Let intLen be 1.
8. Else,
Let intLen be ToUint32(len).
If intLen ≠ len, throw a RangeError exception.
9. Let setStatus be Set(array, "length", intLen, true).
10. Assert: setStatus is not an abrupt completion.
11. Return array.
22.1.1.3 Array (...items )
This description applies if and only if the Array constructor is
called with at least two arguments.
When the Array function is called the following steps are taken:
1. Let numberOfArgs be the number of arguments passed to this function call.
2. Assert: numberOfArgs ≥ 2.
3. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
4. Let proto be GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%").
5. ReturnIfAbrupt(proto).
6. Let array be ArrayCreate(numberOfArgs, proto).
7. ReturnIfAbrupt(array).
8. Let k be 0.
9. Let items be a zero-origined List containing the argument items in order.
10. Repeat, while k < numberOfArgs
Let Pk be ToString(k).
Let itemK be items[k].
Let defineStatus be CreateDataProperty(array, Pk, itemK).
Assert: defineStatus is true.
Increase k by 1.
11. Assert: the value of array’s length property is numberOfArgs.
12. Return array.
19.2.3.1 Function.prototype.apply ( thisArg, argArray )
When the apply method is called on an object func with arguments
thisArg and argArray, the following steps are taken:
1. If IsCallable(func) is false, throw a TypeError exception.
2. If argArray is null or undefined, then
Return Call(func, thisArg).
3. Let argList be CreateListFromArrayLike(argArray).
4. ReturnIfAbrupt(argList ).
5. Perform PrepareForTailCall().
6. Return Call(func, thisArg, argList).
7.3.17 CreateListFromArrayLike (obj [, elementTypes] )
The abstract operation CreateListFromArrayLike is used to create a
List value whose elements are provided by the indexed properties of an
array-like object, obj. The optional argument elementTypes is a List
containing the names of ECMAScript Language Types that are allowed for
element values of the List that is created. This abstract operation
performs the following steps:
1. ReturnIfAbrupt(obj).
2. If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object).
3. If Type(obj) is not Object, throw a TypeError exception.
4. Let len be ToLength(Get(obj, "length")).
5. ReturnIfAbrupt(len).
6. Let list be an empty List.
7. Let index be 0.
8. Repeat while index < len
Let indexName be ToString(index).
Let next be Get(obj, indexName).
ReturnIfAbrupt(next).
If Type(next) is not an element of elementTypes, throw a TypeError exception.
Append next as the last element of list.
Set index to index + 1.
9. Return list.
As you can see, there is plenty of work going here. In short, when Array(100) executes it produces an array-like object with property length equal to 100, but there is no objects inside that Array. But, if you'll try to get any, it will return undefined for any indexes. Because of that, when CreateListFromArrayLike executes, it gets all 100 undefined values from array-like object provided and returns actual List with 100 undefined values in it, which goes to the Array(...items) function.

Related

Why does for-of not skip empty slots of a sparse Array? [JavaScript]

As the title says, why does for-of run the loop body with the loop variable bound to undefined for indices not in the Array, while other iteration constructs (forEach(), for-in, etc.) don't?
Clarification: As many misunderstood the question
It is not about:
iterating over TypedArrays (which cannot be sparse) or any other class
how to "correctly" iterate over a sparse Array (every other method seems to work in the expected way)
skipping undefined elements in an Array
Is the following informal description found on MDN incorrect?
The for...of statement [...] invokes a custom iteration hook with statements to be executed for the value of each distinct property of the object.
I.e. it is also invoked for non-existent properties.
const sparse = [0, 1, 2] // Was [0, , 2], but some are unfamiliar with this syntax
// or think it creates the array [0, undefined, 2]
delete sparse[1]
for (let e of sparse) console.log('for-of', e)
// Contrast with:
sparse.forEach(e => console.log('forEach', e))
for (let i in sparse) console.log('for-in', sparse[i])
console.log('map', sparse.map(e => e)) // Note, prints incorrectly in the snippet
// console, check browser console
// etc.
Is this the intended behaviour (Yes) & why was it designed in this way?
for..of calls the Array iterator method, which is described in the spec.
(2) Let iterator be ObjectCreate(%ArrayIteratorPrototype%, «‍[[IteratedObject]], [[ArrayIteratorNextIndex]], [[ArrayIterationKind]]»).
(4) Set iterator’s [[ArrayIteratorNextIndex]] internal slot to 0.
Then, when the iterator is iterated over, in 22.1.5.2.1 %ArrayIteratorPrototype%.next::
(6) Let index be the value of the [[ArrayIteratorNextIndex]] internal slot of O.
(10) If index ≥ len, then
(10) (a) Set the value of the [[IteratedObject]] internal slot of O to undefined.
(10) (b) Return CreateIterResultObject(undefined, true).
(11) Set the value of the [[ArrayIteratorNextIndex]] internal slot of O to index+1.
(create iterator result object whose value is array[index])
In other words - the iterator iterates starting at index 0, and increments the index by 1 every time .next() is called. It does not check to see if the array actually has an item at that index (which a sparse array will not) - it just checks that the index is less than the .length of the array.
With for..in, on the other hand, all enumerable properties are iterated over, and an array's own enumerable properties do not include sparse array indicies.
const sparse = [0, , 2];
console.log(sparse.hasOwnProperty('0'));
console.log(sparse.hasOwnProperty('1'));
So yes, this is intended behavior.

What does the following Array initialization syntax actually create? [duplicate]

This question already has answers here:
What does [undefined × 2] mean and why is it different to [undefined,undefined]?
(1 answer)
What is "undefined x 1" in JavaScript?
(5 answers)
forEach on array of undefined created by Array constructor
(5 answers)
Closed 5 years ago.
What does the following actually create?
I ask because it cannot be enumerated with Array#foreach and it is represented (misleadingly?) in Chrome console as [undefined x 10].
const arr = new Array(10);
The Array object has three different possible constructor actions:
new Array()
new Array(<multiple arguments)
new Array(<one argument>) (placed as last because of its nature - see further)
The behavior of the constructor depends of the arguments provided to that constructor. All those three actions uses ArrayCreate during the constructor action. From the link:
The abstract operation ArrayCreate with argument length (a positive integer) and optional argument proto is used to specify the creation of new Array exotic objects.
This function accepts two arguments: len (length) and proto which is used to create an array object. (I won't go deeper here because it would be more complex). The len argument is more important in this situation. When we are looking to the constructor actions individually, you have
new Array()
Here, an object is constructed by ArrayCreate(0, proto), thus an empty array with .length = 0.
new Array(<multiple arguments>)
Here, an object is constructed by ArrayCreate(#of args, proto). If you provide 3 arguments, then you get an array with .length = 3. This array is then populated with the provided arguments. Do you give 5 arguments? You get .length = 5. So the .length property depends of the number of the arguments.
new Array(<one argument>)
This one is quite different. It has two cases. At init, an array object is created with ArrayCreate(0, proto) like if you're using new Array(). Still we have an argument here to use.
If the argument is a non-numeric value, then the effect is same as point 2. This argument is being added to the constructed object (like a .push() operation. So new Array('hello') or new Array('10') gives you an array object with .length = 1
Now, the thing is very different when you give a numeric argument new Array(10), which gives in Chrome [undefined x 10]. Why? Because when the argument is numeric (and valid), this function is being called: Set(O,P,V,Throw). This "magic" function sets the property P of object O with the value V. (The Throw is a flag to indicate " throw error or not? " question).
If the argument is a length, set(<constructed array>, 'length', <argument>, true) is being executed. So the .length property returns the argument value while there is nothing being preserved in the memory. End result: you have an object with a fake .length property... A side-effect of this is that using .push adds an element to the 10th index (if .length was 10). Now you have an array with 11 elements: 10 "empty" slots and an element that you've just pushed.
So what does the browsers do with an array with a "fake" .length property? They cannot iterate it correctly. So they provide a failsafe way: display "has 10 elements but are empty" like chrome does with [undefined x 10] or firefox: Array [ <10 empty slots>]
If you ask me why this is being standardized in the specs, then I would respond with "I don't know". It is one of the weirdest decision (among other ones that I have encountered) that is being standardized in ECMAScript.
The following two snippets are equivalent:
const arr = new Array(10)
and
const arr = [,,,,,,,,,,]
as can be demonstrated here:
const arr1 = new Array(10)
const arr2 = [,,,,,,,,,,]
let empty1 = true
for (let index = 0; empty1 && index < arr1.length; index++) {
if (arr1.hasOwnProperty(index)) {
empty1 = false
}
}
let empty2 = true
for (let index = 0; empty1 && index < arr2.length; index++) {
if (arr2.hasOwnProperty(index)) {
empty2 = false
}
}
console.log(empty1)
console.log(empty2)
This means that for all indices from [0, length - 1], they don't actually exist on the array because arr.hasOwnProperty(index) is false. This is distinguished from this example:
const arr = new Array(10).fill()
demonstrated here:
const arr = new Array(10).fill()
console.log(arr.every((_, index) => arr.hasOwnProperty(index)))
So if you want to initialize each index in a constructed array with undefined, just call .fill() without any arguments.
The Array constructor when supplied with a single integer value creates an Array instance with the length own-property set to that value.
Host environments like browsers can choose to render this as they choose. V8 chooses [undefined x 10] which is misleading because it implies there are ten instances of the predefined undefined value in an ordered sequence.
This is not the case.
Array#foreach, Array#map use the enumerable integer indices AND the length own-property value to enumerate the values of an array.
Because this specific Array constructor syntax does not initialize any integer properties on the Array object, they fail to enumerate Arrays initialized in this manner.
I can only presume that this syntax is soley present to enable code patterns that rely on the length own-property of the Array instance WITHOUT needing any keys or values to be initialized.

Difference between Array(n) and Array(n).fill?

I've noticed that if I do:
Array(n).map(() => console.log('test'))
I get nothing printed.
However if I do:
Array(n).fill().map(() => console.log('test'))
I get test printed out n times.
Why is this the case? If I do Array(n).length I get back n.
I notice in the REPL that Array(5) returns:
[ , , , , ]
Whereas Array(5).fill() returns:
[ undefined, undefined, undefined, undefined, undefined ]
In both cases, typeof any element in the array === undefined.
So, what's going on?
map only operates on defined integer properties of an array. Array(n) does not set integer properties, while Array(n).fill() does. There's a difference between a property that doesn't exist and an extant property whose value is undefined.
Array(n) sets the length property of the array, but it does not set any properties. The array object does not have any integer properties.
.fill sets all of the integer properties for an array from zero up to one less than length. When you do Array(n) you set the length property of the new aray, and then .fill() defines and sets each integer property up to n-1. The array created by Array(n).fill() does have properties defined up to length - 1. (The properties happen to be set to undefined, because you didn't pass an argument to fill, but they do exist.)
In pracitcal terms, you can see the difference if you do Object.keys(Array(4)) (empty array) versus Object.keys(Array(4).fill()) (a list of strings "0" to "3"). In the first case, the properties don't exist; in the second case they do.
Array(n) creates a new array of size n, the contents have not been defined.
Array(n).fill() creates an array of size n where every element is set to whatever you passed into fill or undefined in your case since you passed nothing.
Array(n).fill('test') creates an array of size n filled with 'test'.
I had a requirement to create a scale using a minValue, maxValue, and precision. So, for example, with minValue = 5, maxValue = 7, and precision = 0.5, I should get the array [5, 5.5, 6, 6.5, 7]. I used the following (based off of answers above):
const tickCount = ((maxValue - minValue) / precision) + 1
const ticks = Array(tickCount)
.fill()
.map((x, i) => minValue + i * precision)

What internal method is calling in javascript when I get array element value by index?

I have some wtfjs code:
var a = [,];
alert(a.indexOf(a[0]));
a.indexOf(a[0]) returns -1. The main point in this example is difference between uninitialized and undefined values:
a contains one not initialized element.
a[0] return undefined.
a don't contains the undefined value. So a.indexOf(a[0]) === -1 is true.
But where I can find the explanation why a[0] return undefined? What internal method is calling?
P.S. Undefined is the javascript primitive type. Uninitialized means the value that don't have any javascript type, but there is no such primitive type in javascript.
The ES5 spec tells us the following of array initialisers:
Elided array elements are not defined.
Note that they are not defined. That's different from having the value undefined. As you've already noticed, elided elements do contribute to the length of the array:
...the missing array element contributes to the length of the Array and increases the index of subsequent elements.
When you invoke indexOf on an array this is one of the steps that happens:
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument ToString(k).
In that, k is a number corresponding to an array index and O is the array itself. Since elided elements were not defined the array does not have a property for the corresponding index.
The .indexOf() function only examines elements of the array that have explicitly been set. Therefore, in this case, even though the length of the array is 1 (or 2, depending on the browser involved), there are no explicitly-set elements, so the effective length is zero.
Another way of seeing this effect:
var a = [,];
console.log(a.length); // 1 (in Firefox)
console.log('0' in a); // false
That means that even though the length of the array is 1, there is no element with index 0. Thus, any explicit reference to examine the value of a[0] will have the value undefined.
Now, if we play a little more:
a[0] = undefined;
console.log(a.length); // still 1
console.log('0' in a); // true !!
Once the property reference has appeared on the left side of an assignment, it becomes "real" even if its (now explicit) value is undefined.
As for the "internal methods" involved, you can check the Reference Specification type, and in particular how its "Get" operation works.
This is a tricky one. For instance, if you set a = [,,1] and examine the array, you'll see that only the index of 2 has a value, and it is 1. Index 0 & 1 have no value at all, they were implicitly set to undefined. In other words, they are NOT DEFINED. If you search for a value of undefined in an array, it will always return -1. If you instead set them explicitly to null, you'll see your index come back.
To directly address your question, a.indexOf(a[0]) returns -1 because a[0] is undefined.
All elements of an array which are not defined as a value (including null) are undefined.
When you define an array like this:
var a = [,null,];
Elements a[0] and a[2] are undefined. Any index you use into the array a will return undefined except a[1] which is null. The length property of an array is one higher than the highest non-undefined index. For example:
var b = [];
b[10] = null;
b.length
> 11
However the only index which will not return undefined is b[10].
You can read more about it here:
http://msdn.microsoft.com/en-us/library/d8ez24f2(v=vs.94).aspx
I am not sure what you mean by uninitialized - I think that concept is captured by undefined in JavaScript.

Is it safe to pass 'arguments' to 'apply()'

Suppose I have the following code (completely useless, I know)
function add( a, b, c, d ) {
alert(a+b+c+d);
}
function proxy() {
add.apply(window, arguments);
}
proxy(1,2,3,4);
Basically, we know that apply expects an array as the second parameter, but we also know that arguments is not a proper array. The code works as expected, so is it safe to say that I can pass any array-like object as the second parameter in apply()?
The following will also work (in Chrome at least):
function proxy() {
add.apply(window, {
0: arguments[0],
1: arguments[1],
2: arguments[2],
3: arguments[3],
length: 4
});
}
Update: It seems that my second code block fails in IE<9 while the first one (passing arguments) works.
The error is Array or arguments object expected, so we shall conclude that it's always safe to pass arguments, while it's not safe to pass an array-like object in oldIE.
From definition of Function.prototype.apply in MDN:
fun.apply(thisArg[, argsArray])
You can also use arguments for the argsArray parameter. arguments is a
local variable of a function. It can be used for all unspecified
arguments of the called object. Thus, you do not have to know the
arguments of the called object when you use the apply method. You can
use arguments to pass all the arguments to the called object. The
called object is then responsible for handling the arguments.
REF: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
As the second argument apply accepts "an array like object, specifying the arguments with which function should be called". To my understanding, this array-like object should have the length property for internal iteration, and numerically defined properties (zero-indexed) to access the values.
And the same is confirmed my the spec: http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3, as was kindly pointed out by #Pointy.
Assuming ECMAScript 5.1: Yes. As per ECMA-262, 10.6, the arguments object has the length and index properties that 15.3.4.3 (Function.prototype.apply) requires.
MDN can only speak for Mozilla implementations. The actual spec to which all implementations should comply says the following:
15.3.4.3 Function.prototype.apply (thisArg, argArray)
When the apply method is called on an object func with arguments thisArg and
argArray, the following steps are taken:
1. If IsCallable(func) is false, then throw a TypeError exception.
2. If argArray is null or undefined, then
Return the result of calling the [[Call]] internal method of func,
providing thisArg as the this value and an empty list of arguments.
3. If Type(argArray) is not Object, then throw a TypeError exception.
4. Let len be the result of calling the [[Get]] internal method of argArray
with argument "length".
5. If len is null or undefined, then throw a TypeError exception.
6. Let n be ToUint32(len).
7. If n is not equal to ToNumber(len), then throw a TypeError exception.
8. Let argList be an empty List.
9. Let index be 0.
10. Repeat while index < n
a. Let indexName be ToString(index).
b. Let nextArg be the result of calling the [[Get]] internal method of
argArray with indexName as the argument.
c. Append nextArg as the last element of argList.
d. Set index to index + 1.
11. Return the result of calling the [[Call]] internal method of func,
providing thisArg as the this value and argList as the list of arguments.
The length property of the apply method is 2.
NOTE The thisArg value is passed without modification as the this value. This
is a change from Edition 3, where an undefined or null thisArg is replaced
with the global object and ToObject is applied to all other values and that
result is passed as the this value.
It seems that property length and numeric index values are the only prerequisites.

Categories

Resources