views:

58

answers:

2

Here's a ugly bit of Javascript it would be nice to find a workaround.

Javascript has no classes, and that is a good thing. But it implements fallback between objects in a rather ugly way. The foundational construct should be to have one object that, when a property fails to be found, it falls back to another object.

So if we want a to fall back to b we would want to do something like:

a = {sun:1};
b = {dock:2};
a.__fallback__ = b;

then

a.dock == 2;

But, Javascript instead provides a new operator and prototypes. So we do the far less elegant:

function A(sun) {
   this.sun = sun;
};
A.prototype.dock = 2;
a = new A(1);

a.dock == 2;

But aside from elegance, this is also strictly less powerful, because it means that anything created with A gets the same fallback object.

What I would like to do is liberate Javascript from this artificial limitation and have the ability to give any individual object any other individual object as its fallback. That way I could keep the current behavior when it makes sense, but use object-level inheritance when that makes sense.

My initial approach is to create a dummy constructor function:

function setFallback(from_obj, to_obj) {
    from_obj.constructor = function () {};
    from_obj.constructor.prototype = to_obj;
}

a = {sun:1};
b = {dock:2};
setFallback(a, b);

But unfortunately:

a.dock == undefined;

Any ideas why this doesn't work, or any solutions for an implementation of setFallback?

(I'm running on V8, via node.js, in case this is platform dependent)


Edit:

I've posted a partial solution to this below, that works in the case of V8, but isn't general. I'd still appreciate a more general solution.

+4  A: 

You could just use Object.create. It's part of ES5 so it's already available natively in some browsers. I believe it does exactly what you want.

Jimmy Cuadra
Funny, I had an implementation that did just that. But what this makes difficult is the ability to use a constructor function. Sometimes you really do want to use constructors, you just don't want to tie the structure of the resulting objects with the use of the constructor.
Ian
@Ian: The point of `Object.create` is that it *doesn't* require that you go through a constructor function. Crockford's implementation had to because Javascript didn't have a native way of setting an object prototype, but now that the 5th edition spec is out and has added doing exactly that to the language (section 15.2.3.5), this is the way to go for the situation you're describing. (5th ed spec here: http://www.ecma-international.org/publications/standards/Ecma-262.htm)
T.J. Crowder
So how do you deal with the situation when you want a constructor - you want some code to be responsible for constructing the object? Do you just have a factory `function makeFoo(args) { ... }` do it? ---You know, its annoying that you're not going to get any of the credit if I accept Jimmy's answer :)
Ian
@Ian: Yup, that's what you do. When it makes sense to have a constructor function, you use one and give that function's `prototype` property the things you want on the prototype of the objects it creates. When you *don't* want a constructor function (or you do, but you want to use a different prototype; which is weird but possible), you can use `Object.create` instead. *(Re credit -- enh, what goes around... :-) )*
T.J. Crowder
A: 

Okay, some more research and cross-platform checking and there's some more information (though not a general solution).

Some implementations have basically what I did for my __fallback__. It is called __proto__ and is about perfect:

a = {sun:1};
b = {dock:2};
a.__proto__ = b;

a.dock == 2;

It seems that, what happens in when a new object is constructed is roughly this:

a = new Constructor(...args...);

produces behavior roughly equivalent to:

object.constructor = constructor;
object.__proto__ = constructor.prototype;
constructor.call(this, ...args...);

So it is no wonder that coming along later and adjusting an object's constructor or constructor.prototype has no effect, because the __proto__ setting is already set.

Now for my v8 application, I can just use __proto__, but I understand it that this isn't exposed on the IE VM (I don't run windows, so I can't tell). So it is not a general solution to the problem.

Ian
To edit your question, click the 'edit' link under the question, rather than posting an answer to it.
T.J. Crowder
I felt that this qualified as a partial solution to the question rather than a modification of it. I will edit out the run on questions now.
Ian
*"So it is not a general solution to the problem."* Right, it's an extension to the language that some implementations make (I think it started with Firefox). The ECMAScript committee decided not to include it in the language, but did provide `Object.create` and `Object.getPrototypeOf` (and the `isPrototypeOf` property of all objects) so you can create objects with specific prototypes, and find out what the prototype of an object is.
T.J. Crowder
Thanks for the help, I'm getting to understand this now. I think it is an unfortunate choice, again. Something like `__proto__` would allow you to do *anything* that the new mechanisms would allow, but the converse isn't true. `Object.create` is still combining object construction and fallback, which have no a priori need to be conflated.
Ian
@Ian: *"I think it is an unfortunate choice"* It was what could be agreed by the committee. A whole-hog change like `__proto__` was unpalatable to some high-profile implementers. ECMAScript 5th edition is the result of a **lot** of haggling.
T.J. Crowder
@Ian: *"`Object.create` is still combining object construction and fallback"* With apologies, you're missing the point a bit. :-) Crockford's *implementation* of `Object.create` conflates them, because it had no choice -- as of the 3rd ed., there is *no* way to set an object prototype other than via a constructor function. As of the 5th ed., `Object.create` is built-in. It doesn't conflate the concepts, doesn't go via a constructor function, it sets the prototype directly. It's a language change, vs. Crockford's workaround. Do have a look at that spec section I mentioned for details.
T.J. Crowder
@TJ, Thanks for being patient with me on this. So the ES5 Object.create doesn't create a new object? Or was I unclear about what I meant - I used 'constructor' in the informal sense of constructing something. I meant that there are two separate tasks, building a new object, and configuring where an object falls back to. In an AS5 compliant implementation without __proto__, could I take two existing objects and make one fallback to the other? If not then I didn't miss the point: the two things shouldn't be conflated. Are you on or have direct knowledge of the committee (just FWIW)?
Ian