In JavaScript, values of objects and arrays can be indexed like the following: objOrArray[index]. Is there an identity "index" value?
In other words:
Is there a value of x that makes the following always true?
let a = [1, 2, 3, 4];
/* Is this true? */ a[x] == a
let b = { a: 1, b: 2, c: 3 };
/* Is this true? */ b[x] == b
Definition of an identity in this context: https://en.wikipedia.org/wiki/Identity_function
There is no such thing built-in, as there is rarely a need for it (and sometimes even a need against it).0 It is nevertheless possible to roll your own ‘identity’ key:
const self = Symbol('self');
Object.defineProperty(Object.prototype, self, {
enumerable: false,
get() { "use strict"; return this; }
});
This will work on all primitives (other that null and undefined) and most JavaScript objects: that is, other than proxies or those that bypass the usual prototype chain by means of e.g. Object.create(null). Any object later in the prototype chain will also be able to disable the functionality, e.g. by doing { [self]: void 0 }; all these caveats mean that x[self] === x is by no means a universal law. But this is probably the best you can do.
Modifying Object.prototype is usually considered a bad idea, but the above manages to avoid most of its badness: adding the property at a symbol key (and making it explicitly non-enumerable as well) prevents it from unexpectedly showing up in iterations and lookups that walk the prototype chain, helping ensure no code should be impacted that does not specifically look for this property.
0 Even if such a feature existed, it would not be a good solution for the asker’s original use case: a ‘cut to 50 characters or take the whole string if shorter’ operation can be expressed as s.description.substring(0, s.description.length > 50 ? 50 : void 0) (or in fact just s.description.substring(0, 50)). It wouldn’t be any easier to express even with such a feature: depending on the condition, you still need to invoke the substring method, not just look it up, but not invoke the ‘self’ non-method. And given that you need to append an ellipsis at the end in the former case, you would still have to perform the condition check outside the substring call, making any shorthand rather ineffective. All that said, tricks like described in this answer do find some real use.
The indexing operation doesn't have an identity element. The domain and range of indexing is not necessarily the same -- the domain is arrays and objects, but the range is any type of object, since array elements and object properties can hold any type. If you have an array of integers, the domain is Array, while the range is Integer, so it's not possible for there to be an identity. a[x] will always be an integer, which can never be equal to the array itself.
And even if you have an array of arrays, there's no reason to expect any of the elements to be a reference to the array itself. It's possible to create self-referential arrays like this, but most are not. And even if it is, the self-reference could be in any index, so there's no unique identity value.
Related
In my serialization code, I stumbled across a a stinky issue - as I loop through generic object properties, it also serializes array indexes, which is really not the plan - I serialize this data later on without saving the indexes in the stream.
[1].hasOwnProperty("0") // true
So my question is, why are array indexes considered own properties by the hasOwnProperty method? Is there even a way to tell property from array offset? A generic way that also works for TypedArray, HTMLElementCollection and whatever else?
Of course, this can be done, but it stinks:
for(var i in this) {
if(this.hasOwnProperty(i) &&
// If object is an array, we ignore the number offsets as they're not meant to be object properties
(typeof this.length!="number" || !(i<this.length) || i.length==0)) {
And yeah, the i.length==0 is there because you can actually do this:
var obj = {};
obj[""] = "something";
console.log(obj);
Yeah, you're welcome, enjoy your nightmares.
Arrays are objects, just slightly specialised. And as you have discovered, the indexes of an array are just properties called 0, 1, 2 etc.
On a really simple level, the length property just finds the highest numeric property and adds one.
You could make a slightly simpler way of filtering the keys, along the lines of
for (key in obj) {
if (isNaN(+key) && obj.hasOwnProperty(key)) {
doSomething()
}
}
Depends if you want to include the numeric properties of objects. It would be perfectly valid to do a = {'0': 'value'}, which is for the purpose of this exercise the same as b = ['value']. Although b has a length property and a does not, also b has all the other functions that come from being an array.
I've had a bit of a wakeup to the nature of JavaScript array indexes recently. Pursuing it, I found the following (I'm working with Node.js in interpretive mode here):
var x=[];
x['a']='a';
console.log(x); // Yields [ a: 'a' ]
console.log(x.length); // yields 0 not 1
x[1]=1;
console.log(x); // Yields [ , 1, a: 'a' ]
console.log(x.length); // Yields 2 not 3 (one for empty 0 space, one for the occupied 1 space)
Is a: 'a' really what it looks like - an object property embedded in an array - and, thus, isn't counted in the array property .length?
In JavaScript, arrays are just objects with some special properties, such as an automatic length property, and some methods attached (such as sort, pop, join, etc.). Indeed, a will not be counted in your array, since the length property of an array only stores the amount of elements with a property name that can be represented with a 32-bit positive integer.
And since arrays always automatically define every numbered element up to the highest element with a positive 32-bit int property name, this effectively means the length property stores 1 higher than the element with the highest 32-bit integer as a property name. Thanks #Felix Kling for correcting me about this in the comments.
Adding properties such as a is not forbidden at all, but you should watch out with them, since it might be confusing when reading your code.
There's also a difference in walking through the elements in the array:
To walk through all the numbered elements:
for (var i=0; i<myArray.length; i++) {
//do something
}
To walk through every property that's not built-in:
for (var i in myArray) {
//do something
}
Note that this loop will also include anything that's included from Array.prototype that's not a built-in method. So, if you were to add Array.prototype.sum = function() {/*...*/};, it will also be looped through.
To find out if the object you're using is indeed an array, and not just an object, you could perform the following test:
if (Object.prototype.toString.call(myObject) === '[object Array]') {
//myObject is an array
} else if (typeof myObject === 'object') {
//myObject is some other kind of object
}
See #artem's comment: myObject instanceof Array might not always work correctly.
That's correct, and it's a good illustration of the fact that that new Array you've created is really just a special kind of Object. In fact, typeof [] is 'object'! Setting a named property on it (which might be written more cleanly here as x.a = 'a') is really setting a new property to the object wrapper around your "real" array (numbered properties, really). They don't affect the length property for the same reason that the Array.isArray method doesn't.
Yes, that's correct. This works because pretty much everything in JavaScript is an object, including arrays and functions, which means that you can add your own arbitrary string properties. That's not you say that you should do this.
Arrays ([]) should be indexed using nonnegative integers. Objects ({}) should be "indexed" using strings.
{"/book":1,"/order":2,"deliver":3}
In the UI of my app, When I click on /book, I know from the map, what step to go to.
At times I just want to go to the next step by incrementing the number but making sure URL also changes.
How do I do a reverse mapping from step to key.
I came across Object.keys introduced in ECMAScript 5.
Is the order of element in the returned list of keys always same ?
["/book","/order","deliver"]
If yes then why so ? Dictionaries are unordered right ?
The ECMAScript 5.1 specification states that
When the keys function is called with argument O, the following steps
are taken:
If the Type(O) is not Object, throw a TypeError exception.
Let n be the number of own enumerable properties of O
Let array be the result of creating a new Object as if by the expression new Array(n) where Array is the standard built-in
constructor with that name.
Let index be 0.
For each own enumerable property of O whose name String is P:
(a) Call the [[DefineOwnProperty]] internal method of array with arguments ToString(index), the PropertyDescriptor {[[Value]]: P,
[[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and
false.
(b) Increment index by 1.
Return array.
If an implementation defines a specific order of enumeration for the
for-in statement, that same enumeration order must be used in step 5
of this algorithm.
The Mozilla Development Network has this to say about the for-in loop:
A for...in loop iterates over the properties of an object in an arbitrary order (see the delete operator for more on why one cannot depend on the seeming orderliness of iteration, at least in a cross-browser setting).
And the reference to the delete operator leads to this:
Although ECMAScript makes iteration order of objects implementation-dependent, it may appear that all major browsers support an iteration order based on the earliest added property coming first (at least for properties not on the prototype). However, in the case of Internet Explorer, when one uses delete on a property, some confusing behavior results, preventing other browsers from using simple objects like object literals as ordered associative arrays. In Explorer, while the property value is indeed set to undefined, if one later adds back a property with the same name, the property will be iterated in its old position--not at the end of the iteration sequence as one might expect after having deleted the property and then added it back.
So if you want to simulate an ordered associative array in a cross-browser environment, you are forced to either use two separate arrays (one for the keys and the other for the values), or build an array of single-property objects, etc.
I think the conclusion to draw from this is that, within a single browser, the order will be arbitrary but consistent but if you compare multiple browsers then the order may differ across those browsers (especially if you are deleting and re-adding properties).
Edit:
If you want to sort the keys based on the associated value then you can do something like this:
var map = { b: 2, a: 1, c: 3 };
var keys = Object.keys( map );
console.log( keys ); // [ 'b', 'a', 'c' ]
var sorted_keys = keys.slice(0); // sliced so that we can see the difference in order
sorted_keys.sort( function( a, b ){ return map[a] - map[b]; } );
console.log( sorted_keys ); // [ 'a', 'b', 'c' ]
It is up to the implementation. From the ES5 spec on Object.keys:
If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used in step 5 of this algorithm.
The "step 5" mentioned there does not specify an order:
5. For each own enumerable property of O whose name String is P...
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Are Javascript arrays sparse?
I am learning JavaScript at the moment and have been reading some simple introductions and tutorials. While looking at the Array object I stumbled upon some details, which strike me as very odd, coming from other languages like C/Java/Scala/...
So lets assume we define an array as such:
var arr = ['foo','bar','qux']
We now assign
arr[5] = 'baz'
which results in our array looking like this:
arr
>> ["foo", "bar", "qux", undefined, undefined, "baz"]
And the length is as expected
arr.length
>> 6
JavaScript has kindly expanded our array to the needed length - six - and the new items are set to undefined - except for the one we actually assigned a value to.
From a low level point of view this is horrible memory-wise. Typically an array would be a continuous range in memory - making an array bigger generally involves copying the whole array to a new memory location, sufficient in size. This is a very costly operation.
Now, I do realize that this is likely not what JavaScript engines are doing, as copying around arrays would be crazy expensive and the memory space would be wasted on all these 'undefined' values.
Can someone tell me what actually happens behind the door?
Are arrays actually some sort linked lists?
Are the 'undefined' array items actually there?
How expensive is it to work with large arrays that are mostly filled with 'undefined'?
In the first version of JavaScript, there were no arrays. They were later introduced as a sub-class of that "mother of all objects": Object. You can test this quite easily by doing this:
var foo = [1,2,3,4];
for (var n in foo)
{//check if n is equal (value and type) to itself, coerced to a number
console.log(n === +(n) ? 'Number' : 'String');
}
This will log String, time and time again. Internally, all numeric keys are converted to strings. The Length property merely fetches the highest index, and adds 1 to it. Nothing more. When you display your array, the object is iterated, and for each key, the same rules apply as for any object: first the instance is scanned, then the prototype(s)... so if we alter our code a bit:
var foo = [1,2,3,4];
foo[9] = 5;
for (var n in foo)
{
if (foo.hasOwnProperty(n))
{//check if current key is an array property
console.log(n === +(n) ? 'Number' : 'String');
}
}
You'll notice the array only has 5 own properties, the undefined keys 4-8 are undefined, because there was no corresponding value found within the instance, nor in any of the underlying prototypes. In short: Arrays aren't really arrays, but objects that behave similarly.
As Tim remarked, you can have an array instance with an undefined property that does exist within that object:
var foo = [1,2,undefined,3];
console.log(foo[2] === undefined);//true
console.log(foo[99] === undefined);//true
But again, there is a difference:
console.log((foo.hasOwnProperty('2') && foo[2] === undefined));//true
console.log((foo.hasOwnProperty('99') && foo[99] === undefined));//false
RECAP, your three main questions:
Arrays are objects, that allow you to reference their properties with numeric instances
The undefined values are not there, they're merely the default return value when JS scans an object and the prototypes and can't find what you're looking for: "Sorry, what you ask me is undefined in my book." is what it says.
Working with largely undefined arrays doesn't affect the size of the object itself, but accessing an undefined key might be very, very marginally slower, because the prototypes have to be scanned, too.
Update:
Just quoting the Ecma std:
15.4 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
2^32
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 2^32. 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:
1. Let len be the result of calling the [[Get]] internal method of O with argument "length".
2. For each integer i in the range 0≤i
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.
3. Return false.
Arrays are just an ordered list of objects. In JavaScript everything is an object, so arrays are not really arrays as we know them :)
You can find little internals here.
For your doubts about working with large arrays... Well, remember that the less calculation you make "client-side", the faster will be your page.
Answers:
An array in JavaScript is just the same as an object (i.e. an unordered collection of properties) with a magic length property and extra prototype methods (push() etc.)
No, the undefined items are not there. JavaScript has an in operator that test for the existence of a property that you can use to prove this. So for the following array: var arr = ['foo']; arr[2] = 'bar';, 2 in arr returns true and 1 in arr returns false.
A sparse array should take up no more memory than a dense array whose length is the number of properties actually defined in your sparse array. It will only be more expensive to work with a sparse array when you iterate over its undefined properties.
Most javascript implementations implement arrays as some flavor of binary tree or hash table with the array index as the key, so a large range of undefined objects does not use up any memory.
I was told that the arrays come in 2 parts, [value, pointer]. So the pointer of arr[2] is null. When you add a 5 it changes the address from null to point to number 3, which points to number 4, which points to number 5, which is null (so end of array).
Im not sure how true this is as ive never actually checked it. But it seems to make sense.
So you cant do the maths like on a c type array (ie to get to value 4 just do starting memory point + 4x (object amount in memory)) but you can do it by following the array peice by peice
I'm writing a Google Chrome extension, in JavaScript, and I want to use an array to store a bunch of objects, but I want the indexes to be specific non-consecutive ID numbers.
(This is because I need to be able to efficiently look up the values later, using an ID number that comes from another source outside my control.)
For example:
var myObjects = [] ;
myObjects[471] = {foo: "bar"} ;
myObjects[3119] = {hello: "goodbye"}
When I do console.log(myObjects), in the console I see the entire array printed out, with all the thousands of 'missing' indexes showing undefined.
My question is: does this matter? Is this wasting any memory?
And even if it's not wasting memory, surely whenever I loop over the array, it wastes CPU if I have to manually skip over every missing value?
I tried using an object instead of an array, but it seems you can't use numbers as object keys. I'm hoping there's a better way to achieve this?
First of all, everyone, please learn that what the for-in statement does is called enumeration (though it's an IterationStatement) in order to differentiate from iteration. This is very important, because it leads to confusion especially among beginners.
To answer the OP's question: It doesn't take up more space (test) (you could say it's implementation dependent, but we're talking about a Google Chrome Extension!), and it isn't slower either (test).
Yet my advice is: Use what's appropriate!
In this situation: use objects!
What you want to do with them is clearly a hashing mechanism, keys are converted to strings anyway so you can safely use object for this task.
I won't show you a lot of code, other answers do it already, I've just wanted to make things clear.
// keys are converted to strings
// (numbers can be used safely)
var obj = {}
obj[1] = "value"
alert(obj[1]) // "value"
alert(obj["1"]) // "value"
Note on sparse arrays
The main reason why a sparse array will NOT waste any space is because the specification doesn't say so. There is no point where it would require property accessors to check if the internal [[Class]] property is an "Array", and then create every element from 0 < i < len to be the value undefined etc. They just happen to be undefined when the toString method is iterating over the array. It basically means they are not there.
11.2.1 Property Accessors
The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:
Let baseReference be the result of evaluating MemberExpression.
Let baseValue be GetValue(baseReference).
Let propertyNameReference be the result of evaluating Expression.
Let propertyNameValue be GetValue(propertyNameReference).
Call CheckObjectCoercible(baseValue).
Let propertyNameString be ToString(propertyNameValue).
If the syntactic production that is being evaluated is contained in strict mode code, let strict be true, else let strict be false.
Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
The production CallExpression : CallExpression [ Expression ] is evaluated in exactly the same manner, except that the contained CallExpression is evaluated in step 1.
ECMA-262 5th Edition (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
You can simply use an object instead here, having keys as integers, like this:
var myObjects = {};
myObjects[471] = {foo: "bar"};
myObjects[3119] = {hello: "goodbye"};
This allows you to store anything on the object, functions, etc. To enumerate (since it's an object now) over it you'll want a different syntax though, a for...in loop, like this:
for(var key in myObjects) {
if(myObjects.hasOwnProperty(key)) {
console.log("key: " + key, myObjects[key]);
}
}
For your other specific questions:
My question is: does this matter? Is this wasting any memory?
Yes, it wastes a bit of memory for the allocation (more-so for iterating over it) - not much though, does it matter...that depends on how spaced out the keys are.
And even if it's not wasting memory, surely whenever I loop over the array, it wastes CPU if I have to manually skip over every missing value?
Yup, extra cycles are used here.
I tried using an object instead of an array, but it seems you can't use numbers as object keys. I'm hoping there's a better way to achieve this?
Sure you can!, see above.
I would use an object to store these. You can use numbers for properties using subscript notation but you can't using dot notation; the object passed as the key using subscript notation has toString() called on it.
var obj = {};
obj[471] = {foo: "bar"} ;
As I understand it from my reading of Crockford's "The Good Parts," this does not particularly waste memory, since javascript arrays are more like a special kind of key value collection than an actual array. The array's length is defined not as the number of addresses in the actual array, but as the highest-numbered index in the array.
But you are right that iterating through all possible values until you get to the array's length. Better to do as you mention, and use an object with numeric keys. This is possible by using the syntax myObj['x']=y where x is the symbol for some integer. e.g. myObj['5']=poodles Basically, convert your index to a string and you're fine to use it as an object key.
It would be implementation dependent, but I don't think you need to worry about wasted memory for the "in between" indices. The developer tools don't represent how the data is necessarily stored.
Regarding iterating over them, yes, you would be iterating over everything in between when using a for loop.
If the sequential order isn't important, then definitely use a plain Object instead of an Array. And yes, you can use numeric names for the properties.
var myObjects = {} ;
myObjects["471"] = {foo: "bar"} ;
myObjects["3119"] = {hello: "goodbye"};
Here I used Strings for the names since you said you were having trouble with the numbers. They ultimately end up represented as strings when you loop anyway.
Now you'll use a for-in statement to iterate over the set, and you'll only get the properties you've defined.
EDIT:
With regard to console.log() displaying indices that shouldn't be there, here's an example of how easy it is to trick the developer tools into thinking you have an Array.
var someObj = {};
someObj.length = 11;
someObj.splice = function(){};
someObj[10] = 'val';
console.log(someObj);
Clearly this is an Object, but Firebug and the Chrome dev tools will display it as an Array with 11 members.
[undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, "val"]
So you can see that the console doesn't necessarily reflect how/what data is actually stored.
I would simply use a constant prefix, to avoid such problems.
var myObjects = {};
myObjects['objectId_'+365] = {test: 3};
will default to Js-Objects.
You could attempt to do something like this to make it loud and clear to the JIST compiler that this is a more objecty-ish array like so:
window.SparseArray = function(len){
if (typeof len === 'number')
if (0 <= len && len <= (-1>>>0))
this.length = len;
else
new Array(len); // throws an Invalid array length type error
else
this.push.apply(this, arguments);
}
window.SparseArray.prototype = Array.prototype