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?
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.
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.
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.
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();
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With