jshashtable states:
JavaScript's built-in objects do provide hashtable functionality using
the square brackets notation for
properties, provided your keys are
strings or numbers:
From what I know, keys are only strings, (since numbers are coerced into strings anyway). I just want to check and be sure that what is stated above is false (since keys can't be numbers).
Did ECMA standard stated anything about this..
Or is the implementation browser-specific?
JavaScript's built-in objects do provide hashtable functionality using
the square brackets notation for properties, provided your keys are
strings or numbers
That seems to be incorrect - object keys are always strings may be strings or (since ECMAScript 2015, aka ECMA-262 ed 6) symbols. But that is a different topic to square bracket property access.
See ECMA-262 ed 3 § 11.2.1 (Please also see ECMAScript 2017 (draft).):
Properties are accessed by name, using either the dot notation:
MemberExpression . IdentifierName
CallExpression . IdentifierName
or the bracket notation:
MemberExpression [ Expression ]
CallExpression [ Expression ]
The dot notation is explained by the following syntactic conversion:
MemberExpression . IdentifierName
is identical in its behaviour to
MemberExpression [ <identifier-name-string> ]
and similarly
CallExpression . IdentifierName
is identical in its behaviour to
CallExpression [ <identifier-name-string> ]
where <identifier-name-string> is a string literal containing the
same sequence of characters after processing of Unicode escape
sequences as the IdentifierName.
So when using dot notation, the bit after the dot must fit the criteria for an IdentifierName. But when using square brackets, an expression is provided that is evaluated and resolved to a string.
Briefly, square bracket notation is provided so that properties can be accessed using an expression, e.g.
var y = {};
var x = 'foo';
y[x] = 'foo value';
In the above, x is provided in square brackets so it is evaluated, returning the string 'foo'. Since this property doesn't exist on y yet, it is added. The foo property of y is then assigned a value of 'foo value'.
In general terms, the expression in the square brackets is evaluated and its toString() method called. It is that value that is used as the property name.
In the dot property access method, the identifier is not evaluated, so:
y.bar = 'bar value';
creates a property bar with a value bar value.
If you want to create a numeric property, then:
y[5] = 5;
will evaluate 5, see it's not a string, call (more or less) Number(5).toString() which returns the string 5, which is used for the property name. It is then assigned the value 5, which is a number.
Edit
This answer was written when ECMAScript ed3 was current, however things have moved on. Please see later references and MDN.
You're right keys can only be strings, and numeric keys such as those used in Arrays are coerced and stored as strings.
var arr = [true];
arr[0] === true;
arr['0'] = false;
arr[0] === false;
ECMAScript spec, page 42: ECMA-262 Script 3rd Edition.
The production PropertyName : NumericLiteral is evaluated as follows:
Form the value of the NumericLiteral.
Return ToString(Result(1)).
Well, here is my answer -- mostly because I was not satisfied with the references in the other (correct) answers -- expressions for property names in [ ] are always coereced to strings and this behavior is well defined in the specification. Thus, depending upon interpretation of the quote in question, it can be taken as misleading and/or incorrect.
However, the quote does not presume that x[42] and x["42"] are different; it states -- with the misleading exclusion of other primitives and details -- that only strings and numbers are usable as "hash keys" (really property names) under normal property resolution and, in this sense, the quote is arguably correct.
These rules are from Standard ECMA-262 ECMAScript Language Specification 5th edition (December 2009)
From section "11.2.1 Property Accessors" (production rules omitted):
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.
Happy coding.
The keys are always strings. This means you can't use an object instance's identity as a key.
In Flash's ActionScript 3 (uses strong run-time types unlike AS2) there is a Dictionary object which uses strict equality comparison for keys, so that you can use object instances themselves as keys (as well as numbers, strings, etc.).
If you wanted to do the same thing in JavaScript, it would be difficult, because you'd have to generate your own unique object ids and attach them to every object you wanted to track. Some have suggested adding a prototype function to the Object class, but that would add overhead to every object unnecessarily. In any case, you'd want to give an object a trackable ID via a function call that assigns an incrementing static number to a unique property such as "__objectid__".
It would then be conceivable to create a Dictionary-like class with methods like Add(key,value), but it would have to store strings, numbers, and objects in three separate internal hashes to ensure "3" doesn't collide with the number 3 or the object with id 3. The add method would have to automatically assigned an __objectid__ to any key of type object that didn't already have an id assigned. Even after all that, you wouldn't be able to access the dictionary using brackets, unless there are some hooks for property assignments that I'm not aware of in JavaScript.
Here is a functor Array. It is useful when using a Functional Programming paradigm.
javascript:
alert(["Using ",window.navigator.userAgent] );
FunctorRA=[]; f=function(){return f}; g=function(x){return x};
FunctorRA[f]=43;
FunctorRA[g(function(){})]="generic";
FunctorRA[g(g)]="idempotent";
alert(FunctorRA);
for (i in FunctorRA)
alert([ "Functor[ ", i,
"]'s\n\n value is \n\n", FunctorRA[i]].join(""));
displays:
Using ,Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100423
Ubuntu/10.04 (lucid) Firefox/3.6.3
an empty alert and then:
Functor[ function () {
return f;
}]'s
value is
43
etc.
Note Bene:
alert( FunctorRA ) shows .toString() does not enumerate non-numeric indexes
FunctorRA is a generic object in array's "clothing"
there is no direct . syntactic equivalent ( even with string eval coercion )
see Dynamic function name in javascript? for details on how :, a colon, can be embedded in a function name even though it is usually a delimiter to syntactically delineate initializer properties, labels, ?: (conditional expressions) etc. An analogous problem exists with . requiring the escaping of all syntactically significant JavaScript character codes such as (){}\n ., ... . The [] construct effectively does this for the parenthetically contained expression, as a whole, presumably by deferring the evaluation of the string to be an object of type String. This is similar to the fact 43.x=2 is verboten but not if 43 is represented as an object of type Number by (43).x=2 or 43["x"]=2.
(proof: javascript:alert( [ (43).x=2, 43["x"]=2 ] ) displays 2,2 but javascript:alert(43.x=2) generates an error).
Yes, keys can be numbers. In fact, the specification utilizes the same generic mapping functions for both Objects and Arrays.
Well everything that you use for the key is converted to string because of hashing function that hashes strings to a nearly unique short set of bytes that can be interpreted as an integer, cause integer comparison during the linear search is low level and fast.(JavaScript objects are hash tables). You stringify objects and arrays to use them as a key. Keys can be unlimited in size. But beware, cause javascript objects are unordered.
Yesterday and today, I've tried to wrap my head around the problems appearing in this domain and I've written these solutions.
First one is a custom implementation of a hash table, http://stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/
And there everything is explained in laymans terms...
other one is an upgrade to the first one, and it is a deterministic JSON stringify, that stringifies objects with alphabetically ordered properties: http://stamat.wordpress.com/2013/07/03/javascript-object-ordered-property-stringify/
Check it out :)
JavaScript's built-in objects do provide hashtable functionality ...
Yes, kind of. Objects are by definition just a collection of key-value pairs (whereas keys are strings or Symbols). However as they are used like lookup tables by a lot of people, most engines will choose a hashtable like datastructure for implementing objects internally (in some cases). So yes, you can use objects as a hashtable (but it is not guaranteed that the engine will actually use a hashtable under the hood).
your keys are strings or numbers ...
No. Numbers will be casted to string when used as object keys, just as any other value. Therefore you can use Objects as a hashtable for numbers, and for strings, but not for both at the same time:
{ "1": true }[1] // true
If you need a Hashtable for arbitrary / mixed values, a Map or a Set will be the better choice, especially because they guarantee O(1) lookup time.
Related
As I've tested, $('foo') is an instance of jQuery, and not an instance of Array. And AFAIK, javascript doesn't have operator overloading.
So how can $('foo')[0] return the corresponding HTMLElement if $('foo') is not an instance of Array?
I tried to look at jQuery's source code but it's a bit too much for me right now.
Thanks for any insight.
Because you can use brackets notation with any object in JavaScript, not just arrays. In fact, normal arrays aren't really arrays at all in JavaScript, they're just objects with a specific prototype and special treatment of a class of property names ("array indexes") and a special length property.
Brackets notation can be used to access any object property1 via its string name (and believe it or not, [0] is converted to ["0"] when accessing arrays, in theory).
Example:
var obj = {foo: "bar", 0: "zero"};
console.log(obj[0]); // zero
console.log(obj["0"]); // zero
console.log(obj.foo); // bar
console.log(obj["foo"]); // bar
var f = "f" + "o" + "o";
console.log(obj[f]); // bar
The object above has two properties of its own. Their names are "0" and "foo"; both names are strings (even though I wrote 0 as a numeric literal in the initializer; it gets coerced to string).
jQuery maintains its "array entry" properties to ensure that you can use brackets notation to access them. It also maintains a length property.
If jQuery were being written from scratch today, it probably would extend Array. But when jQuery was written, you couldn't extend Array. (It only became possible as of ES2015's ("ES6's") class feature.)
1 Except properties that don't have a string name; ES2015 introduced the concept of properties with names that are Symbols rather than strings. Prior to that, all object property names were strings (even array indexes).
I have the following code:
String.prototype.isLengthGreaterThan = function(limit){
return this.length > limit;
}
console.log("John".isLengthGreaterThan(3));
Number.prototype.isPositive = function(){
return this > 0;
}
console.log(5.isPositive(3)); //error
The 1st example work, the other doesn't. Why?
From my understanding, primitives are not objects – though they do have access to their function constructor's prototype (Number, String, etc.). So, you can add methods directly to the prototype. In my example above it didn't work in one instance.
I am looking for "under the hood" answer, to understand what happens when you do, for example:
var a = 1;
How does it really have an access to its prototype if it isn't an object?
The dot thing isn't to do with primitives, it's just the syntax of writing numeric literals. In a number, the . is a decimal point, whereas of course that's not the case for a string. To use a method on a literal number, you have two choices:
Parens:
console.log((5).isPositive(3));
Two dots (yes, really):
console.log(5..isPositive(3));
In the second case, it works because the first dot is a decimal point; this means that the dot following can't be a decimal point, and so it's the property accessor operator.
How does it really have an access to it's prototype if it isn't an object.
It's promoted to an object automatically by the JavaScript engine when you do the propety access. In the case of numbers, it's as though you called new Number(5) or similar. In the case of strings, it's as though you called new String("the string"). When the property access is complete, the temporary object is then discarded immediately when the expression is complete. Naturally, the JavaScript engine can optimize the object allocation out, but conceptually that's what happens.
So conceptually:
We do (say) var n = 1; The variable a contains the primitive number value 1.
We do n.isPositive(3); to create a string from it using Number.prototype.toFixed. That's a property access operation:
The engine evaluates the left-hand side, n, and gets the result 1 (a primitive number); this is the base it'll use for the property access operation.
The engine evaluates the right-hand side (isPositive) to determine the property key (name) to look up. In this case it's a literal, so the key is the string "isPositive". This is the property key for the property access operation.
The engine goes to look up the property on the base. Since the base we have is a primitive, the JavaScript engine promotes it (coerces it) to an equivalent Number object.
Since that object doesn't have a "isPositive" property, the engine looks at its prototype, Number.prototype, and finds it; it's a reference to a function.
Various things happen that aren't really germane, and ultimately isPositive is called with this being an object coerced from the primitive value 1. It does its work and generates its return value.
Since the temporary object isn't referenced by anything anymore, it's eligible for garbage collection.
The mechanism by which this happens is a bit scattered in the specification:
The runtime semantics of the property accessor operator return something called a Reference specification type, which basically is a purely abstract specification holding object that will get evaluated later. It says that the base will be the result of evaluating the left-hand side of the property accessor, and the property anme will be the result of evaluting the right-hand side.
The GetValue operation on the Reference type, which says (step 5) that if it's a property reference and the base is primitive, it's coerced via the specification's ToObject operation.
ToObject, which defines the rules for doing that.
Updated Question
What, exactly, qualifies as a valid property name in Javascript? How do various methods of property assignment differ? And how does the property name affect property access?
Note
The answers to my original question (seen below) helped to clear some things up, but also opened a new can of worms. Now that I've had a chance to become a bit more familiar with JavaScript, I believe I've been able to figure a lot of it out.
Since I had a hard time finding this information consolidated into one explanation, I thought it might be helpful to expand my original question, and attempt to answer it.
Original Question
Originally, there was some confusion with the MDN JavaScript guide (object literals). Specifically, I wondered why they claimed that if a property name was not a valid JavaScript identifier, then it would have to be enclosed in quotes. Yet, they offered example code that showed that the number 7 could be used — without quotes — as a property name.
As it turns out, the guide simply left off one important part, and Pointy updated it (changes in bold):
If the property name would not be a valid JavaScript identifier or number, it must be enclosed in quotes.
I also wondered why property names were allowed to deviate away from the "may not start with a digit" rule, that applies to identifiers. That question actually reveals the complete misunderstanding that I had of property names, and is what lead me to do some more research.
Answer for 1st question:
Yes, the statement given in the MDN guide is not 100% accurate, but in your daily work it'd be better to follow it as rule. You really don't need to create properties names which are numbers.
Answer for 2nd question:
A property name may not start with a digit but a property name that is a number without any other characters in its name is fine.
This exception exists because the properties with number for name as the same as indexes.
Let's try this:
var obj = {7: "abc"};
obj[7]; // works fine
obj.7; // gives an error (SyntaxError)
Now try to call Array.push on the object and observe what happens:
Array.prototype.push.call(obj, "xyz");
console.log(obj);
console.log(obj[0]);
// Prints
Object {0: "xyz", 7: "abc", length: 1}
"xyz"
You can see that few new properties (one with name 0 and another with name length) have been added to the object. Moreover, you can use the object as an array:
var obj = { "0": "abc", "1": "xyz", length: 2 };
Array.prototype.pop.call(obj); // Returns: "xyz"
Array.prototype.pop.call(obj); // Returns: "abc"
You can use array's methods on objects and this is called Duck Typing.
Arrays are nothing more than objects with some predefined methods.
From MDN:
Array elements are object properties in the same way that length is a property, but trying to access an element of an array with dot notation throws a syntax error, because the property name is not valid. There is nothing special about JavaScript arrays and the properties that cause this. JavaScript properties that begin with a digit cannot be referenced with dot notation and must be accessed using bracket notation.
Now you can understand why a number for property name is valid. These are called just indexes and they are used in JavaScript arrays. And since JavaScript needs to be consistent with other languages, numbers are valid for indexes/properties names.
Hope this makes it clear.
Here are some interesting articles:
JavaScript identifiers (in ECMAScript 5)
JavaScript identifiers (in ECMAScript 6)
Short Answer
Object property names can be any valid identifier, numeric literal, or string literal (including the empty string).
With that said, there are some potentially confusing intricacies to keep in mind about JavaScript property names, as described below.
And unless you're working with valid (non-negative integer) array indexes, it's a good idea to explicitly assign all numerical property names as strings.
Negative Numbers
What might look like a negative number is actually an expression — something property names do not support.
// SyntaxError
const obj = { -12: 'nope' };
Fortunately, bracket notation handles expressions for us.
// Successful property assignment.
const obj = {};
obj[-12] = 'yup';
Typecasting
All property names are typecasted into strings before being stored.
const obj = {
12: '12'
};
console.log(typeof Object.keys(obj)[0]); // -> string
Parsing
But even before typecasting occurs, keys are parsed according to the syntax used, and transformed into a decimal literal.
const obj = {
// Valid string literal
'022': '022',
// Interpreted as decimal
6: '6',
// Interpreted as floating-point
.345: '0.345',
// Interpreted as floating-point
1.000: '1',
// Interpreted as floating-point
8.9890: '8.989',
// Interpreted as decimal
000888: '888',
// Interpreted as octal
0777: '511',
// Interpreted as hexadecimal
0x00111: '273',
// Interpreted as binary
0b0011: '3',
};
/* Quoted property name */
console.log(obj['022']); // "022"; as expected
console.log(obj[022]); // undefined; 022 is an octal literal that evaluates to 18 before our lookup ever occurs
/* Valid (non-negative integer) array index */
console.log(obj[6]); // "6"; as expected
console.log(obj['6']); // "6"; as expected
/* Non-valid array index */
console.log(obj[0x00111]); // "273"; we're accessing the property name as it was assigned (before it was parsed and typecasted)
console.log(obj['0x00111']); // undefined; after parsing and typecasting, our property name seems to have disappeared
console.log(obj['273']); // "273"; there it is, we found it using the evaluation of our original assignment
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.
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/