0

I need to make a Javascript object that would behave as an associative array, but with some functions that are called before getting and setting properties.

For example, the task may be like this: we should make an object, that would contain a squared value of a key, like this:

obj.two should be equal to 4, 
obj.four should be equal to 16, 
obj['twenty one'] should be equal to 441.

This is an example. Actually I need to make setting operation overridden too. The getting and setting operations would go to the database, and they not necceserily would take strings as keys, but any types of objects, from which it would create a DB query.

How would I do that a) with as less thirdparty libraries as possible and b) to make it work on as much platforms as possible?

I am new to JS, I've found that JS has no associative arrays, relying on the ability to define objects on the fly with arbitrary properties. I googled and had an idea to use or even override lookupgetter (and setter), where define a new getter/setter on the fly, but I coundn't find if the interpreter would use this method every time it encounters new key. Anyway, it looks like I wouldn't be able to use anything except strings or maybe numbers as keys.

In Java, I would just implement java.util.Map.

Please help me, how would I do the same in Javascript?

edit

I think I will get what I want if I manage to override [[Get]] and [[Put]] methods mentioned here http://interglacial.com/javascript_spec/a-8.html#a-8.6.2.1

fedd
  • 880
  • 12
  • 39

3 Answers3

1

See http://ejohn.org/blog/javascript-getters-and-setters/

Also this particular answer: Javascript getters and setters for dummies?

edit

According to Does JavaScript have the equivalent of Python's __getattribute__? and Is there an equivalent of the __noSuchMethod__ feature for properties, or a way to implement it in JS? there is no nice way of accomplishing exactly what the OP wants. Getters and setters are not useful because you must know the name of what you're looking for in advance.

My recommendation would thus be to do something like:

var database = {}
database.cache = {}
database.get = function(key) {
    // INSERT CUSTOM LOGIC to recognize "forty-two"
    if (!(key in database.data))
        database.cache[key] = fetch_data_from_database();
    return database.cache[key];
}
database.put = function(key, value) {
    database.cache[key] = value;
    send_data_to_database(key, value);
}
Community
  • 1
  • 1
ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • How would I have, say, dates, or more complicate types, as keys? – fedd Jun 17 '11 at 07:16
  • @Fyodor: You would just assume they're being passed in as the parameters to the getter and setter. The semantics are entirely up to you. You don't even need to store anything in the object. If you do decide to store things in the object, the keys will automatically be converted to strings; but you can work around that in an elegant manner; there are many ways to do so, including the not-so-elegant solution of making the key=`[typeof(x),x]` (which is converted to a string). – ninjagecko Jun 17 '11 at 07:24
  • @Fyodor: To elaborate even further, this would work on any object and does not limit you to strings, as long as you aren't storing anything in the object. – ninjagecko Jun 17 '11 at 07:34
  • @ninjagecko - Do you use JS getters and setters for the web? I thought IE didn't support the standard syntax (except maybe in IE9). – nnnnnn Jun 17 '11 at 07:37
  • @ninjagecko, I can't get it. Should I define a zillion of getters, for every key I may have? Can't yet figure out how to avoid that – fedd Jun 17 '11 at 07:46
  • @Fyodor: oops, very sorry, I seem to have messed up in understanding your question. You are correct in that you would need to know the name of the object you are querying. According to http://stackoverflow.com/questions/3690394/does-javascript-have-the-equivalent-of-pythons-getattribute and http://stackoverflow.com/questions/2266789/is-there-an-equivalent-of-the-nosuchmethod-feature-for-properties-or-a-way-t/3757676#3757676 there is no nice way to do what you want. I am editing the answer. – ninjagecko Jun 17 '11 at 08:35
  • @ninjagecko, i tried and it works right as I expected even in ie 6. you're right! simple answer for descendants: just override get and put methods. didn't try other objects as keys but sure @nnnnnn `toString` advice would work – fedd Jun 17 '11 at 16:34
  • @Fyodor: `get` and `put` are custom methods (they were not defined before, and thus not overridden). My answer was merely "write a custom function and back it with a cache". =) – ninjagecko Jun 18 '11 at 08:59
  • @ninjagecko seems like they are defined. when i (re?)defined them, it started accessing properties through dots, without using `get` and `put` functions. as a novice i don't know the right place and way to read the spec, here's what i found: http://interglacial.com/javascript_spec/a-8.html#a-8.6.2.1 i may be wrong – fedd Jun 18 '11 at 21:06
  • @Fyodor: In javascript, as long as the key is a simple string, `x.two` is the same as `x['two']` – ninjagecko Jun 18 '11 at 21:41
  • i understood what i need: to override 'internal methods' [[Get]] and [[Put]] mentioned here http://interglacial.com/javascript_spec/a-8.html#a-8.6.2.1. I'll edit the question – fedd Jun 19 '11 at 07:14
  • @ninjagecko won't you mind if I unaccept your answer, maybe someone would say how to override 'internal methods', if it is possible – fedd Jun 19 '11 at 07:17
  • @Fyodor: I won't mind at all. But the document you linked says "*Internal properties and methods are not part of the language. They are defined by this specification purely for expository purposes. An implementation of ECMAScript must behave as if it produced and operated upon internal properties in the manner described here.*" I am extremely confused about the behavior you describe. What flavor of javascript are you using, and on which platform? It seems there are non-compatible ways: http://stackoverflow.com/questions/3112793/how-can-i-define-a-default-getter-and-setter-using-ecmascript-5 – ninjagecko Jun 19 '11 at 07:25
  • @ninjagecko what i described actually doesn't work, i thought it worked due to my error. :( – fedd Jun 19 '11 at 08:04
  • @Fyodor: that is what I suspected. There is no way to do what you are trying to do, unless you either use proprietary non-compatible things that only work in some browsers, or create your own scripting language syntax. Why is `square('twenty-four')` bad for you?; why doesn't using a simple function work for you? Why do you really need these semantics? – ninjagecko Jun 19 '11 at 08:21
  • @ninjagecko the task is to mimic how JSP EL expression language works with java.util.Map (an associative array of Java). If we have a Map `themap`, issuing `${themap.somekey}` or `${themap.['somekey']}` would actually call `themap.get('somekey')`. This is needed because the users should work with the library in a similar manner both in EL and in JS. Sad that it seems impossible in a portable way. May be I may make some native (c++) implementation of this to work?.. – fedd Jun 19 '11 at 08:41
  • @ninjagecko and in Java my `get` and `put` methods go to the database and cache results; using EL we avoid mentioning `get` and `put` all the time; so we wanted to make the same in JS. /ps i renamed myself to @fedd – fedd Jun 19 '11 at 09:15
1

For your example, doesn't this do what you want:

var myObj = {};

myObj["two"] = 4;
myObj["four"] = 16;
myObj["twenty one"] = 441;

alert(myObj["four"]); // says 16

Or are you trying to say that the object should magically calculate the squares for you?

JavaScript object keys are strings. If you try to use a number as a key JavaScript basically converts it to a string first.

Having said that, you can use objects as keys if you define a meaningful toString method on them. But of course meaningful is something that happens on a case by case basis and only you will know what needs to be done for your case.

You can also define objects that maintain their own internal data structures which you access via object methods. I think explaining that is beyond the scope of this post. Google "javascript module pattern" for some pointers to get you started.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • nnnnnn> are you trying to say that the object should magically calculate the squares for you? yes. thanks for toString hint – fedd Jun 17 '11 at 07:47
1

I decided that the most correct way to implement this is to use Harmony:Proxies. It isn't working on all platforms, but it lets implement this in the most seamless way; and it may be supported in more platforms in the future.

This page contains an example that I used as a template to do what I want:

http://wiki.ecmascript.org/doku.php?id=harmony:proxies

fedd
  • 880
  • 12
  • 39