43

I recently read about the fact that there is a possibility of defining getters/setters in JavaScript. It seems extremely helpful - the setter is a kind of 'helper' which can parse the value to be set first, before actually setting it.

For example, I currently have this code:

var obj = function(value) {
    var test = !!value; // 'test' has to be a boolean
    return {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new obj(true);

This code always converts value to a boolean. So if you code instance.test = 0, then instance.test === false.

However, for this to work you have to actually return an object, which means that the new instance is not of type obj but just is a plain object. This means that changing the prototype of obj has no effect on instances. For example, this does not work - instance.func is undefined:

obj.prototype.func = function() { console.log(this.value); };

because instance is not of type obj. To get the prototype functions work, I guess I should not return a plain object, but rather not return anything so that instance would just be of type obj, like a regular constructor works.

The problem then is how to implement getters/setters? I can only find articles describing how to add these to an object, not as being part of the constructor of a custom type.

So how do I implement getters/setters in the constructor so as to be able to both use getters/setters and extending the prototype?

pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • 1
    @Pointy watch out with those thing. Yes there slower but I doubt it can cause a notable bottleneck in almost all cases. – Raynos Mar 07 '11 at 16:42
  • 1
    Thanks for that article. I haven't experienced any performance breakdowns though. – pimvdb Mar 07 '11 at 16:45
  • 1
    @Pointy That link is dead now, could you provide an alternative? Curious what it entails. – ApathyBear Mar 25 '15 at 04:57
  • @ApathyBear darn it, it was dumb to use a link shortener for that. I think it was something about runtime performance. Somebody asked a question with a jsperf about why getter/setter functions are so slow just recently (they're not *that* slow, just slower than you'd think they'd be). – Pointy Mar 25 '15 at 13:05
  • @Pointy Link is broken :-) – Royi Namir Nov 15 '15 at 14:49
  • @RoyiNamir yes, I know; it's been dead for a while and I wasn't able to locate it again. Sorry about that. (Since I posted the comment in 2011, whatever the article said it's probably irrelevant now because that's such a long time ago in the JavaScript world.) – Pointy Nov 15 '15 at 15:59
  • 2
    @Pointy Here's an archive of the dead link from the Internet Archives: https://web.archive.org/web/20110310163319/http://labs.transloc.com/?p=21 – Ken Bellows Mar 24 '16 at 16:35

6 Answers6

62

You can't do that.

You can set setter/getters for properties of objects though. I advice you use ES5 Object.defineProperties though. of course this only works in modern browsers.

var obj = function() {
    ...
    Object.defineProperties(this, {
        "test": {
             "get": function() { ... },
             "set": function() { ... }
        }
    });
}

obj.prototype.func = function() { ... }

var o = new obj;
o.test;
o.func();
Raynos
  • 166,823
  • 56
  • 351
  • 396
20

Usually you want class methods. The answer by @Raynos on May 7, 2011 gets the job done, but it defines an instance method, not a class method.

The following illustrates a class definition with a the getter and setter being part of the class. This definition is a lot like the answer by @Raynos, but with two differences in the code: (1) The "defineProperties()" action has been moved out of the constructor. (2) The argument to "defineProperties()"as been changed from the instance object "this", to the constructor's prototype object.

function TheConstructor(side) {
  this.side = side;
}

Object.defineProperties(TheConstructor.prototype, {
        area: {
             get: function()    { return this.side * this.side; }
            ,set: function(val) { this.side = Math.sqrt(val);   }
        }
});

// Test code:

var anInstance = new TheConstructor(2);
console.log("initial  Area:"+anInstance.area);
anInstance.area = 9;
console.log("modified Area:"+anInstance.area);

Which produces these results:

initial  Area:4
modified Area:9

Although usually the distinction between class versus instance definition is just a matter of style, there is a purpose to good style, and there is a case where the distinction matters: the memoized getter. The purpose for a memoized getter is described here: Smart/self-overwriting/lazy getters

Define the getter at the class level when the memoized value is to pertain to the entire class. For example, a configuration file should be read only once; the resulting values should then apply for the duration of the program. The following sample code defines a memoized getter at the class level.

function configureMe() {
  return 42;
}

Object.defineProperties(TheConstructor.prototype, {
    memoizedConfigParam: {
        get: function() {
            delete TheConstructor.prototype.memoizedConfigParam;
            return TheConstructor.prototype.memoizedConfigParam = configureMe();
        }
        ,configurable:  true
    }
});

// Test code:

console.log("memoizedConfigParam:"+anInstance.memoizedConfigParam);

Produces:

memoizedConfigParam:42

As can be seen in the example, memoized getters have the characteristic that the getter function deletes itself, then replaces itself with a simple value that (presumably) will never change. Note that 'configurable' must be set to 'true'.

Define the getter at the instance level when the memoized value depends upon the contents of instance. The definition moves inside the constructor, and the object of attention is 'this'.

function TheConstructorI(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            return this.memoizedCalculation = this.expensiveOperation();
        }
        ,configurable:  true
    }
  });
}

TheConstructorI.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instance2 = new TheConstructorI(2);
var instance3 = new TheConstructorI(3);

console.log("memoizedCalculation 2:"+instance2.memoizedCalculation);
console.log("memoizedCalculation 3:"+instance3.memoizedCalculation);

Produces:

memoizedCalculation 2:8
memoizedCalculation 3:27

If you want to guarantee (rather than presume) that the memoized value will never be changed, the 'writable' attribute needs to be changed. That makes the code a bit more complicated.

function TheConstructorJ(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            Object.defineProperty( this, 'memoizedCalculation'
              ,{  value    : this.expensiveOperation()
                 ,writable : false
              });
            return this.memoizedCalculation;
        }
        ,configurable:  true
    }
  });
}

TheConstructorJ.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instanceJ = new TheConstructorJ(2);

console.log("memoizedCalculation:"+instanceJ.memoizedCalculation);
instanceJ.memoizedCalculation = 42;  // results in error

Produces:

memoizedCalculation:8
>Uncaught TypeError: Cannot assign to read only property 'memoizedCalculation' of object '#<TheConstructorJ>'

The OP's original question, from March 7, 2011, presented basic getter and setter syntax, noted that it worked on an object but not on 'this', and asked how to define getters and setters within a constructor. In addition to all the examples above, there is also a "cheap-shot" way of doing it: create a new object within the constructor, like the OP did, but then assign the object to be a member within 'this'. So, the original code would look like this:

var MyClass = function(value) {
    var test = !!value; // 'test' has to be a boolean
    this.data = {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new MyClass(true);

// But now 'data' is part of the access path
instance.data.test = 0;
console.log(instance.data.test);

Produces:

false

Believe it or not, I have actually run into situations where this "cheap-shot" is the best solution. Specifically, I used this technique when I had records from several tables encapsulated within a single class, and wanted to present a unified view as though they were a single record called 'data'.

Have fun.

IAM_AL_X

IAM_AL_X
  • 1,221
  • 11
  • 12
9

Update for ES6 -- have a look at section 19.3.1 of Alex Rauschmayer's book Exploring ES6 http://exploringjs.com/es6/ch_maps-sets.html#sec_weakmaps-private-data which demonstrates how to use WeakMaps with getters and setters to hold private data. Combining with section 16.2.2.3 http://exploringjs.com/es6/ch_classes.html#leanpub-auto-getters-and-setters would result in something like

# module test_WeakMap_getter.js
var _MyClassProp = new WeakMap();
class MyClass {
    get prop() {
        return _MyClassProp.get( this ); 
    }
    set prop(value) {
        _MyClassProp.set( this, value );
    }
}
var mc = new MyClass();
mc.prop = 5 ;
console.log( 'My value is', mc.prop );

$ node --use_strict test_WeakMap_getter.js 
My value is 5
Jeff
  • 2,095
  • 25
  • 18
4
function Obj(value){
    this.value = !!value;
}

Obj.prototype = {
    get test () {
        return this.value;``
    },
    set test (value) {
        this.value = !!this.value;
    }
};
var obj = new Obj(true);
Jason Nichols
  • 3,739
  • 3
  • 29
  • 49
  • 3
    Please add a description of what your code is doing to help the OP understand it better. – Suever Apr 10 '16 at 15:58
  • 2
    Welcome to SO! When posting answers, it is important to explain your code and explain how one might use it – bren Nov 28 '16 at 03:15
  • Funny, I scrolled all of the top answers because the text explaining the code is utterly a waste of time in this case, your code explains itself, and is exactly what the OP probably wish he used. Thanks. – Leathan Nov 25 '17 at 22:01
  • 1
    Not a good practice to replace your prototype by default the obj.prototype would have a property constructor that would be pointing back to Obj. You would need to add that if you are using instanceOf to check if the objects created with Obj are its instances. More over that would be very explicit and there ought to be a better way to do it. – Rahil Ahmad Jun 17 '19 at 03:53
1

@Alex I see it as more option and more power, programming is art, @Nat share his finding with us, and for that I thank him. Maybe someone want to do it that way.

I'm sure the setter version is the same but just changing that g to a s.

i.g:

function Constructor(input){
     this.input = input;
}

Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

Object.__defineSetter__.call(Constructor.prototype, "bar", function(foo){
    return this.input *= foo;
});

var test = new Constructor(5);
console.log(test.value); // 10
test.bar = 5;
console.log(test.input); //25

With that said, this feature is deprecated, advices to not to use in production coding.

1

I know this might be extremely late but I figured out a different way to accomplish what you want and for the sake of people, like myself, googling for an answer to this here it is.

function Constructor(input){
     this.input = input;
}
Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

var test = new Constructor(5);
alert(test.value) // 10

I've tested this in chrome, safari, mobile safari, firefox and they all work (latest versions of course)

  • I'm not sure this works cross-browser, but even if it does it's really gross looking. Is there any reason why you would go this route instead of using `defineProperties()` as @Raynos did? – CatDadCode Jun 20 '13 at 23:05
  • 5
    Fair warning, this syntax is [deprecated](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__). Going forward for modern browsers the appropriate syntax is `Object.defineProperty(someObject, 'somePropertyName', { get: function(){}, set: function(val){} })`. – Hallmanac Feb 28 '15 at 17:24