Consider:
var a = Array(3);
var b = [undefined,undefined,undefined];
What's the reason that a.map and b.map produce different results?
a.map(function(){ return 0; }); //produces -> [undefined,undefined,undefined]
b.map(function(){ return 0; }); //produces -> [0,0,0]
The array constructor creates an array with the given length. It does not create the keys. Array.prototype.map's callback function is only executed for the elements in the list.
That is, all values which are associated with a key (integer) 0 ≤ i < length.
Array(3) has zero keys, so .map's callback is never triggered.
[void 0, void 0, void 0] has three keys, for which the callback function is executed.
Array(3).hasOwnProperty(0); // false
[void 0, void 0, void 0].hasOwnProperty(0); // true
The specification and its polyfill are mentioned at MDN. At line 47, if (k in O) { shows that non-existant keys are not treated by the callback function.
From MDN:
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 the array a, you've instantiated an array of length 3 but have not assigned any values. The map function finds no elements with assigned values, so it does not produce a new array.
For the array b, you've instantiated an array of 3 elements, each with the value undefined. The map function finds 3 elements with assigned values, and returns '0' as the new value for each of them in a new array.
map only iterates existing properties, not empty indices.
Therefore, if you want it to work, you must first fill the array.
There are multiple ways to do that, for example:
.fill(), introduced in ES6
console.log(new Array(3).fill().map(function(){ return 0; }));
Call concat with apply:
var arr = [].concat.apply([], new Array(3));
console.log(arr.map(function(){ return 0; }));
An old for loop.
var arr = new Array(3);
for(var i=0; i<arr.length; ++i) arr[i] = 1; /* whatever */
console.log(arr.map(function(){ return 0; }));
Use some idea from Most efficient way to create a zero filled JavaScript array?
Etcetera.
a is an empty array that doesn't have elements, so map function produces empty array without elements (per specification, map produces results only if [[HasProperty]] is true.) b is an array of three elements, so map produces an array of three elements.
Constructed arrays are enumerable but empty
Array(len) creates an array and sets its length accordingly but only its length is "enumerable", not the values contained. So, you cannot map the array Array(100).map(/* nope */)— it's not a "real array" yet and it is actually empty despite having the correct length.
callback is invoked only for indexes of the array which have assigned values including undefined.,
The array does not contain any values; not even undefined
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).
To populate array you need to iterate it someway… E.g.: [...Array(100)] or Array.from(Array(100))
I imagine the purpose of this initialization is to optimize memory allocation… there isn't really anything in the array. MDN says "empty arrayLength objects" which might be misleading as trying to access any of the "empty items" just returns undefined… But we know they aren't really undefined since map fails, therefore we can confirm it is a truly empty array.
Constructor equivalent
This example does not seek to mirror specification but instead to illustrate why an array returned from Array cannot yet be iterated
function * (length) {
const arr = [];
Object.defineProperty(arr, 'length', { value: length });
// Equivalent, but invokes assignment trap and mutates array:
// arr.length = length;
Object.defineProperty(arr, Symbol.iterator, {
value() {
let i = 0;
return {
next() {
return {
value: undefined, // (Not omitted for illustration)
done: i++ == length
};
}
}
}
})
return arr;
}
It is worth pointing out that despite providing a undefined value in the generator value property, it does not recognize it as a value and so the array is empty.
Array(len) Specification
https://www.ecma-international.org/ecma-262/6.0/#sec-array-len
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
a) Let defineStatus be CreateDataProperty(array, "0", len).
b) Assert: defineStatus is true.
c) Let intLen be 1.
8) Else, a) Let intLen be ToUint32(len). b) 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.
Related
I'm following an online course about Javascript Functional Programming
at the Exercise 16 it show you how reduce is actually implemented, in order to help you understand how to use it, but into this implementation there is something i don't actually get, i'll show the code:
Array.prototype.reduce = function(combiner, initialValue) {
var counter, accumulatedValue;
// If the array is empty, do nothing
if (this.length === 0) {
return this;
}
else {
// If the user didn't pass an initial value, use the first item.
if (arguments.length === 1) {
counter = 1;
accumulatedValue = this[0];
}
else if (arguments.length >= 2) {
counter = 0;
accumulatedValue = initialValue;
}
else {
throw "Invalid arguments.";
}
// Loop through the array, feeding the current value and the result of
// the previous computation back into the combiner function until
// we've exhausted the entire array and are left with only one value.
while(counter < this.length) {
accumulatedValue = combiner(accumulatedValue, this[counter])
counter++;
}
return [accumulatedValue];
}
};
I don't understand the first if statement, when it check for this.length what this actually mean?
Take note this is different from the reduce in ES5, which returns an value instead of an Array, this is used just as a sample for the learning purpose.
Array.prototype.reduce = function(...
is saying, "create a function on the prototype of Array" - this means that the new reduce function will be callable on all arrays, eg:
[1, 2, 3].reduce(...
This means you can also call it on empty arrays, eg:
[].reduce(...
Building on the comment:
If the array is empty, do nothing
You're working on an array, and when the function is called, this is set to the array that reduce was called on. This implementation of reduce assumes that if that array is empty (ie this.length === 0), you can't logically reduce it any further - there's nothing to reduce, so you can return the same empty array.
As pointed out by #Alnitak in the comments, this implementation of reduce is flawed as compared to the specification. A different implementation is available on the MDN for polyfilling older browsers.
What is different between two variable one is assigned a value undefined and second one is only declared a var not initiased ?
var a = undefined;
var b;
a === b; // returns true
What is different between two variable a and b?
var ar = new Array(4); // ar = 0[undefined × 4]
ar[0] = null; // ar = [null, undefined × 3]
ar[1] = undefined; // ar = [null, undefined, undefined × 2]
ar.filter(function(item, index, arr){
return item == undefined;
}); // [null, undefined]
I know Array's filter function runs for only initialized index.
How javascript internally check that ar[1] is assigend undefined so run filter for this and ar[2] is unassigned undefined so dont run for this ?
There are three separate issues going on here:
A regular variable that has just been declared, but not assigned a value will report undefined when you read that variable. That is essentially its default value.
You have to be very careful when comparing null with == because you will find that null == undefined is true because of auto-type conversion. So, if you want to filter on only undefined elements, you have to use item === undefined in the filter.
.filter() only iterates elements in the array that have actually been assigned a value. Unlike plain variables, there is a difference between an array element that has assigned a value and one that has never been assigned a value and .filter() knows to skip the ones that have never been assigned a value (the ones that are essentially "sparse").
Here's some more detail on these three issues:
A variable that has been declared, but not explicitly initialized, has a value of undefined. That is its default value.
This, this code is as expected:
var a = undefined;
var b;
a === b; // returns true
Then, in your second code block, if you want to truly test for whether something is undefined, then you need to use ===, not == to avoid any type conversion.
var ar = new Array(4); // ar = 0[undefined × 4]
ar[0] = null; // ar = [null, undefined × 3]
ar[1] = undefined; // ar = [null, undefined, undefined × 2]
var result = ar.filter(function(item, index, arr){
return item === undefined;
});
for (var i = 0; i < result.length; i++) {
document.write(i, ": ", result[i], "<br>");
}
Note: .filter() will skip elements of the array that have not been initialized to have any value, thus they will never even be considered in the .filter() operation. If you try to read their values, you will get undefined, but .filter() knows the difference between a "sparse" array value that has never been assigned anything. It has to do with whether the array key exists or not, not what the value is and thus it is different for array items than it is for plain variables.
From the MDN doc for .filter():
The range of elements processed by filter() is set before the first
invocation of callback. Elements which are appended to the array after
the call to filter() begins will not be visited by callback. If
existing elements of the array are changed, or deleted, their value as
passed to callback will be the value at the time filter() visits them;
elements that are deleted are not visited.
Here's a demonstration of how the array keys do not actually exist until someting is assigned to the array element. This is the information that .filter() uses to know what to iterate and what to skip:
var ar = new Array(4);
ar[1] = "foo";
ar[2] = undefined;
for (var index in ar) {
document.write(index, ": ", ar[index], "<br>"); // 1 foo, 2 undefined
}
Note that indexes 0 and 3 are not iterated with the for loop because those keys do not exist in the array. The array is "sparse". Only some elements actually exist.
According to documentation at MDN you can provide this argument to forEach function if invoked like this, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach, But it doesn't work. It works if i use call with it.
As i understand call and apply are used to supply this and arguments to function. However, there should be no need. So what am i missing?
It definitely does work, the second parameter to forEach supplies context to the callback
var numbers = [1,2,3,4];
var sum = {value: 0};
numbers.forEach(function(num){
this.value += num;
}, sum);
console.log(sum); // Object {value: 10}
Explanation
It's already in the documentation (emphasis mine):
15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
callbackfn should be a function that accepts three arguments.
forEach calls callbackfn once for each element present in the
array, in ascending order. callbackfn is called only for elements of
the array which actually exist; it is not called for missing elements
of the array.
If a thisArg parameter is provided, it will be used as the this
value for each invocation of callbackfn. If it is not provided,
undefined is used instead.
thisArg is only used in the invocation of callbackfn. It's not, however, used to provide the this value for forEach, where this needs to be an array like structure (that means it has a length property). If you use Array.prototype.forEach(..., someObject), the this value in forEach's context will be undefined.
Simplified forEach version (which shows the problem immediately)
function forEach( callback , thisArg ){
// The algorithm parses the length as UInt32, see step 3.
// >>> not only acts as bit shift, but also forces the
// value into an unsigned number.
var len = this.length >>> 0, // using "this", not "thisArg"!
i;
for(i = 0; i < len; ++i){
callback.call(thisArg, this[i]);
// thisArg ^^^^^^^^^ is used here, not up at length
}
}
// example calls:
var logArguments = function(args){
console.log(args, this);
}
forEach(logArguments, [1,2,3]); // logs nothing
forEach.call([1,2,3], logArguments); // logs 1, 2, 3
forEach.call([1,2,3], logArguments, [2,3,4]); // logs "1 Array [2,3,4]"
// "2 Array [2,3,4]"
// "3 Array [2,3,4]"
Can some one please explain to me in a layman's what is happening in the loop as it iterates to produce the statement (the objects properties are in an array).
var o = {x:1, y:2, z:3};
var a = [], i = 0;
for (a[i++] in o)
{
console.log(o);
}
This is how the for/in loop is evaluated:
for each property in object o
assign the property name to the left hand side, that is a[i++]
Initially i = 0, so:
a[0] will get x. // notice it gets the property name, not its value
a[1] will get y.
a[2] will get z.
NOTE: i++ is equal to i = i + 1.
The previous code is equivalent to the following:
var o = {x:1, y:2, z:3};
var a = []
var i = 0;
for (propertyName in o)
{
a[i] = propertyName;
i = i + 1;
console.log(o);
}
It assigns an object with three keys (x,y,z) to o. It assigns an empty array to a, and the number 0 to i.
The for ( in ) loop will iterate over an object's properties, but first the condition is evaluated.
i++ is evaluated first. The ++ is a post-increment operator and is notorious for people getting the damn thing wrong. Douglass Crockford (search for him) suggests not using it. It returns the value stored in i (which was 0) then increments it.
So now i is storing 1, and we're evaluating a[0], which is accessing an element in an array, except... that array is empty (we're accessing an undefined value).
It now looks at in o, which iterates through the keys in o, which there are 3 of. Thus, it iterates the loop three times. Each time it then logs the object to the console.
Whatever this code is, I'd suggest replacing it. It shouldn't be something you want to see in code. It's confusing and surely doesn't do anything meaningful.
Firebug represents (new Array(N)) as an array with N undefineds in it. I recently ran across a scenario that demonstrated that a sized array with all undefined values in it is different from a newly constructed, sized array. I'd like to understand the difference.
Suppose you want to generate a list of random integers between 0 and 1000.
function kilorange() {
return Math.floor(Math.random() * (1001));
}
no_random_numbers = (new Array(6)).map(kilorange);
my_random_numbers = [undefined, undefined, undefined,
undefined, undefined, undefined].map(kilorange);
I would have expected no_random_numbers and my_random_numbers to be equivalent, but they're not. no_random_numbers is another array of undefineds, whereas my_random_numbers is an array with six random integers in it. Furthermore, after throwing a console.count statement into kilorange, I learned that my function never gets called for the array created with the Array constructor.
What is the difference, and why does map (and presumably other iterable methods) not treat the above arrays the same?
The ES standard (15.4.4.19) defines the algorithm for map, and it's quite clear from step 8b that since your array doesn't actually have any of those elements, it will return an "empty" array with length 6.
As others have mentioned, it has to do with array objects in js being (unlike their rigid C counterparts) very dynamic, and potentially sparse (see 15.4 for the sparsity test algorithm).
When you use:
var a = new Array(N);
no values are stored in the new array and even the index "properties" are not created. That is why map won't do a thing on that array.
The fact that Firebug does that is a bug/feature of the Firebug. You should remember that it's console is an eval envelope. There are other bug/features in the Firebug console.
For example in Chrome console you'll see the above a array as [].
Look at this sample and run it: http://jsfiddle.net/ArtPD/1/ (it create the two arrays without using the map over them and then list the key/values of each one of them)
I would say that (new Array(6)) doesn't allocate "named" properties (so it doesn't create "1": undefined, "2": undefined...) while the other form [undefined, ... ] does.
In fact if I use a for (var i in ... ) the two outputs are:
no_random_numbers
my_random_numbers
0 undefined
1 undefined
2 undefined
3 undefined
4 undefined
5 undefined
Good question, good answers. I fiddled a bit with the map prototype from MDN. If it's adapted like this, map would work for a new Array([length])
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
var O = Object(this);
var len = O.length >>> 0;
if ({}.toString.call(callback) != "[object Function]") {
throw new TypeError(callback + " is not a function");
}
if (thisArg) {
T = thisArg;
}
A = new Array(len);
k = 0;
while(k < len) {
var kValue, mappedValue;
if (k in O || (O.length && !O[k])) {
// ^ added this
kValue = O[ k ];
mappedValue = callback.call(T, kValue, k, O);
A[ k ] = mappedValue;
}
k++;
}
return A;
};
Based on Casy Hopes answer you could also make a mapx-extension to be able to use map with some new Array([length]):
Array.prototype.mapx = function(callback){
return this.join(',').split(',').map(callback);
}
//usage
var no_random_numbers = new Array(6).mapx(kilorange);
To answer the title question (why presize arrays), the only use that I've come across for initializing an Array(n) array is to create an n-1 length string:
var x = Array(6).join('-'); // "-----"
To answer why you would presize an array, you'd do it so you don't have to do as many individual allocations. Allocating memory takes some time, and it might trigger a garbage collection run that can take a lot longer.
On a typical web page, you won't notice a difference, but if you're doing something major, it might help a fair bit.