Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getter/setter in constructor

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?

like image 915
pimvdb Avatar asked Mar 07 '11 16:03

pimvdb


People also ask

Do getters and setters go inside the constructor?

You should not call getters and setters from the constructor. A constructor constructs the specific class in which it is defined. It is its job to initialise the fields because - well - nothing else will. The only way to guarantee initialising the fields is to assign them.

Can we use setters in constructor?

Where properties have logic, setter logic is usually validation and sometimes change propagation to observers. I'd usually expect the constructor parameters to be checked explicitly at the start of the method, and you wouldn't want any change propagation to occur before an instance is fully created anyway.

Can we use constructor and getter setter in Java?

If you want to create a instance with the value of int parameter other than 0, you can use constructor. if you are using setters, they are actualy methods, so if you have more than one setters then it is better to use constructor.


2 Answers

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(); 
like image 115
Raynos Avatar answered Sep 30 '22 18:09

Raynos


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

like image 26
IAM_AL_X Avatar answered Sep 30 '22 16:09

IAM_AL_X