Length of subclassed array - javascript

In the following snippets the subclassed array behaves differently from original arrays. Why?
function Arr() {}
Arr.prototype = new Array();
var iArr = new Arr();
iArr[0] = 9;
iArr[5] = 10;
console.log(iArr.length);
console.log(iArr[0]);
console.log(iArr[5]);
iArr.push(87);
console.log(iArr.length);
console.log(iArr[0]);
console.log(iArr[5]);
results in
0
9
10
1
87
10
while
var iArr = new Array();
iArr[0] = 9;
iArr[5] = 10;
console.log(iArr.length);
console.log(iArr[0]);
console.log(iArr[5]);
iArr.push(87);
console.log(iArr.length);
console.log(iArr[0]);
console.log(iArr[5]);
gives
6
9
10
7
9
10
what I would have expected for the first snippet as well.
I cannot understand why the length property in the first snippet does not change through accessing elements by index.

length behaviors only work for objects that have been created by the Array constructor via new Array. that is, length auto-behaviors only work for genuine Array instances. Here, the only Array instance you have is Arr.prototype, which is iArr's prototype parent. iArr was not created by new Array, so it does not get the magic length behaviors.
Setting values on iArr does not manipulate the prototype parent in any way, so your Array instance is unmodified, which means that length is unmodified.
When you call iArr.push, you're not modifying the parent Array instance either, but the ES5 spec for push requires that push set the length property of whatever object is called on:
Call the [[Put]] internal method of O with arguments "length", n, and true.
For an example that works, using ES6 techniques, see Array subclassing with setPrototypeOf

Related

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.

JavaScript: Weird (edge?) case of a mixed Array/Object

I saw this for the first time (or noticed it for the first time) today during a maintenance of a colleagues code.
Essentially the "weird" part is the same as if you try to run this code:
var arr = [];
arr[0] = "cat"; // this adds to the array
arr[1] = "mouse"; // this adds to the array
arr.length; // returns 2
arr["favoriteFood"] = "pizza"; // this DOES NOT add to the array. Setting a string parameter adds to the underlying object
arr.length; // returns 2, not 3
Got this example from nfiredly.com
I don't know what the technical term for this "case" is so I haven't been able to find any additional information about it here or on Google but it strikes me very peculiar that this "behaviour" can at all exists in JavaScript; a kind of "mix" between Arrays and Objects (or Associative Arrays).
It states in the above code snippet that that Setting a string parameter adds to the underlying object and thus not affect the length of the "array" itself.
What is this kind of pattern?
Why is it at all possible? Is it a weird JS quirk or is it deliberate?
For what purpose would you "mix" these types?
It's possible because arrays are objects with some special behaviors, but objects nevertheless.
15.4 Array Objects
However, if you start using non-array properties on an array, some implementations may change the underlying data structure to a hash. Then array operations might be slower.
In JavaScript, arrays, functions and objects are all objects. Arrays are objects created with Array constructor function.
E.g.
var a = new Array();
Or, using shortcut array literal,
var a = [];
Both are the same. They both create objects. However, special kind of object. It has a length property and numeric properties with corresponding values which are the array elements.
This object (array) has methods like push, pop etc. which you can use to manipulate the object.
When you add a non-numeric property to this array object, you do not affect its length. But, you do add a new property to the object.
So, if you have
var a = [1];
a.x = 'y';
console.log(Object.keys(a)); // outputs ["0", "x"]
console.log(a); // outputs [1];

What's is the difference between Array.of(n) , Array(n) and array = [n]?

As the title, i'm wondering what's the difference between this 3 methods of initialization an array.
I'm actually more interested in the new Array.of() method provided from ES6, why they feel the needs of implements that?
The Array constructor can be called in two ways: a list of values to be used as values for array elements, or with a single numeric value giving the initial length:
var myArray = new Array("hello", "world"); // 2 elements
var otherArray = new Array(100); // 100 elements, all empty
Because there's an ambiguity when just one number is passed, that old API is considered badly designed. Thus, there's Array.of(), which is the same as the first option for the Array constructor:
var otherArray = Array.of(100); // 1 element
The third way to make an array is with an array initialization expression:
var otherArray = [100]; // 1 element
The array instances that are created by each of the above are functionally equivalent and completely interchangeable.
One more thing: why does Array.of() have to exist, given that we can use the array initialization expression? Well, Array.of() is a function, so it can be used as a value applied in functional-style programming. You can (as a slightly dumb example) copy an array with:
var copy = Array.of.apply(Array, original);
One reason that's dumb is that there's also (in ES2015) Array.from() to do the same thing:
var copy = Array.from(original);
That works on any sort of iterable original, so it's a good way to turn arguments or a NodeList into an array.
The MDN site has documentation on Array.of(). The constructor and the array initializer form have been around since forever, so any JavaScript reference will cover those (though possibly without reference to Array.of()).
Array.of(2) will create an array with the element 2.
var temp = Array.of(2); // temp = [2]
Array(2) will create an array of 2 elements.
var temp = new Array(2); // temp = [undefined, undefined]
temp = [2] will create an array with the element 2.
var temp = [2]; // temp = [2]

Is always necessary to use new at create an instance of Array in JavaScript? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What are the best practices to follow when declaring an array in Javascript?
Would be correct don't use new Array after of defining a variable using []?
var v = [];
v = Array(5); // is new necessary?
You are creating two Array objects and throwing away one, so what you are doing is not necessary.
Declare the variable and create an array:
var v;
v = new Array(5);
Or as a single statement:
var v = new Array(5);
The preferred way of creating arrays is using new Array when you want to specify a size, and an array literal when you want to create an array from items:
var a1 = new Array(42); // creates an array with 42 undefined items
var a2 = [1, 2, 3]; // creates an array with the 3 items
For a zero size array you can use either:
var a1 = new Array(); // creates an empty array
var a2 = []; // creates an empty array
The Array constructor can also take a list of items, but that use should be avoided because it behaves differently if there is one item and that item is numerical:
var a1 = new Array(100, 101, 102); // creates an array with three items
var a2 = new Array(100, 101); // creates an array with two items
var a3 = new Array(100); // creates an array with 100 undefined items
The first line creates an empty array.
The second creates an array with 5 elements, all of which have the value undefined.
There is no sense in using both of them. Which one to use depends on what you are trying to do, but the second form is not encountered very often in practice. It's very likely that the first is all you are going to need.
No it is not necessary because both do the same thing. Basically all you should be doing is :
var v=[]; or var v=[value1, value2 ...];
The second version of defining an array has some issues like:
Array(4) ==> creates an empty 4-elements array
Array(4,5) ==> creates an array with 2 elements (4 and 5)
So if you want to create a 1 element array, it blows up in your face. Therefore, the first version is recommended.
The simple answer is that there's almost never a good reason to use the Array keyword in a declaration, whether that's:
var a = Array(n)
or
var a = new Array(n)
just use:
var a = [];
instead.
See https://stackoverflow.com/a/11500550/6782
Its not necessary to use new with Array,i think you can create Array without using new operator
You are reassigning the value of v from a zero length array to an array of length 5. This makes the =[] part redundant.
EDIT:
The major difference here is that the Array constructor's prototype can be modified, so what you get in the end is not necessarily identical to a standard issue javascript Array.
When you assign using [], an internal constructor is used, which means Array prototype tampering will not affect your new object. For example, I can set Array.prototype.join=function(){alert("duh");} and all Arrays that you construct using new Array(...) will alert "duh" when you try to call the join method. Arrays assigned using [] are immune to this.
JavaScript - unlike most language - provides you arrays that kind of self-extend themselves when needed. But that doesn't mean it has no cost.
If you have an array of length N and put something at index M >= N, it has to create a new array of length M - 1, copy everything from N into it and then add your element, that's quite slow.
Note that JavaScript has very diverse implementations and that some just consider arrays as normal objets with a special length property.
So to avoid that, when you know what size your array will be but not what it'll contain, you use new Array( size ). If you know what it will contain, it's better to use the litteral notation [ item1, item2 ]. And if you really don't know, just put [].
added

How does a javascript array work whilst pushing new elements?

I tested this code in Chrome / Firefox :
console.time('simple push');
var arr0 = [];
for(var i =0; i < 1000000; i++){
arr0.push(i);
}
console.timeEnd('simple push');
console.time('set length and push');
var arr1 = [];
arr1.length=1000000;
for(var j =0; j < 1000000; j++){
arr1[j]=j;
}
console.timeEnd('set length and push');
console.time('new Array push');
var arr2 = new Array(1000000);
for(var k =0; k < 1000000; k++){
arr2[k]=k;
}
console.timeEnd('new Array push');
Chrome 13 Result
simple push:59ms
set length and push:192ms
new Array push:187ms
Firefox 4 Result
simple push:76ms
set length and push:44ms
new Array push:40ms
My doubt
So new Array operation is definitely the slowest, but I wanna know why?
Why set length behaves different in Chrome and Firefox, it seems preallocated memory doesn't works well in Chrome?
Update
I updated Chrome and FF results.
Why is the new Array(N) the slowest?
console.log(arr0.length);
console.log(arr1.length);
console.log(arr2.length);
1000000
1000000
2000000
As you have been already made aware of, your testcases were flawed. Here is a jsperf test case which uses assignment in all methods.
The speed is the same for all of them in Firefox 6 (~50,000 op/s). In Chrome 13 however, setting the array length beforehand results in a huge speed improvement (~80,000 op/s vs. ~400.000 op/s). To find the reason for that, one would have to have a look at the source code of Chrome's JavaScript engine.
You asked what happens on .push() and new Array. The specification describes was should happen:
15.4.2.2 new Array (len)
The [[Prototype]] internal property of the newly constructed object is set to the original Array prototype object, the one that is the initial value of Array.prototype (15.4.3.1). The [[Class]] internal property of the newly constructed object is set to "Array". The [[Extensible]] internal property of the newly constructed object is set to true.
If the argument len is a Number and ToUint32(len) is equal to len, then the length property of the newly constructed object is set to ToUint32(len). If the argument len is a Number and ToUint32(len) is not equal to len, a RangeError exception is thrown.
If the argument len is not a Number, then the length property of the newly constructed object is set to 1 and the 0 property of the newly constructed object is set to len with attributes {[[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}..
15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
When the push method is called with zero or more arguments item1, item2, etc., the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenVal be the result of calling the [[Get]] internal method of O with argument "length".
Let n be ToUint32(lenVal).
Let items be an internal List whose elements are, in left to right order, the arguments that were passed to this function invocation.
Repeat, while items is not empty
Remove the first element from items and let E be the value of the element.
Call the [[Put]] internal method of O with arguments ToString(n), E, and true.
Increase n by 1.
Call the [[Put]] internal method of O with arguments "length", n, and true.
Return n.
That said, the implementation can be different. As you saw from the difference in Firefox and Chrome, Chrome seems to optimize the array structure internally.

Categories

Resources