1

I'm trying to figure out if there is a way to have an object property defined in its prototype as a dynamic value that can change every time an instance of the object is created. That's kind of the best way I can describe it; I have made a fiddle to show what I'm trying to do (though it doesn't work).

var Response = {
    LCID: 321
};


Date.prototype.LCID = Number(0);
Date.prototype.LCID.valueOf = function() { return Response.LCID; };


document.write((new Date()).LCID);
Response.LCID = 456;
document.write((new Date()).LCID);

http://jsfiddle.net/tx2fW/2/

What I'm trying to achieve is that Response.LCID can change over the lifetime of the code as you can see from me changing its value in the code later on. Whenever I create a Date object, I want the value of (new Date()).LCID to be the current value of Response.LCID, not just the value that it had when I first create Date.prototype.LCID.

Is there any way this can be done? The biggest limitation is that it has to work in JavaScript 1.5... though I would be curious to see if it could be done in recent versions.

Eli Sand
  • 1,032
  • 7
  • 11

3 Answers3

1

Ok this does it using the __defineGetter__ method, however I am not sure how it will behave on all browser but that the only way I could find to achieve what you want (well I think this time it is what you want)

http://jsfiddle.net/tx2fW/6/ working example.

var Response = {
    LCID: 321
};

var d = Date.prototype;

d._LCID = Number(0);
d.getLCID = function() {
    if (d._LCID != Response.LCID) d._LCID = Response.LCID;
    return d._LCID ;
};
d.__defineGetter__("LCID", function() {
    return this.getLCID();
});

document.write((new Date()).LCID);
Response.LCID = 456;
document.write((new Date()).LCID);​

For alternative to __defineGetter__ please see this post

Community
  • 1
  • 1
GillesC
  • 10,647
  • 3
  • 40
  • 55
  • No, I specifically want Response.LCID to change and have it's change reflected in new Date objects. I do not want to have to change `Date.prototype.LCID`. – Eli Sand Apr 15 '12 at 08:48
  • Need your own wrapper for Date then, otherwise what you ask can't be done http://jsfiddle.net/tx2fW/4/ – GillesC Apr 15 '12 at 08:53
  • Using a custom date object is also not an option unfortunately. What I'm trying to ultimately achieve is being able to attach locale ID's to objects, either built-in objects or custom objects. If I can't do it for built-in objects, the point is lost since I would have to get other programmers to be aware of all the custom objects to use instead of built-ins. – Eli Sand Apr 15 '12 at 09:11
  • Yeah I see what you are trying to achieve. The only way that could be done is by modifying the Date object constructor I guess but I don't think you can do that in JS (need to play with it and see). As that way it would set its own value on creation. – GillesC Apr 15 '12 at 09:15
  • Internet Explorer JScript doesn't support `__defineGetter__`. – AnthonyWJones Apr 15 '12 at 16:15
  • I know this is a couple years old but __defineGetter__ is deprecated. Is there an alternative? – p0lar_bear Feb 24 '15 at 16:55
1

Number(0) === 0. Modifying .valueOf and .toString on primitive values have no effect.

The correct way to do this is to pass an instance of the Number constructor, using the new operator:

var Response = {
    LCID: 321
};
Date.prototype.LCID = new Number();   // <-- Use the "new" operator
Date.prototype.LCID.valueOf = function() { return Response.LCID; };

Annotated demo and notes: http://jsfiddle.net/tx2fW/7/

  • LCID is an object. typeof new Date().LCID === 'object'.
  • LCID is a true instance of Number. new Date().LCID instanceof Number === true
  • LCID is equal to 321: (new Date().LCID == 321) === true
  • LCID is not identical to 321: (new Date().LCID === 321) === false.
    (because LCID is an object, and 321 is a primitive number value).

PS. If you're not familiar with == versus ===, see Which equals operator (== vs ===) should be used in JavaScript comparisons?

Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • +1 for getting the answer right and posting the code here. But no one should mess with built–in prototypes. Seems you got a drive–by -1. – RobG Apr 15 '12 at 10:12
  • @RobG About modifying prototypes: He did not elaborate his application, so I cannot judge whether his decision is OK or not. Rule of thumb: Does the added property directly relate to the `Date` constructor? If not: Do not extend the prototype. – Rob W Apr 15 '12 at 10:16
  • Thank you for this - I had actually tried that before and kept trying other ways because your code fails if JavaScript doesn't have to cast/coerce the LCID property in to another type. Your code only works because it's being converted to a string for concatenation. Try outputting **just** `(new Date()).LCID` to see what happens (you get a zero). – Eli Sand Apr 15 '12 at 14:44
  • @EliSand I explained that in my annotations: It is an object. One character can make the method to behave as expected, across all (ancient) browsers: `+(new Date).LCID` (notice the unary `+`), see also point **s and t of [Comparison between all number-conversion methods](http://stackoverflow.com/a/8112802/938089?are-there-are-any-side-effects-of-using-this-method-to-convert-a-string-to-an-in)**. – Rob W Apr 15 '12 at 14:46
  • Oh, and fully aware of == and ===, as well as when and why to extend an objects prototype. This properly does directly relate to the object; I'm creating a locale ID for Date objects. This is for server-side JavaScript in Classic ASP (`Response.LCID` is an ASP object). – Eli Sand Apr 15 '12 at 14:47
0

After some more research and experimenting, I was able to actually solve the issue. I had tried playing with the Date constructor and such, but I didn't have much luck in my initial trials - apparently because I overlooked the fact that the Date object is unique in that it differs in functionality depending on how it is called (as a function or object constructor). This means that you can't just do Date.prototype.constructor.apply(this, arguments) since all you'll ever get back is a string (the Date object is being called as a function).

After having found this thread and reading it, I came up with the following code that creates an actual Date object (or string if called as a function) and mimics the built-in Date object perfectly (as far as my tests show anyways). Every time a new Date object is created, it gets the LCID property which is dynamically generated during object creation which is exactly what I needed.

Date = (function(orig) {
    var date = function(a, b, c, d, e, f, g) {
        var object = (this instanceof Object ? (arguments.length < 1 ? new orig() : (arguments.length < 2 ? new orig(a) : (arguments.length < 4 ? new orig(a, b || 0, c || 1) : new orig(a, b, c, d || 0, e || 0, f || 0, g || 0)))) : orig());
        object.LCID = Response.LCID;

        return object;
    };
    date.prototype = orig.prototype;

    return date;
})(Date);

I also created a bunch of test cases to ensure that there is no difference from a built-in Date object, or using this code (comment out this code to see the results using the built-in Date object and compare).

var Response = { 'LCID': 123 };


Date = (function(orig) {
    var date = function(a, b, c, d, e, f, g) {
        var object = (this instanceof Object ? (arguments.length < 1 ? new orig() : (arguments.length < 2 ? new orig(a) : (arguments.length < 4 ? new orig(a, b || 0, c || 1) : new orig(a, b, c, d || 0, e || 0, f || 0, g || 0)))) : orig());
        object.LCID = Response.LCID;
        return object;
    };
    date.prototype = orig.prototype;
    return date;
})(Date);


var x = new Date();
document.writeln(x);
document.writeln(x.LCID);
document.writeln(x.getFullYear());
document.writeln(typeof x);
document.writeln(Object.prototype.toString.call(x));
document.writeln(x instanceof Date);
document.writeln("<br/>");

Response.LCID = 456;

var y = new Date();
document.writeln(y);
document.writeln(y.LCID);
document.writeln(y.getFullYear());
document.writeln(typeof y);
document.writeln(Object.prototype.toString.call(y));
document.writeln(y instanceof Date);
document.writeln("<br/>");

document.writeln(Date());
document.writeln(new Date());
document.writeln(new Date(2012));
document.writeln(new Date(2012, 7));
document.writeln(new Date(2012, 7, 14));
document.writeln(new Date(2012, 7, 14, 9));
document.writeln(new Date(2012, 7, 14, 9, 45));
document.writeln(new Date(2012, 7, 14, 9, 45, 27));
document.writeln(new Date(2012, 7, 14, 9, 45, 27, 687));

This is also available as an updated fiddle: http://jsfiddle.net/tx2fW/9/

Community
  • 1
  • 1
Eli Sand
  • 1,032
  • 7
  • 11