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.
Related
When I use a for ... in loop like:
for(var i in object) {}
I want to know if the object in question is evaluated just once or each time the loop, well, loops.
I made a quick test in my browser (also in node) like:
for(var i in (console.log('INIT'), [1, 2, 3, 4])) { console.log('KEY', i); }
and I get:
INIT
KEY 0
KEY 1
KEY 2
KEY 3
So from this empirical evidence, I could assume that is indeed only evaluated once.
But, is this behavior standard?
From the Mozilla documentation, a for...in loop will iterate over all enumerable properties of the object itself and those the object inherits from its constructor's prototype. An (enumerable) property that is deleted before it has been visited will not be visited later. Properties added to the object over which iteration is occurring may either be visited or omitted from iteration.
In short, the outcome of the example posted by #Amit is not guaranteed, although Chrome and IE may use a different specification for the for ..in loop. However, at least deleting an element seems to prevent it from being visited in Chrome:
var obj = { a: 1, b: 2, c: 3 };
for(var k in obj) {
document.write(k);
delete obj['c'];
}
Bear in mind that (console.log('INIT'), [1, 2, 3, 4]) is an expression that evaluates to [1, 2, 3, 4] so it's not a valid evidence of the issue. A better empirical evidence can be obtained with:
var obj = { a: 1, b: 2, c: 3 };
for(var k in obj) {
document.write(k);
obj.d = 4;
}
And we don't see "d"...
The ECMAScript® Language Specification, section 12.6.4 does not say much about this, except that the expression on the right side is evaluated once and cast to an object:
Let exprRef be the result of evaluating the Expression.
Let experValue be GetValue(exprRef).
If experValue is null or undefined, return (normal, empty, empty).
Let obj be ToObject(experValue).
This says nothing about evaluating any of the keys or values of that object at that time.
In fact, the part that follows the quoted paragraph suggests the keys could be retrieved during the iterations:
Repeat
Let P be the name of the next property of obj whose [[Enumerable]] attribute is true. If there is no such property, return (normal, V, empty).
But there is no requirement in either direction. So, ... this could be implementation (browser) dependent.
When you say that the object is evaluated just once, that can be have different meanings:
The object reference is evaluated just once. This is important when that object is the outcome of an expression, such as a function call. But this is surely taken for granted and not what you mean;
The object's keys (properties) are enumerated once, but the values are retrieved on demand;
The object's keys and values are retrieved once. This could be done at 1 level or nested levels at a potential high memory cost.
This test case shows in my browser (FireFox) the second happens, not the third:
var obj = {a: 1, b: 2, c: 3};
for (key in obj) {
document.write('obj[' + key + '] = ' + obj[key] + '<br>');
if (key=='a') {
// modify a value that is still to be visited
obj["c"] = 4;
// add a key/value pair
obj["d"] = 9;
}
}
Output is (as you can probably also see in your browser):
obj[a] = 1
obj[b] = 2
obj[c] = 4
So the keys were only retrieved once, the values on demand (i.e. not at the start of the loop).
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.
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.
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.
For example:
var a = [];
function p(x) { a.push(x); }
[[p(1),p(2)],p(3),[p(4),[p(5)]],p(6)]
a == [1,2,3,4,5,6] // Always true?
Is 'a == [1,2,3,4,5,6]' defined behavior? Can it be relied upon?
Are elements of Javascript arrays processed in a defined order?
Yes they are.
Is 'a == [1,2,3,4,5,6]' defined behavior? Can it be relied upon?
No, the equals operator performs referential equality when comparing object.
Short answer: "Yes".
Longer answer: Your question is actually about JavaScript statements in general and not Arrays. The code you posted ([[p(1),p(2)],p(3),[p(4),[p(5)]],p(6)]) is not an Array, it is a statement that returns an Array whilst also populating an Array. JavaScript statements are executed according to the rules of operator precedence.
Your question is not very clear, what do you mean by "processed"?
var a = [];
That is an array literal, it assigns a reference to an empty array to a.
function p(x) { a.push(x); }
Each time push() is called, a new element is added to a at index a.length (i.e. it is always added after the highest index).
[[p(1),p(2)],p(3),[p(4),[p(5)]],p(6)]
That is an array literal that is equivalent to:
a = [];
a[0] = [p(1),p(2)];
a[1] = p(3);
a[2] = [p(4),[p(5)]];
a[3] = p(6);
The following expression:
a == [1,2,3,4,5,6]
is always false, since arrays are objects and objects are never equal to any other object. You can do:
var a = [0, 1];
var b = a;
a == b; // true since a and b reference the same array
But
var a = [0, 1];
var b = [0, 1];
a == b; // false since a and b reference different arrays