I'm writing a JavaScript interpreter for extremely resource-constrained embedded devices (http://www.espruino.com), and every time I think I have implemented some bit of JavaScript correctly I realise I am wrong.
My question now is about []. How would you implement one of the most basic bits of JavaScript correctly?
I've looked through the JavaScript spec and maybe I haven't found the right bit, but I can't find a useful answer.
I had previously assumed that you effectively had two 'maps' - one for integers, and one for strings. And the array length was the value of the highest integer plus one. However this seems wrong, according to jsconsole on chrome:
var a = [];
a[5] = 42;
a["5"]; // 42
a.length; // 6
but also:
var a = [];
a["5"] = 42;
a[5]; // 42
a.length; // 6
So... great - everything is converted into a string, and the highest valued string that represents an integer is used (plus one) to get the length? Wrong.
var a = [];
a["05"] = 42;
a.length; // 0
"05" is a valid integer - even in Octal. So why does it not affect the length?
Do you have to convert the string to an integer, and then check that when converted back to a string, it matches?
Does anyone have a reference to the exact algorithm used to store and get items in an array or object? It seems like it should be very simple, but it looks like it actually isn't!
As the specs said, and was noted by others:
"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."
That's explain why in your scenario "5" is considered an array index and "05" is not:
console.log("5" === String("5" >>> 0));
// true, "5" is equal to "5", so it's an index
console.log("05" === String("05" >>> 0));
// false, "05" is not equal to "5", so it's not an index
Note: the Zero-fill right shift is the shortest way in JS to have a substitute of ToUint32, shifting a number by zero.
See MDN
It's possible to quote the JavaScript array indexes as well (e.g.,
years["2"] instead of years[2]), although it's not necessary. The 2 in
years[2] eventually gets coerced into a string by the JavaScript
engine, anyway, through an implicit toString conversion. It is for
this reason that "2" and "02" would refer to two different slots on
the years object and the following example logs true:
console.log(years["2"] != years["02"]);
So with a["5"] you are accessing the array while a["05"] sets a property on the array object.
Arrays are just objects. That means they can have additional properties which are not considered elements of the array.
If the square bracket argument is an integer, it uses it to perform an assignment to the array. Otherwise, it treats it as a string and stores it as a property on the array object.
Edit based on delnan's comment and DCoder's comment, this is how JavaScript determines if it is an appropriate index for an array (versus just a property):
http://www.ecma-international.org/ecma-262/5.1/#sec-15.4
Arrays are also objects.
By doing this
a["05"] = 5;
You are doing the same thing as:
a.05 = 5;
However, the above will result in a syntax error, as a property specified after a dot cannot start with a number.
So if you do this:
a = [];
a["05"] = 5;
you still have an empty array, but the property of a named 05 has the value 5.
The number x is an array index if and only if ToString(ToUint32(x)) is equal to x (so in case of "05" that requirement is not met).
Related
This question already has answers here:
Is a JavaScript array index a string or an integer?
(5 answers)
Closed 4 years ago.
In Javascript it is possible to use a number (integer) or a string (or a char) to index an array for example:
array[0] = true;
or
array['0'] = true;
Does the computer memory work different depending on how you index the array or is it the exact same thing to do it both ways?
The indexes are stored internally as strings.
But it's more common practise to use numbers to access an array by it's index.
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^³²−1
It's explained in this old post
Below is a snippet that showcases it.
Accessing the index with a string works fine as long that index string only contains digits.
But by expressing the index as a word then it can only be accessed with that word. And console logging the array doesn't show it.
let arr = [0,'a one','a two','a three'];
arr['four'] = 'a four';
arr.push('5');
arr.push(6);
arr.push([7,'7']);
arr.push({a:8, b:'8'});
console.log('\nLog the array:\n\n');
console.log(arr);
console.log('\nAccessing the array:\n\n');
console.log("arr[0]:\t" + arr[0]);
console.log("arr[\'1\']:\t" + arr['1']);
console.log("arr[\'two\']:\t" + arr['two']);
let i=2;
console.log("arr[++i]:\t" + arr[++i]);
console.log("arr[\'four\']:\t" + arr['four']);
console.log('arr[4]:\t'+ arr[4]);
console.log('arr[5]:\t'+ arr[5]);
console.log('\nListing the types in the array:\n\n');
for (var a in arr) console.log(a+'\tindex type: '+ typeof a +', value: '+ arr[a] + ', value type: '+ typeof arr[a]);
And here's a snippet to compare speed between using a number or a string in your browser. It should take roughly the same time.
let arr1 = [];
console.time('Access array through string');
for(let i = 0; i <= 42000000;i++){
arr1['42000000'] = 42;
}
console.timeEnd('Access array through string');
let arr2 = [];
console.time('Access array through integer');
for(let i = 0; i <= 42000000;i++){
arr2[42000000] = 42;
}
console.timeEnd('Access array through integer');
The answer is simple: there is no difference.
Javascript arrays are objects. All keys of objects are strings (or symbols), but never numbers.
Property names must be strings. This means that non-string objects
cannot be used as keys in the object. Any non-string object, including
a number, is typecasted into a string via the toString method. ...see more here
The property accessor [] converts to string first before looking for the property. (some engines may optimize this step and not perform a proper toString call, but it's of no concern here)
So array[0] is interpreted as array['0'].
const a = {
toString: function () {
console.log("a.toString called")
return "1";
}
};
const array = ['a','b','c'];
console.log(array[a]);
When you use quotations for indexing, you're creating a key-value pair in the array. I recommended you stick to numerical notation unless you're intentionally creating those pairs; while array['0'] technically posts to the position, it's bad practice.
Even though myArr['two'] doesn't seem to want to show up in the snippet output, if you hit F12 and look at the console output, you'll clearly see it displayed.
["String 0", "String 1", two: "String 2"]
let myArr = [];
myArr[0] = "String 0";
myArr['1'] = "String 1";
myArr['two'] = "String 2";
console.log(myArr);
As the mdn docs state, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
Arrays are actually just objects that have array-like properties. Keys in JavaScript are strings. So when you see an array, it's actually just a set of key-value pairs, where the keys are index numbers.
Arrays are a special data type. Adding keys that do not correlate to possible indexes are added to an 'object property collection' and are not returned when you use the object.
I think the chrome console does the best job of displaying this:
This array is actually an object with keys of 0 1 2 3, a length property, and a prototype.
I am using the following pattern to index an injection from pairs of numbers to numbers:
var myHash = {};
...
for (... billion of iterations ...)
var x = someNum;
var y = otherNum;
myHash[x + "," + y] = z;
The problem with this code is that I'm using a string as the key of myHash, which has been tested to be much slower than integer keys. My question is: what is a more intelligent way to combine 2 numbers before using them as keys of an object? I.E., how to combine 2 doubles into an unique Integer?
There is the definition of an array in JavaScript:
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.
In other words, if the index you specify is a number representing an integer between 0 and 0xFFFFFFFE, then it is used as an array index. Any other value is taken as a string and it is used to create an object member instead of an array item.
So if you have constraints on your indices which would fit the valid range (0 to 0xFFFFFFFE) then you're good. Otherwise, what you have is probably the fastest.
So the following represents string indices which are members of object myHash:
myHash[x + "," + y] = z;
Someone mentioned using an array of arrays. That would not help you. You'd get many arrays instead of many strings. It would probably be about the same if not slower. The idea is something like this:
myHash[x] = []; // initialize the sub-array (must be done only once per value of 'x'
myHash[x][y] = z; // save z in that array
I do not recommend the double array because it will initialize one array for each value of 'x' on top of myHash and that probably not any faster than having the string concatenation (especially because you'll have to test whether the myHash[x] array was already defined or not...).
So... it is possible to write:
myHash[3.3] = "that worked?";
But if after that you check out the length, you'll notice it is zero:
console.log("Hash length = " + myHash.length);
This is because 3.3 is not an integer.
I have a scenario like this------
right = some_function (some_value)
var idR =right.split('~')[0];
var r =right.split('~')[1];
left = another-func (some_value)
var idL = left.split('~')[0];
var l = left.split('~')[1];
point to be noted that r,l contains numeric values.
My purpose is that I want to take the maximum between r and l, and after that I will traverse back to 'right' or 'left' according to maximum value of r or l.
I am using math.max() but this is not working as it only returns the value, not the name of variable.
How can I achieve my goal???
Let me provide some sample inputs and outputs--
"some_function" returns a string in the format "id~value" where "id" can be "1-1", "2-2" and so on and "value" can be any numeric value. I need to look at the greater "value" and fetch its corresponding "id". Note that "another_func" returns values in same pattern
MY CODE IS NOT THESE TWO BLOCKS ONLY, I HAVE EIGHT FUNCTIONS LIKE THESE AND I HAVE TO TAKE THE BIGGEST NUMBER AND ITS CORRESPONDING ID
Your question is not very clear.
Let's try this, so you have 2 arrays with values r and l and 2 arrays with variable names idR and adL.
Now this is what's not clear, do those arrays containing variable names have the same order? the same values? can this condition be trusted?
You clearly want to get some max value from the values arrays that's ok, but you aren't explaining how the other arrays fit on the picture :) After you do, probably me or someone else can help you :)
Can anyone explain why the second alert says 0 ?
var pollData = new Array();
pollData['pollType'] = 2;
alert(pollData['pollType']); // This prints 2
alert(pollData.length); // This prints 0 ??
The length of the array is only changed when you add numeric indexes. For example,
pollData["randomString"] = 23;
has no effect on length, but
var pollData = [];
pollData["45"] = "Hello";
pollData.length; // 46
changes the length to 46. Note that it doesn't matter if the key was a number or a string, as long as it is a numeric integer.
Besides, you are not supposed to use arrays in this manner. Consider it more of a side effect, since arrays are objects too, and in JavaScript any object can hold arbitrary keys as strings.
Because you haven't put anything into the array yet. You've only been assigning to a dynamically-created pollType attribute on the array object.
If you use numeric indices, then the array automagically takes care of length. For example:
var arr = [ ]; // same as new Array()
arr[2] = 'Banana!';
alert(arr.length); // prints 3 (indexes 0 through 2 were created)
The length property takes into consideration only those members of the array which names are indexes (like '1', '2', '3', ... ).
Arrays in JavaScript have numeric indexes only.
Use an object, which is essentially what you are doing above, setting properties on that array object.
array.length returns how many values are stored in the array. The first alert is returning the value of the position 'pollType'.
The reference guide I always use when needing help with javascript arrays is this page http://www.hunlock.com/blogs/Mastering_Javascript_Arrays
I'd also read what it says under the heading Javascript Does Not Support Associative Arrays, as you may run into problems with this also.
var pollData = Array();
function test() {
pollData[0] = 2
alert(pollData[0]);
alert(pollData.length);
}
//[x] is the array position; hence ['polltype'] is causing issues
When I do something like this:
var o = new Array();
o[20] = true;
o[1000] = true;
o[4000] = true;
Is it reasonable to expect that only 3 elements will be allocated or can the implementation decide to suddenly allocate something with 4000 elements?
The reason I'm asking is that when I do this I see in firebug an indication that there are actually 4000 undefined in o. are they really there?
Now that we know that o is an Array, I can answer you precisely.
No, the elements will not be 'allocated' or 'created'.
When you make an assignment of an index property to an Array object, which is greater than the actual length of the array two things happen:
The index named property is created
The length property is incremented, to be the index + 1
For example:
var o = [];
o[4000] = true;
o.hasOwnProperty(0); // false, the property doesn't exist
o.hasOwnProperty(1); // false
o.hasOwnProperty(4000); // true, the property exist
As you can see, the hasOwnProperty method returns false when we test the presence of the 0 or 1 properties, because they don't exist physically on the object, whereas it returns true for 4000, the property that was created.
When Firebug detects that the object being printed in the console is an array-like object, it will simply make a loop, showing each of the index values from 0 to length - 1.
Firebug detects array-like objects simply by looking if they have a length property whose its value is an unsigned 32-bit integer (less than 2^32 - 1), and if they have a splice property that is a function, for example, the following object will be detected and printed as an Array on the Firebug's console:
console.log({length:3, splice:function(){}});
// Firebug will log: `[undefined, undefined, undefined]`
a) That code is not valid. You need either var o = {}; // object or var o = []; // array.
b) In the first case, the object is sparse. In the second, it may depend on the interpreter; in modern browsers it is also sparse. Try o[99999999] = "hi"; and see how much memory your browser does or does not allocate. If it does not go up by at least 10MB, your arrays are sparse.
I think this one answers the question.
Are Javascript arrays sparse?
And according to that one, arrays are spares, thats is, if you use for(item in array) you only get 3 items, not 4000 but if you use array.length it will take the larges integer value and return one larger, look here:
http://www.crockford.com/javascript/survey.html
Linkpad will use a for(item = 0; item < array.length; item++) and that one will return undefined for any index that is not present in the array.
/*
On the other hand, if you have a very large index any array manipulation will have to loop
through each index- it won't skip from one defined item to the the next defined item.
*/
var A= [];
A[0]= 'a';
A[10]= 'b';
A[4000000]= 'c';
alert(A.filter(function(itm){
return itm!= undefined;
}));
Javascript will generate all elements inbetween, always. You may want to use o.length to verify the length of the array. It will return 4000 and not 3.