Is it an antipattern to set an array length in JavaScript? - javascript

Is it bad to use code like:
var a = [1,2,3,4];
a.length = 2; // 3 and 4 are removed
Does it have decent browser support? Do the removed values get garbage collected properly?

Does it have decent browser support?
Yes. This has been present since the very first edition of ECMAScript:
Specifically, whenever a property is added whose name is an array index, the
length property is changed, if necessary, to be one more than the numeric value of that array index; and whenever
the length property is changed, every property whose name is an array index whose value is not smaller than the
new length is automatically deleted.
Standard ECMA-262, 15.4
In ECMAScript 5, this was moved to 15.4.5.2.
Attempting to set the length property of an Array object to a value that is numerically less than or equal to the largest numeric property name of an existing array indexed non-deletable property of the array will result in the length being set to a numeric value that is one greater than that largest numeric property name.
Standard ECMA-262, 15.4.5.2
All browsers support this behavior.
Do the removed values get garbage collected properly?
Yes. (See quotes above, and 15.4.5.1.)
Is it an antipattern to set array length in Javascript?
No, not really. While arrays are "exotic objects" in ES6-speak, the real crazily unique thing about arrays are their indexing, not setting the length property. You could replicate the same behavior with a property setter.
It's true that property setters with non-subtle effects are somewhat unusual in JS, as their side-effects can be unobvious. But since .length has been there in Javascript from Day 0, it should be fairly widely understood.
If you want an alternative, use .splice():
// a is the array and n is the eventual length
a.length = n;
a.splice(n, a.length - n); // equivalent
a.splice(n, a.length); // also equivalent
If I were avoid setting .length for some reason, it would be because it mutates the array, and I prefer immutable programming where reasonable. The immutable alternative is .slice().
a.slice(0, n); // returns a new array with the first n elements

Arrays are exotic objects.
An exotic object is any form of object whose property semantics differ in any way from the default semantics.1
The property semantics for arrays are special, in this case that changing length affects the actual contents of the array.
An Array object is an exotic object that gives special treatment to array index property keys [...] 2
The behavior of the specific property key for this array exotic object is outlined as well.
[...] Every Array object has a length property whose value is always a nonnegative integer less than 2^32. The value of the length property is numerically greater than the name of every own property whose name is an array index; whenever an own property of an Array object is created or changed, other properties are adjusted as necessary to maintain this invariant [...]
This section is detailing that the length property is a 32 bit integer. It is also saying that if an array index is added, the length is changed. What this is implying is that when an own property name that is not an index is used, the length is not changed and also that names which are not indexes are considered numerically less than the indexes. This means that if you have an array and also add a string (not implicitly numeric either, as in not "3") property to it, that changing the length to delete elements will not remove the value associated with the string property. For example,
var a = [1,2,3,4];
a.hello = "world";
a.length = 2; // 3 and 4 are removed
console.log(a.hello);//"world"
[...] Specifically, whenever an own property is added whose name is an array index, the value of the length property is changed, if necessary, to be one more than the numeric value of that array index; [...]
In addition to the truncation, expansion is also available by use of an index. If an index value is used (an integer basically) then the length will be updated to reflect that change. As arrays in JavaScript are sparse (as in, no gaps allowed) this means that adding a value for a larger index can make an array rather larger.
var a = [1,2,3,4];
a[50] = "hello";
console.log(a.length);//51
[...] and whenever the value of the length property is changed, every own property whose name is an array index whose value is not smaller than the new length is deleted.
Finally, this is the specific aspect in question which the OP raises. When the length property of an array is modified, it will delete every index and value which is numerically more than the new length value minus 1. In the OP's example, we can clearly see that changing the length to 2 removed the values at index 2 and 3, leaving only the first two values who had index 0 and 1.
The algorithm for the deletion mentioned above can be found in the ArraySetLength(A, Desc) definition.
b. Let deleteSucceeded be A.[[Delete]](ToString(oldLen)). 3
Which is converting the index to a string and using the delete behavior for objects on the index. At which point the entire property is removed. As this internally reaches the delete call, there is no reason to believe that it will leak memory or even that it is an anti pattern as it is explicitly described in the language specification.
In conclusion, Does it have decent browser support? Yes. Do the removed values get garbage collected properly? Yes.
However, is it an anti-pattern? This could be argued either way. What it really breaks down to is the level of familiarity with the language of others who would be using the same code which is a nice way of saying that it may not have the best readability. On the other hand, since JavaScript does take up bandwidth or memory with each character used (hence the need for minification and bundling) it can be useful to take this approach to truncate an array. More often than not, worrying about this type of minutia is going to be a micro-optimization and the use of this special property should be considered on a case to case basis in context with the overall design of the related code.
1. The Object Type ECMA 6
2. Array Exotic Objects ECMA 6
3. ArraySetLength(A, Desc) ECMA 6

As it is writeable https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length (seen here) It should be okay to use it that way
You can set the length property to truncate an array at any time. When you extend an array by changing its length property, the number of actual elements does not increase; for example, if you set length to 3 when it is currently 2, the array still contains only 2 elements.
There's even an example how to shorten an array, using this on Mozilla Developer Network
if (statesUS.length > 50) {
statesUS.length = 50;
}

It's better to use slice because your intentions are clear.
var truncated = a.slice(0,2)
Slice does create a new array though.
If that is an issue for you then there is nothing wrong with modifying the length property.
It's actually the fastest way of truncation and supported in all browsers. jsperf

Is it an antipattern to set array length in JavaScript?
No. It would be OK to set it to truncate the array.
Does it have decent browser support?
Yes, see the compatiblity matrix here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length
Do the removed values get garbage collected properly?
Yes, they should. Will they actually get gced - you will need to profile the JavaScript code if this is a real concern.

This way of truncating an array is mentioned in JavaScript: Good Parts, so it is not an anti-pattern.

It is not an anti pattern. It is supported in all major browsers.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length

Related

Length of the javascript array does not change after deleting the element from it

I am trying to copy one array values into another, but without breaking the links that is associated with this array other words i can not just assign the new array to this value, thats why i cannot use methods like slice() or concat().
Here is the code of the function that does the thing:
self.updateBreadcrumbs = function (newBreadcrumbs) {
var old_length = self.breadcrumbs.length;
var new_length =newBreadcrumbs.length;
var j = new_length > old_length ? new_length: old_length;
for (var i = 0; i < j; i++) {
if(old_length < i+1){
self.breadcrumbs.push(newBreadcrumbs[i]);
continue;
}
if(new_length < i+1){
delete self.breadcrumbs[i];
continue;
}
if (self.breadcrumbs[i].title !== newBreadcrumbs[i].title) {
self.breadcrumbs[i] = newBreadcrumbs[i];
}
}
}
My problem is that length of the array does not change when i delete something from the array.
P.S If you know any easier way to do this i am totally open for propositions.
Length of an Array can never change by deleting elements in it.
However It can be altered with splice
eg.
var arr=[1,2,3,4,5]; //length 5
arr.splice(0,1); //length 4
Unlike what common belief suggests, the delete operator has nothing to do with directly freeing memory. delete is only effective on an object's properties. It has no effect on array length
The splice() method changes the content of an array by removing
existing elements and/or adding new elements.
More about Splice
The length property of an Array object is a property that is maintained by it's methods, it is not an inherent characteristic of the object itself. So, if you take an action on it that is not one of Array's own methods, it will not update the length property. In this case, it is the splice method that you would want to use ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice )
Interestingly, you can change the makeup of an array by directly altering its length . . . for example, if you have an array with 6 elements in it and you were to change its length property to 5 (i.e., array.length = 5;), it would no longer recognize the 6th element as being part of the array.
More on the length property: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length
An Array is (aside from its prototype methods) not much more than a simple object. The difference is in the length property which has a spcieal setter method, and the different way of adding a new property.
If a property is added to the object, its name is tested for being a numerical value and whether this value is >= the current array length. if so, the internal length is updated.
If the length is set, it is checked, whether the new value is smaller than the old one, and if so, all own properties with a property name that is a numerical value and >= the new length will be deleted.
If you delete an indexed element from an array, it will just be deleted (and accessing this index later will return undefined, as it would do with any non-existent property). It won't change the array length, nor will increasing the array length create any properties for the added index positions (sparse array). Using [] or new Aray[10000] will both create Arrays with exactly the same memory footprint. Just the length property has a different initial value.
This is completely different from Arrays in other languages, where an array of size n will have n storage spaces reserved for the elements (being pointers to objects or being native value types). So in other languages, creating an array with a given size and filling it is faster than creating an empty array and adding values (in which case each time a larger consecutive space must be allocated and the array must be copied to this larger memory psoition before adding the element). OTOH, accessing the elements of an JS array is somewhat slower (as they are just named properties which have to be located in the property list first) than in other languages where an element is at its given, fixed position.
This answer will not help if you use array, but.
If you still need to delete item by key and you can use object:
$o = {'blue' : '', 'apple' : '', 'red' : ''};
console.log(Object.keys($o).length); // will output 3
delete $o['apple'];
console.log(Object.keys($o).length); // will output 2

Is a JavaScript array index a string or an integer?

I had a generic question about JavaScript arrays. Are array indices in JavaScript internally handled as strings?
I read somewhere that because arrays are objects in JavaScript, the index is actually a string. I am a bit confused about this, and would be glad for any explanation.
Formally, all property names are strings. That means that array-like numeric property names really aren't any different from any other property names.
If you check step 6 in the relevant part of the spec, you'll see that property accessor expressions are always coerced to strings before looking up the property. That process is followed (formally) regardless of whether the object is an array instance or another sort of object. (Again, it just has to seem like that's what's happening.)
Now, internally, the JavaScript runtime is free to implement array functionality any way it wants.
edit — I had the idea of playing with Number.toString to demonstrate that a number-to-string conversion happens, but it turns out that the spec explicitly describes that specific type conversion as taking place via an internal process, and not by an implicit cast followed by a call to .toString() (which probably is a good thing for performance reasons).
That is correct so:
> var a = ['a','b','c']
undefined
> a
[ 'a', 'b', 'c' ]
> a[0]
'a'
> a['0']
'a'
> a['4'] = 'e'
'e'
> a[3] = 'd'
'd'
> a
[ 'a', 'b', 'c', 'd', 'e' ]
Yes, technically array-indexes are strings, but as Flanagan elegantly put it in his 'Definitive guide':
"It is helpful to clearly distinguish an array index from an object property name. All indexes are property names, but only property names that are integers between 0 and 232-1 are indexes."
Usually you should not care what the browser (or more in general 'script-host') does internally as long as the outcome conforms to a predictable and (usually/hopefully) specified result. In fact, in case of JavaScript (or ECMAScript 262) is only described in terms of what conceptual steps are needed. That (intentionally) leaves room for script-host (and browsers) to come up with clever smaller and faster way's to implement that specified behavior.
In fact, modern browsers use a number of different algorithms for different types of arrays internally: it matters what they contain, how big they are, if they are in order, if they are fixed and optimizable upon (JIT) compile-time or if they are sparse or dense (yes it often pays to do new Array(length_val) instead of ninja []).
In your thinking-concept (when learning JavaScript) it might help to know that arrays are just special kind of objects. But they are not always the same thing one might expect, for example:
var a=[];
a['4294967295']="I'm not the only one..";
a['4294967296']="Yes you are..";
alert(a); // === I'm not the only one..
although it is easy and pretty transparent to the uninformed programmer to have an array (with indexes) and attach properties to the array-object.
The best answer (I think) is from the specification (15.4) itself:
Array Objects
Array objects give special treatment to a certain class of property
names. A property name P (in the form of a String value) is an array
index if and only if ToString(ToUint32(P)) is equal to P and
ToUint32(P) is not equal to 232−1. A property whose property name is
an array index is also called an element. Every Array object has a
length property whose value is always a nonnegative integer less than
232. The value of the length property is numerically greater than the name of every property whose name is an array index; whenever a
property of an Array object is created or changed, other properties
are adjusted as necessary to maintain this invariant. Specifically,
whenever a property is added whose name is an array index, the length
property is changed, if necessary, to be one more than the numeric
value of that array index; and whenever the length property is
changed, every property whose name is an array index whose value is
not smaller than the new length is automatically deleted. This
constraint applies only to own properties of an Array object and is
unaffected by length or array index properties that may be inherited
from its prototypes.
An object, O, is said to be sparse if the following algorithm returns
true:
Let len be the result of calling the [[Get]] internal method of O with argument "length".
For each integer i in the range 0≤i<ToUint32(len)
a. Let elem be the result of calling the [[GetOwnProperty]] internal method of O with argument ToString(i).
b. If elem is undefined, return true.
Return false.
Effectively the ECMAScript 262 spec just ensures to the JavaScript-programmer unambiguous array-references regardless of getting/setting arr['42'] or arr[42] up to 32-bit unsigned.
The main difference is for example (auto-updating of) array.length, array.push and other array-sugar like array.concat, etc.
While, yes, JavaScript also lets one loop over the properties one has set to an object, we can not read how much we have set (without a loop). And yes, to the best of my knowledge, modern browsers (especially chrome in what they call (but don't exactly specify)) 'small integers' are wicked fast with true (pre-initialized) small-int arrays.
Also see for example this related question.
Edit: as per #Felix Kling's test (from his comment above):
After arr[4294967294] = 42;, arr.length correctly shows 4294967295. However, calling arr.push(21); throws a RangeError: Invalid array length. arr[arr.length] = 21 works, but doesn't change length.
The explanation for this (predictable and intended) behavior should be clear after this answer.
Edit2:
Now, someone gave the comment:
for (var i in a) console.log(typeof i) shows 'string' for all indexes.
Since for in is the (unordered I must add) property iterator in JavaScript, it is kind of obvious it returns a string (I'd be pretty darned if it didn't).
From MDN:
for..in should not be used to iterate over an Array where index order
is important.
Array indexes are just enumerable properties with integer names and
are otherwise identical to general Object properties. There is no
guarantee that for...in will return the indexes in any particular
order and it will return all enumerable properties, including those
with non–integer names and those that are inherited.
Because the order of iteration is implementation dependent, iterating
over an array may not visit elements in a consistent order. Therefore
it is better to use a for loop with a numeric index (or Array.forEach
or the for...of loop) when iterating over arrays where the order of
access is important.
So.. what have we learned? If order is important to us (often is with arrays), then we need this quirky array in JavaScript, and having a 'length' is rather useful for looping in numerical order.
Now think of the alternative: Give your objects an id/order, but then you'd need to loop over your objects for every next id/order (property) once again...
Edit 3:
Someone answered along the lines of:
var a = ['a','b','c'];
a['4'] = 'e';
a[3] = 'd';
alert(a); // returns a,b,c,d,e
Now using the explanation in my answer: what happened is that '4' is coercible to integer 4 and that is in the range [0, 4294967295] making it into a valid array index also called element. Since var a is an array ([]), the array element 4 gets added as array element, not as property (what would have happened if var a was an object ({}).
An example to further outline the difference between array and object:
var a = ['a','b','c'];
a['prop']='d';
alert(a);
see how it returns a,b,c with no 'd' to be seen.
Edit 4:
You commented: "In that case, an integer index should be handled as a string, as it is a property of the array, which is a special type of JavaScript object."
That is wrong in terms of terminology because: (strings representing) integer indexes (between [0, 4294967295]) create array indexes or elements; not properties.
It's better to say: Both an actual integer and a string representing an integer (both between [0, 4294967295]) is a valid array index (and should conceptually be regarded as integer) and creates/changes array elements (the 'things'/values (only) that get returned when you do arr.join() or arr.concat() for example).
Everything else creates/changes a property (and should conceptually be regarded as string).
What the browser really does, usually shouldn't interest you, noting that the simpler and clearer specified you code, the better chance the browser has to recognize: 'oh, let’s optimize this to an actual array under the hood'.
Let's see:
[1]["0"] === 1 // true
Oh, but that's not conclusive, since the runtime could be coercing "0" to +"0" and +"0" === 0.
[1][false] === undefined // true
Now, +false === 0, so no, the runtime isn't coercing the value to a number.
var arr = [];
arr.false = "foobar";
arr[false] === "foobar" // true
So actually, the runtime is coercing the value to a string. So yep, it's a hash table lookup (externally).
In JavaScript there are two type of arrays: standard arrays and associative arrays (or an object with properies)
[ ] - standard array - 0 based integer indexes only
{ } - associative array - JavaScript objects where keys can be any strings
So ...
var arr = [ 0, 1, 2, 3 ];
... is defined as a standard array where indexes can only be integers. When you do arr["something"] since something (which is what you use as index) is not an integer you are basically defining a property to the arr object (everything is object in JavaScript). But you are not adding an element to the standard array.

Create an associative array with integer keys

I need to create an associative array in javascript with an integer key as follows;
a["10"] = "ten";
but when i create an array, it puts the value to the 10th index of the array and it creates an array with the length 11. I want it to be a key value pair. I know this can be done by using objects but i need an array only.
JavaScript does not have associative arrays. The only way to do this in JavaScript is to use objects:
var a = {
'10': 'ten'
};
ECMAScript does have Associated Arrays1 - Objects (and by extension, Arrays) are an example
However, some properties of Arrays are treated specially:
Array objects give special treatment to a certain class of property names. A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P ..
.. Specifically, whenever a property is added whose name is an array index, the length property is changed, if necessary, to be one more than the numeric value of that array index ..
Thus, given arr = [], the expressions arr["1"] and arr[1] refer to the same property name. Since P (the property name) is "1" and length is 0 from above, then assignment to such property will set arr.length to ToUint32(P)+1, or 2.
It is not possible to change this behavior. If you wish to not have a special length property, then use a "normal" Object instead of an Array. However, many of the Array.prototype functions can be used with arbitrary objects (with some implementation quirks aside) that have a length property and an Object can be created such that it uses Array.prototype as its own prototype.
All that being said, the post does not say what the real issue is. Instead of supposing that it must be done in that particular manner, consider explaining what the intent is: e.g. why a["10"]? And what is wrong if there are "11 items" if the object will be used in a List?
1 Please read the article before debating this statement: the term "Array" in the name does not imply an ordered sequence nor does it preclude an additional notion of a Length or the use of Hashing, etc. If you are going by a different definition, make sure to specify what it is and what the desired behavior is for a given operation.

Javascript Array index fundamentals

I am not sure how the Javascript engines (specifically browser engines) store an array.
For example - how much memory would this use?
var x = new Array(0, 1, 2, 1000, 100000000);
I want to map integer dates as array indexes, but I need to be sure it isn't a bad idea.
Arrays are "special" in only a couple ways:
They've got some interesting array-like methods from their prototype ("slice()" etc)
They've got a "magic" length property that tracks the largest numeric property "name"
If you store something at position 10299123 in a brand-new array, the runtime does not use up all your memory allocating an actual, empty array. Instead, it stores whatever you want to store and makes sure that length is updated to 10299124.
Now the problem specifically with dates, if you're talking about storing the timestamp, is that (I think) they're bigger than 32-bit integers. Array indexes are limited to that size. However, all that means is that length won't be correct. If you don't really care about any of the array stuff anyway, then really all you need is a plain object:
var dateStorage = {};
dateStorage[someDate.getTime()] = "whatever";
JavaScript objects can be used as name-value maps as long as the name can be represented as a string (which is clearly true for numbers).

How are JavaScript arrays implemented?

Namely, how does the following code:
var sup = new Array(5);
sup[0] = 'z3ero';
sup[1] = 'o3ne';
sup[4] = 'f3our';
document.write(sup.length + "<br />");
output '5' for the length, when all you've done is set various elements?
My 'problem' with this code is that I don't understand how length changes without calling a getLength() or a setLength() method. When I do any of the following:
a.length
a['length']
a.length = 4
a['length'] = 5
on a non-array object, it behaves like a dict / associative array. When I do this on the array object, it has special meaning. What mechanism in JavaScript allows this to happen? Does JavaScript have some type of property system which translates
a.length
a['length']
into "get" methods and
a.length = 4
a['length'] = 5
into "set" methods?
Everything in JavaScript is an object. In the case of an Array, the length property returns the size of the internal storage area for indexed items of the array. Some of the confusion may come into play in that the [] operator works for both numeric and string arguments. For an array, if you use it with a numeric index, it returns/sets the expected indexed item. If you use it with a string, it returns/sets the named property on the array object - unless the string corresponds to a numeric value, then it returns the indexed item. This is because in JavaScript array indexes are coerced to strings by an implicit toString() call. Frankly, this is just one more of those things that makes you scratch your head and say "JavaScript, this, this is why they laugh at you."
The actual underlying representation may differ between browsers (or it may not). I wouldn't rely on anything other than the interface that is supplied when working with it.
You can find out more about JavaScript arrays at MDN.
Characteristics of a JavaScript array
Dynamic - Arrays in JavaScript can grow dynamically .push
Can be sparse - for example, array[50000] = 2;
Can be dense - for example, array = [1, 2, 3, 4, 5]
In JavaScript, it is hard for the runtime to know whether the array is going to be dense or sparse. So all it can do is take a guess. All implementations use a heuristic to determine if the array is dense or sparse.
For example, code in point 2 above, can indicate to the JavaScript runtime that this is likely a sparse array implementation. If the array is initialised with an initial count, this could indicate that this is likely a dense array.
When the runtime detects that the array is sparse, it is implemented in a similar way to an object. So instead of maintaining a contiguous array, a key/value map is built.
For more references, see How are JavaScript arrays implemented internally?
This really depends on what you intend to do with it.
[].length is "magical".
It doesn't actually return the number of items in the array. It returns the largest instated index in the array.
var testArr = []; testArr[5000] = "something"; testArr.length; // 5001
But the method behind the setter is hidden in the engine itself.
Some engines in some browsers will give you access to their implementations of those magic-methods.
Others will keep everything completely locked down.
So don't rely on defineGetter and defineSetter methods, or even, really, __proto__ methods, unless you know which browsers you know you're targeting, and which you aren't.
This will change in the future, where opt-in applications written in ECMAScript Next/6 will have access to more.
ECMAScript 5-compliant browsers are already starting to offer get and set magic methods in objects and there's more to come... ...but it's probably a while away before you can dump support for oldIE and a tonne of smartphones, et cetera...
It is important to know that when you do sup['look'] = 4; you are not using an associative array, but rather modify properties on the object sup.
It is equivalent to sup.look = 4; since you can dynamically add properties on JavaScript objects at any time. sup['length'] would for an instance output 5 in your first example.
To add to tvanfosson's answer: In ECMA-262 (the 3.0 specification, I believe), arrays are simply defined as having this behavior for setting properties (See 15.4.5.1). There's no general mechanism underlying it (at least as of now) - this is just how it's defined, and how JavaScript interpreters must behave.
As other people have mentioned, a property in JavaScript can basically act as both as getter and a setter of your array (or string or other inputs).
As a matter of fact, you might try this yourself:
const test = [1, 2, 3, 4, 5]
test.length = 3
console.log(test) // [1, 2, 3]
test.length = 5
console.log(test) // Guess what happens here!
As far as I know, arrays in JavaScript do not work exactly like associative arrays and you have elements which are put in memory as contiguously as possible (given that you can have arrays of mixed objects), depending on the JavaScript engine you are considering.
As a side note, I am a bit baffled that the most voted answer keeps spreading the over-simplified myth (or half-truth) of "everything being an object in JavaScript"; that is not exactly true, otherwise you will never study primitives, for example.
Try to do this:
const pippi = "pippi"
pippi.cat = "cat"
console.log(pippi.cat) // Will it work? Throw an error? Guess why again
Spoiler: the string is wrapped in a throwaway object for that specific operation on the second line, and then in the following one you are just going to access a property of the primitive which is not there (provided you did not play with String.prototype or the like), so you get undefined.
Array object inherits caller, constructor, length, and name properties from Function.prototype.
A JavaScript array is an object just like any other object, but JavaScript gives it special syntax.
arr[5] = "yo"
The above is syntactic sugar for
arr.insert(5,"yo")
which is how you would add stuff to a regular object. It's what is inside the insert method that changes the value of arr.length
See my implementation of a customArray type here: http://jsfiddle.net/vfm3vkxy/4/

Categories