Attending to it's specification, JSON elements (and javascript objects) are unordered so, even in almost all cases, when you iterate over a javascript object, you get elements in the same order they was defined; you definitively cannot trust in that order because engine is allowed to alter it.
This is extremely rare. I have been able to observe it one time, but I don't find that code right now and I don't remember the exact version of JS engine (but it was node). If I manage to find it, I will add it to this post.
That being said, the point is that code relying in this behaviour can (and should) be considered buggy because, even it will work as expected in most engines, it may fail typically because of internal engine optimisations.
For example:
"use strict";
var x = {
b: 23,
a: 7
};
function someSorting(input) {
var output = {};
Object.keys(input).sort().map(
function(k){
output[k] = input[k];
}
);
return output;
};
x = someSorting(x);
// Some smart engine could notice that input and output objects have the
// exact same properties and guess that, attending the unordered nature of
// javascript Object, there is no need to actually execute someSorting();
console.log(x);
// Usually will display: { a: 7, b: 23 }
// But perfectly we could got: { b: 23, a: 7 }
I know there is too many literature (even StackOverflow questions) about this (NON-) issue and "workarrounds" to achieve the expected behaviour by sorting keys in a separate array.
But doing so code goes too messy compared in simply trusting in key order.
I'm pretty sure that this can be achieved in a more elegant fashion by implementing a so-called "sObject" alternative having native Object as its prototype but overloading it's native iterator and setter so that:
When any new property is added, it's key is appended to an Array index mantained under the hood.
When an sObject instance is iterated, our customized iterator uses that index to retrieve elements in the right order.
In summary: Actual Object specification is right because, in most cases, properties order doesn't care. So I think that engine optimisations that could mess it are wonderfull.
But it would be also wonderful to have an alternative sObject with which we could do something like:
var x = new sObject({b: 23, a: 7});
...and trust that we could iterate it in the same exact order or, also / at least, do some sorting task over it and trust that this will not be altered.
Of course!! I'm initalyzing it with a native javascript Object so, in fact, theoretically we can't trust that it will be populated right (even I can't imagine why any engine optimisation should alter it before any operation).
I used that notation for brevity (and, I confess) because I expect that, in that case should work always (even I'm not really sure). However we even could sort it later (which, in most cases we will do that way) or use other kind of initialization like providing a JSON string or an array of objects (or arrays) with single key and value pairs.
My concern is: Such a thing exists yet? I wasn't able to find it. But sure I'm not the first guy thinking in that...
I can try to implement it (I'm thinking about that). I think it's possible and that I could achieve it. But it's not as simple so first I want to be sure that I'm not reinventing the wheel...
So any comments, suggestions, etc... will be welcome.
Sure, you could do all this. You will need some machinery such as Object.observer, which is currently only available in Chrome. We can define this as the following:
function myObject(object) {
// if the object already has keys, bring them in in whatever order.
var keys = Object.keys(object);
// Override Object.keys to return our list of keys.
Object.defineProperty(object, 'keys', { get: function() { return keys; });
// Watch the object for new or deleted properties.
// Add new ones at the end, to preserve order.
Object.observe(object, function(changes) {
changes.forEach(function(change) {
if (change.type === 'add') keys.push(change.name);
if (change.type === 'delete') keys = keys.filter(function(key) {
return key === change.name;
});
});
});
return object;
}
Note that Object.observe is asynchronous, so even after you add a property to the object, it won't be reflected in the custom keys property until after a tick of the clock, although in theory you could use Object.deliverChangedRecords.
The above approach uses a function which adds the new ordered key functionality to an existing object. Of course there are other ways to design this.
This "solution" obviously cannot control the behavior of for...in loops.
Related
The WeakSet is supposed to store elements by weak reference. That is, if an object is not referenced by anything else, it should be cleaned from the WeakSet.
I have written the following test:
var weakset = new WeakSet(),
numbers = [1, 2, 3];
weakset.add(numbers);
weakset.add({name: "Charlie"});
console.log(weakset);
numbers = undefined;
console.log(weakset);
Even though my [1, 2, 3] array is not referenced by anything, it's not being removed from the WeakSet. The console prints:
WeakSet {[1, 2, 3], Object {name: "Charlie"}}
WeakSet {[1, 2, 3], Object {name: "Charlie"}}
Why is that?
Plus, I have one more question. What is the point of adding objects to WeakSets directly, like this:
weakset.add({name: "Charlie"});
Are those Traceur's glitches or am I missing something?
And finally, what is the practical use of WeakSet if we cannot even iterate through it nor get the current size?
it's not being removed from the WeakSet. Why is that?
Most likely because the garbage collector has not yet run. However, you say you are using Traceur, so it just might be that they're not properly supported. I wonder how the console can show the contents of a WeakSet anyway.
What is the point of adding objects to WeakSets directly?
There is absolutely no point of adding object literals to WeakSets.
What is the practical use of WeakSet if we cannot even iterate through it nor get the current size?
All you can get is one bit of information: Is the object (or generically, value) contained in the set?
This can be useful in situations where you want to "tag" objects without actually mutating them (setting a property on them). Lots of algorithms contain some sort of "if x was already seen" condition (a JSON.stringify cycle detection might be a good example), and when you work with user-provided values the use of a Set/WeakSet would be advisable. The advantage of a WeakSet here is that its contents can be garbage-collected while your algorithm is still running, so it helps to reduce memory consumption (or even prevents leaks) when you are dealing with lots of data that is lazily (possibly even asynchronously) produced.
This is a really hard question. To be completely honest I had no idea in the context of JavaScript so I asked in esdiscuss and got a convincing answer from Domenic.
WeakSets are useful for security and validation reasons. If you want to be able to isolate a piece of JavaScript. They allow you to tag an object to indicate it belongs to a special set of object.
Let's say I have a class ApiRequest:
class ApiRequest {
constructor() {
// bring object to a consistent state, use platform code you have no direct access to
}
makeRequest() {
// do work
}
}
Now, I'm writing a JavaScript platform - my platform allows you to run JavaScript to make calls - to make those calls you need a ApiRequest - I only want you to make ApiRequests with the objects I give you so you can't bypass any constraints I have in place.
However, at the moment nothing is stopping you from doing:
ApiRequest.prototype.makeRequest.call(null, args); // make request as function
Object.create(ApiRequest.prototype).makeRequest(); // no initialization
function Foo(){}; Foo.prototype = ApiRequest.prototype; new Foo().makeRequest(); // no super
And so on, note that you can't keep a normal list or array of ApiRequest objects since that would prevent them from being garbage collected. Other than a closure, anything can be achieved with public methods like Object.getOwnPropertyNames or Object.getOwnSymbols. So you one up me and do:
const requests = new WeakSet();
class ApiRequest {
constructor() {
requests.add(this);
}
makeRequest() {
if(!request.has(this)) throw new Error("Invalid access");
// do work
}
}
Now, no matter what I do - I must hold a valid ApiRequest object to call the makeRequest method on it. This is impossible without a WeakMap/WeakSet.
So in short - WeakMaps are useful for writing platforms in JavaScript. Normally this sort of validation is done on the C++ side but adding these features will enable moving and making things in JavaScript.
(Of course, everything a WeakSet does a WeakMap that maps values to true can also do, but that's true for any map/set construct)
(Like Bergi's answer suggests, there is never a reason to add an object literal directly to a WeakMap or a WeakSet)
By definition, WeakSet has only three key functionalities
Weakly link an object into the set
Remove a link to an object from the set
Check if an object has already been linked to the set
Sounds more pretty familiar?
In some application, developers may need to implement a quick way to iterate through a series of data which is polluted by lots and lots of redundancy but you want to pick only ones which have not been processed before (unique). WeakSet could help you. See an example below:
var processedBag = new WeakSet();
var nextObject = getNext();
while (nextObject !== null){
// Check if already processed this similar object?
if (!processedBag.has(nextObject)){
// If not, process it and memorize
process(nextObject);
processedBag.add(nextObject);
}
nextObject = getNext();
}
One of the best data structure for application above is Bloom filter which is very good for a massive data size. However, you can apply the use of WeakSet for this purpose as well.
A "weak" set or map is useful when you need to keep an arbitrary collection of things but you don't want their presence in the collection from preventing those things from being garbage-collected if memory gets tight. (If garbage collection does occur, the "reaped" objects will silently disappear from the collection, so you can actually tell if they're gone.)
They are excellent, for example, for use as a look-aside cache: "have I already retrieved this record, recently?" Each time you retrieve something, put it into the map, knowing that the JavaScript garbage collector will be the one responsible for "trimming the list" for you, and that it will automatically do so in response to prevailing memory conditions (which you can't reasonably anticipate).
The only drawback is that these types are not "enumerable." You can't iterate over a list of entries – probably because this would likely "touch" those entries and so defeat the purpose. But, that's a small price to pay (and you could, if need be, "code around it").
WeakSet is a simplification of WeakMap for where your value is always going to be boolean true. It allows you to tag JavaScript objects so to only do something with them once or to maintain their state in respect to a certain process. In theory as it doesn't need to hold a value it should use a little less memory and perform slightly faster than WeakMap.
var [touch, untouch] = (() => {
var seen = new WeakSet();
return [
value => seen.has(value)) || (seen.add(value), !1),
value => !seen.has(value) || (seen.delete(value), !1)
];
})();
function convert(object) {
if(touch(object)) return;
extend(object, yunoprototype); // Made up.
};
function unconvert(object) {
if(untouch(object)) return;
del_props(object, Object.keys(yunoprototype)); // Never do this IRL.
};
Your console was probably incorrectly showing the contents due to the fact that the garbage collection did not take place yet. Therefore since the object wasn't garbage collected it would show the object still in weakset.
If you really want to see if a weakset still has a reference to a certain object then use the WeakSet.prototype.has() method. This method, as the name implies returns a boolean indicating wether the object still exists in the weakset.
Example:
var weakset = new WeakSet(),
numbers = [1, 2, 3];
weakset.add(numbers);
weakset.add({name: "Charlie"});
console.log(weakset.has(numbers));
numbers = undefined;
console.log(weakset.has(numbers));
Let me answer the first part, and try to avoid confusing you further.
The garbage collection of dereferenced objects is not observable! It would be a paradox, because you need an object reference to check if it exists in a map. But don't trust me on this, trust Kyle Simpson:
https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/es6%20%26%20beyond/ch5.md#weakmaps
The problem with a lot of explanations I see here, is that they re-reference a variable to another object, or assign it a primitive value, and then check if the WeakMap contains that object or value as a key. Of course it doesn't! It never had that object/value as a key!
So the final piece to this puzzle: why does inspecting the WeakMap in a console still show all those objects there, even after you've removed all of your references to those objects? Because the console itself keeps persistent references to those Objects, for the purpose of being able to list all the keys in the WeakMap, because that is something that the WeakMap itself cannot do.
While I'm searching about use cases of Weakset I found these points:
"The WeakSet is weak, meaning references to objects in a WeakSet are held weakly.
If no other references to an object stored in the WeakSet exist, those objects can be garbage collected."
##################################
They are black boxes: we only get any data out of a WeakSet if we have both the WeakSet and a value.
##################################
Use Cases:
1 - to avoid bugs
2 - it can be very useful in general to avoid any object to be visited/setup twice
Refrence: https://esdiscuss.org/topic/actual-weakset-use-cases
3 - The contents of a WeakSet can be garbage collected.
4 - Possibility of lowering memory utilization.
Refrence: https://www.geeksforgeeks.org/what-is-the-use-of-a-weakset-object-in-javascript/
##################################
Example on Weakset: https://exploringjs.com/impatient-js/ch_weaksets.html
I Advice you to learn more about weak concept in JS: https://blog.logrocket.com/weakmap-weakset-understanding-javascript-weak-references/
(Let us suppose that there is a good reason for wishing this. See the end of the question if you want to read the good reason.)
I would like to obtain the same result as a for in loop, but without using that language construct. By result I mean only an array of the property names (I don't need to reproduce the behavior that would happen if I modify the object while iterating over it).
To put the question into code, I'd like to implement this function without for in:
function getPropertiesOf(obj) {
var props = [];
for (var prop in obj)
props.push(prop);
return props;
}
From my understanding of the ECMAScript 5.1 specification about the for in statement and the Object.keys method, it seems the following implementation should be correct:
function getPropertiesOf(obj) {
var props = [];
var alreadySeen = {};
// Handle primitive types
if (obj === null || obj === undefined)
return props;
obj = Object(obj);
// For each object in the prototype chain:
while (obj !== null) {
// Add own enumerable properties that have not been seen yet
var enumProps = Object.keys(obj);
for (var i = 0; i < enumProps.length; i++) {
var prop = enumProps[i];
if (!alreadySeen[prop])
props.push(prop);
}
// Add all own properties (including non-enumerable ones)
// in the alreadySeen set.
var allProps = Object.getOwnPropertyNames(obj);
for (var i = 0; i < allProps.length; i++)
alreadySeen[allProps[i]] = true;
// Continue with the object's prototype
obj = Object.getPrototypeOf(obj);
}
return props;
}
The idea is to walk explicitly the prototype chain, and use Object.keys to get the own properties in each object of the chain. We exclude property names already seen in previous objects in the chain, including when they were seen as non-enumerable. This method should even respect the additional guarantee mentioned on MDN:
The Object.keys() method returns an array of a given object's own
enumerable properties, in the same order as that provided by a
for...in loop [...].
(emphasis is mine)
I played a bit with this implementation, and I haven't been able to break it.
So the question:
Is my analysis correct? Or am I overlooking a detail of the spec that would make this implementation incorrect?
Do you know another way to do this, that would match the implementation's specific order of for in in all cases?
Remarks:
I don't care about ECMAScript < 5.1.
I don't care about performance (it can be disastrous).
Edit: to satisfy #lexicore's curiosity (but not really part of the question), the good reason is the following. I develop a compiler to JavaScript (from Scala), and the for in language construct is not part of the things I want to support directly in the intermediate representation of my compiler. Instead, I have a "built-in" function getPropertiesOf which is basically what I show as first example. I'm trying to get rid of as many builtins as possible by replacing them by "user-space" implementations (written in Scala). For performance, I still have an optimizer that sometimes "intrinsifies" some methods, and in this case it would intrinsify getPropertiesOf with the efficient first implementation. But to make the intermediate representation sound, and work when the optimizer is disabled, I need a true implementation of the feature, no matter the performance cost, as long as it's correct. And in this case I cannot use for in, since my IR cannot represent that construct (but I can call arbitrary JavaScript functions on any objects, e.g., Object.keys).
From the specification point of view, your analysis correct only under assumption that a particular implementation defines a specific order of enumeration for the for-in statement:
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.
See the last sentence here.
So if an implementation does not provide such specific order, then for-in and Object.keys may return different things. Well, in this case even two different for-ins may return different things.
Quite interesting, the whole story reduces to the question if two for-ins will give the same results if the object was not changed. Because, if it is not the case, then how could you test "the same" anyway?
In practice, this will most probably be true, but I could also easily imagine that an object could rebuild its internal structure dynamically, between for-in calls. For instance, if certain property is accessed very often, the implementation may restructure the hash table so that access to that property is more efficient. As far as I can see, the specification does not prohibit that. And it is also not-so-unreasonable.
So the answer to your question is: no, there is no guarantee according to the specification, but still will probably work in practice.
Update
I think there's another problem. Where is it defined, what the order of properties between the members of the prototype chain is? You may get the "own" properties in the right order, but are they merged exactly the way as you do it? For instance, why child properties first and parent's next?
I have long ago been willing to get the line between native Arrays and regular Objects totally blurred, not only extending Object with the same capabilities as Arrays got in ES5, but bundle up with my custom package of methods on both sides.
Couple of smart people thought about these paradigm changes. Like Angus Croll mentioned it in the article javascript-object-keys-finally:
"Moreover as the line between Arrays and regular Objects blurs (aided
by custom getters and setters) we’re likely to see a growth in generic
“array-like” objects which enjoy the best of both worlds – non-numeric
identifiers and access to the rich API set defined by Array.prototype.
EcmaScript 5 has apparently pre-empted this trend by introducing the
generic method, defined by one type but useable by any."
Along the way, he get things coded in the article:
extending-objects-with-javascript-getters
function extendAsArray(obj) {
if (obj.length === undefined || obj.__lookupGetter__('length')) {
var index = 0;
for (var prop in obj) {
if(!obj.__lookupGetter__(prop)) {
(function(thisIndex, thisProp) {
obj.__defineGetter__(thisIndex, function() {return obj[thisProp]});
})(index, prop)
index++;
}
};
obj.__defineGetter__("length", function() {return index});
}
return obj;
}
var myObj = {
left:50,
top:20,
width:10,
height:10
}
extendAsArray(myObj);
[].map.call(myObj,function(s){return s+' px'}).join(', ');
//"50px ,20px ,10px, 10px"
This approach is exceptionally interesting for me. However, it is also seemingly suffering a couple of serious problems!
How about extending the original myObj model with a couple of new properties?
Should we run extendAsArray on every property change to update it's concerning length property?
When a property changes, it's not just the length property that's relevant;
the array indices should also be updated, because an array-like property request definitely turns out to be undefined. So when
console.log(myObj.length) -> 4
myObj.zAxis=0
then
console.log(myObj[4]) // -> undefined!
console.log(myObj.length) // -> 4!
I have modified Angus' code accordingly, so it supports automatic update of length property on request:
function extendAsArray(obj) {
var index = 0;
for(var prop in obj){
if(!obj.__lookupGetter__(prop)){
(function(thisIndex, thisProp){
Object.defineProperty(obj, thisIndex, {
get: function(){return obj[thisProp]}
, enumerable: true
, configurable: true
, writeable: true
});
})(index, prop)
index++;
}
}
if(!obj.__lookupGetter__('length')){
Object.defineProperty(obj, 'length', {
get: function(){
return extendAsArray(obj);
}
, configurable: true
, writeable: true
});
return obj;
}
else{
return index;
}
}
The problem is: how do we updating the object's array indices together with its length property when a property is changed, added or removed?
Should I use Object.watch?
And there is still an unsolved question: how to interfere with my own unshimmed utility library, having made it also for Objects in a consistent way?
I am using the same codebase for both types: z.Object({}).mapEvery does the same as z.Object([]).mapEvery
Please avoid mentioning JQuery, and Underscore as well. I have got a comprehensive, custom list of methods for both types, and I am willing to use the standards completed possibly with my unshimmed ones, and I am not willing to refactor it!
I guess this is your question:
how do we updating the object's array indices together with its length property when a property is changed, added or removed?
You create methods to do it, so you essentially mimic the Object internal methods. I don't think you can do that with getters and setters, but I may be wrong about that.
The rest is more of a comment than an answer.
I have long ago been willing to get the line between native Arrays and regular Objects totally blurred
The line is already totally blurred. Arrays are Objects, the only thing that sets them apart is their special length property.
EcmaScript 5 has apparently pre-empted this trend by introducing the generic method,
ES5 didn't introduce generic methods, they have been in the language since ed 3 at least.
defined by one type but useable by any
Not at all, in fact ES5 is more restrictive. In ed 3, call and apply coerce the thisArg to an object using Object(*thisArg*), or substitute the global object if nothing was passed. Not so in ES5 which passes thisArg unmodified.
The restriction on using arrays as objects is related to convention, not the language itself. Most developers see a clear divide between when an object or array should be used. There are few cases where you really need to use an array like an object, but no doubt they exist. jQuery is an example of where an Object leverages Array properties, e.g. the elements collected by a selector are added as numeric properties and there is a length property that is the number of elements. That way generic array methods can be applied to jQuery objects (all in ed 3, by the way).
The Object.watch method is in JavaScrpit™, it's not part of ES5 so use with caution.
A major problem with creating your own version of built–in objects is that you'll probably end up wrapping every built–in method in a native one (like jQuery wraps every DOM method pretty much) and start setting getters and setters on every property, or end up with function calls to replace property access (e.g. jQuery's val, attr and prop methods). Rather tedious, and slow if performance matters.
Oh sorry, I mentioned jQuery… :-(
It just seems to be more sensible to design a library or framework to make the best use of the features that the language has, rather than trying to force it to do things it doesn't do well, or does not do natively.
But full marks for trying. :-)
There is a library watch.js out there, which is watching out for either property updates or new property addition as well.
try out!
It is working with setInterval, so it is not performance-friendly however.
When Harmony is out, we can do things that simply:
Object.observe(obj,Observer);
Check the spec for that:
Harmony
But, when later object extension is not in focus, i can freeze up the whole object upon initialization, not to be bothered with neither property changes nor property addition.
The code is changed accordingly:
extendAsArray = function z_extendAsArray(obj){
var index = 0;
for(var prop in obj){
if(!obj.__lookupGetter__(prop)){
(function(thisIndex, thisProp){
Object.defineProperty(obj, thisIndex, {
get: function(){return obj[thisProp]}
, enumerable: true
, configurable: true
, writeable: true
});
})(index, prop)
index++;
}
}
if(!obj.__lookupGetter__('length')){
Object.defineProperty(obj, 'length', {
value:index
});
if(!Object.freeze){
Object.defineProperty(Object, "freeze", {
enumerable: false
, configurable: false
, writable: false
, value: function (obj) {
var props = Object.getOwnPropertyNames(obj);
for(var i=0; i<props.length; i++){
var desc = Object.getOwnPropertyDescriptor(obj,props[i]);
if("value" in desc ){
desc.writable = false;
}
desc.configurable = false;
Object.defineProperty( obj, props[i], desc );
}
return Object.preventExtensions(obj);
}
});
}
Object.freeze(obj);
}
return obj;
};
Also i have found out what Angus Croll, who has been mentioned by the previous post has talked about it.
"Yes, we can make use of the equivalent functionality offered by well written libraries like underscore.js, but still we’re locked into non-standard, inverted signatures in which methods are static and objects are merely extra arguments – an ungainly arrangement for an instance-only language. At some point all supported browsers will be ES5 compliant, at which point the shimmed codebase can simply remove it’s shim library and carry on, while the unshimmed one must choose between a major refactor or a perpetually non-standard and static utility library."
I'm pretty shocked I can't find an answer to this (maybe there isn't one). Suppose I want to iterate over an array one time. I don't to have to create a variable to iterate with, but it seems that that's what I have to do. Ideally, I'd be able to do something like this:
for (key, val in 'one two three'.split(' ')) {
console.log(val);
}
However, it seems that you can only get the keys from javascript's for..in syntax. Is there any way to iterate over that array without storing it to a variable first?
About half of all web surfers (as of this writing, the number's going up all the time) are using browsers that natively support ECMAScript5 (e.g., the 5th edition), which has some new constructs for this, including forEach:
"one two three".split(" ").forEach(function(val, key) {
// do stuff here
});
(Or if you don't need the key variable, just leave it off [e.g., ...forEach(function(val) {...].)
Even if the browser doesn't have it, forEach is one of those features that can be "shimmed" (emulated) entirely correctly even in older browsers, e.g. via es5_shim.js or similar. So just include the shim (or write your own) and happily use it regardless of whether it's natively supported or something you've added. Or you may be using a library that already provides it or an analogue of it (jQuery has $.each, Prototype adds each to arrays, Backbone offers something similar, etc.).
forEach also has the advantage that the loop-specific variables (entry and index) are contained by the iterator function and so you don't pollute the scope in which you're using it. There is a runtime cost, because you have the function call overhead for every array element. But function calls are very fast in real terms (even on IE6, which is a dog), you can be sure that whatever else you're doing in the loop body will completely wash out the added overhead.
With the "older" version of JavaScript (3rd edition), there's nothing built in, you either provide your own function for doing it, or on every loop you declare a variable for the array (as well as an indexing variable and an array entry variable), e.g.:
var array = "one two three".split(" ");
var key, val;
for (key = 0; key < array.length; ++key) {
val = array[key];
// do stuff here
}
But again you could provide your own forEach easily enough.
Another construct you can use both in older and newer engines is for..in, which iterates over the enumerable properties of an object. You still end up needing all of the variables, though. Because that means more than just "indexes", you have to add some safeguards:
var array = "one two three".split(" ");
var key, val;
for (key in array) {
if (array.hasOwnProperty(key) && String(Number(key)) === key) {
// It's a numeric key and the object itself owns it
val = array[key];
// do stuff here
}
}
...but in this case it doesn't buy you anything. It's useful for when the array is sparse (e.g., has large gaps in it).
for/in is generally not used on arrays as for performs the same basic thing with less overhead and no need for a sanity wrapper on the loop block.
That said, given the scenario you present, no, there is no way to obtain the value in such a case. Since for/in only lets you get the key, you have to use that key to access the value in a reference to the array.
I read a few questions and answers about javascript dictionary implementations, but they don't meet my requirements:
the dictionary must be able to take objects as keys
the values must be accessible by the []-operator
So I came up with the idea to overwrite the valueOf-method in Object.prototype, as follows:
Object.__id__ = 0;
Object.prototype.valueOf = function() {
if(!this.__id__)
this.__id__ = ++Object.__id__;
return "__id__" + this.__id__;
}
Object.prototype.toString = Object.prototype.valueOf;
//test
var x = {p1: "5"};
var y = [6];
var z = {};
z[x] = "7";
z[y] = "8";
console.log(z[x], z[y]);
I tested this with google-chrome and it seems to work well, but I'm a bit sceptical, whether this will cause some drawbacks, since it was so easy to implement.
Considering that the valueOf method is not used for other purposes in the whole code, do you think there are any disadvantages?
It's an interesting idea. I suggest my jshashtable. It meets your first requirement but not the second. I don't really see the advantage of insisting on using the square bracket property access notation: do you have a particular requirement for it?
With jshashtable, you can provide a hashing function to the Hashtable constructor. This function is passed an object to be used as a key and must return a string; you could use a function not dissimilar to what you have there, without having to touch Object.prototype.
There are some disadvantages to your idea:
Your valueOf method will show up in a for...in loop over any native object;
You have no way determining which keys should be considered equal, which is something you may want to do. Instead, all keys will be considered unique.
This won't work with host objects (i.e. objects provided by the environment, such as DOM elements)
It is an interesting question, because I had so far assumed that any object can be used as an index (but never tried with associative arrays). I don't know enough about the inner workings of JavaScript to be sure, but I'd bet that valueOf is used somewhere else by JavaScript, even if not in your code. You might run into seemingly inexplicable problems later. At least, I'd restrict myself to a new class and leave Object alone ;) Or, you explicitly call your hashing function, calling it myHash() or whatever and calling z[x.myHash()] which adds clutter but would let me, personally, sleep better ;) I can't resist thinking there's a more JavaScript-aware solution to this, so consider all of these ugly workarounds ;)
If you came upon this question looking for a JS dictionary where objects are keys look at Map Map vs Object in JavaScript