Does JavaScript have an equivalent way to implement c# style auto properties without massive overhead?

StackOverflow https://stackoverflow.com/questions/14925808

  •  10-03-2022
  •  | 
  •  

Question

I'm curious if JavaScript has a way to bind a function when a property is changed without an enormous overhead like watching all the auto-properties with a timer, but without setting them via a function call. For example, I know you could do something like:

var c = new (function () {
    this.Prop = 'test';
    this.Prop_get = function (value) {
        return('Prop = ' + this.Prop);
    };
    this.Prop_set = function (value) {
        if (value != 'no') {
            this.Prop = value;
        }
    };
})();

document.write(c.Prop_get());
document.write('<BR />');
c.Prop_set('no');
document.write(c.Prop_get());
document.write('<BR />');
c.Prop_set('yes');
document.write(c.Prop_get());
document.write('<BR />');

But I'm looking for some way to allow the following to produce the same result:

document.write(c.Prop);
document.write('<BR />');
c.Prop = 'no';
document.write(c.Prop);
document.write('<BR />');
c.Prop = 'yes';
document.write(c.Prop);
document.write('<BR />');

With any changes to the pseudoclass other than adding a timer to watch the Prop property for changes or similarly high-overhead solutions.

Was it helpful?

Solution 2

I found the solution to this after coming across this link relating to getters and setters. Here is a generic method of applying properties to objects I put together as a result if anyone is interested in it:

Object.prototype.Property = function (name, fn) {
    if (fn.hasOwnProperty('get')) { this.__defineGetter__(name, fn.get); }
    else { this.__defineGetter__(name, function () { throw ('Cannot read property ' + name + '.'); }); }
    if (fn.hasOwnProperty('set')) { this.__defineSetter__(name, fn.set); }
    else { this.__defineSetter__(name, function () { throw ('Cannot write property ' + name + '.'); }); }
};

function C() {
    var _Field = 'test';
    this.Property('Field', {
        get: function () {
            return ('Field = ' + _Field);
        },
        set: function (value) {
            if (value != 'no') {
                _Field = value;
            }
        }
    });
};
C.prototype.constructor = C;

var c = new C();
document.write(c.Field);
document.write('<BR />');
c.Field = 'no';
document.write(c.Field);
document.write('<BR />');
c.Field = 'yes';
document.write(c.Field);
document.write('<BR />');

Edit: A JQuery-friendly Object.prototype.Property function like the above:

Object.defineProperty(Object.prototype, 'Property', {
    enumerable: false,
    value: function (name, fn) {
        if (fn.hasOwnProperty('get')) { this.__defineGetter__(name, fn.get); }
        else { this.__defineGetter__(name, function () { throw ('Cannot read property ' + name + '.'); }); }
        if (fn.hasOwnProperty('set')) { this.__defineSetter__(name, fn.set); }
        else { this.__defineSetter__(name, function () { throw ('Cannot write property ' + name + '.'); }); }
    }
});

And a working JSFiddle.

OTHER TIPS

Any solution to this issue comes down to what it is that you need to support.

If you require IE6-IE8, it's probably more sane to resort to timers or horrible abuse of the DOM to make changes to hidden DOM objects, which will fire listenable events, etc...

There are a few blogs which have talked about their efforts to squeeze these browsers into conformity with some kind of mutation-aware library.
Results and caveats vary.

If you're talking about ES5-compliant browsers, most support "get" and "set" keywords directly inside of objects.
This might lead to even cleaner constructors/interfaces than C#, because constructors can be as simple as var a = {};, but you also get the magic-methods, rather than the Java list of getX, getY, z, and the headache of trying to remember what's a method and what's a property, when you get to the interface.

Seriously, this is kinda pretty:

var person = {
    person_name : "Bob",
    get name () { return this.person_name; },
    set name (value) {
        console.log("But my parents named me " + this.person_name + "!");
    }
};


person.name;
person.name = "Mark";

But there's an issue here: person.person_name isn't private at all.
Anybody could swoop in and change that.

Not to fret -- get and set don't actually have to operate on properties of the object.

var Person = function (name, age) {
    // we don't need to save these; closures mean they'll be remembered as arguments
    // I'm saving them as `private_*` to illustrate
    var private_name = name,
        private_age  = age;

    var public_interface = {
        get name () { return private_name; },
        set name (value) { console.log("Nope!"); },
        get age () { return private_age; },
        set age (value) { console.log("Nope!"); },
        set court_appointed_name (value) {
            console.log("If I must...");
            private_name = value;
        }
    };

    return public_interface;
};

var mark = Person("Mark", 32);
mark.name; // "Mark";
mark.name = "Bubba"; // log: "Nope!";
mark.name; // "Mark";
mark.court_appointed_name = "Jim-Bob"; // log: "If I must..."
mark.name; // "Jim-Bob"

You could also force assignments to pass in objects, with auth-tokens, et cetera.

mark.name = {
    value : "Jimmy Hoffa",
    requested_by : system.user.id,
    auth : system.user.auth.token
};

This is all fantastic, isn't it?
Why aren't we doing it?

Browser support.

Problem is this requires brand new syntax: all objects are defined as key-value pairs.
Messing with the syntax means any non-supporting browser will crash and burn unless you wrap your entire program in a try/catch (which is performance-suicide).

You could do one try-catch test, and lazy-load the awesome interface, versus fugly workarounds, at page-load, which is the right way to go about it, but now you're developing two versions of the application.

Or three versions, as case may be (new browsers, intermediate-browsers like FF3, and hacks for Ghetto_IE).

Intermediate browsers used {}.__defineGetter__ and {}.__defineSetter__.
Object.prototype.defineProperty (/.defineProperties) are the methods which instill hope of IE compatibility, until you realize that older versions of IE only supported the mutations on DOM objects (attached to the actual DOM tree), hence the headaches. Hooray.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top