Inherited value overwriting getter - javascript

I was messing around with classes when I found this weird behaviour:
class A {
test = 1
}
class B extends A {
get test () {
return 2
}
}
const b = new B()
console.log(b.test)
The logical answer to the output should be 2 but when running the code, the output is 1.
Am I missing something? Is this intended javascript behaviour? I could not find anything in the MDN documentation about this.
I found this question which might be related to mine but it did not include any work arounds.

You would have to redefine the value of test inside of B.
class A {
test = 1
}
class B extends A {
test = 2 // redefine
}
const b = new B()
console.log(b.test) // 2
If you check out MDN - References - JavaScript - Functions - getter, you will see that this is not possible.
Description
…
Note the following when working with the get syntax:
…
It must not appear with a data entry for the same property e.g. the following is forbidden
{
x: /* … */, get x() { /* … */ }
}

As pointed out by Mr. Polyshirl and Sebastian Simon, it is not possible to overwrite an inherited property using a getter. My "solution" was to use plain-old functions instead of getters and setters.
So, for example, instead of using
class Example {
method = 'value'
}
const example = new Example()
console.log(example.method)
use
class Example {
#method = 'value'
getMethod () {
return this.#method
}
setMethod (method) {
this.#method = method
// Use return this to allow for method chaining
return this
}
}
const example = new Example()
console.log(example.getMethod())
or
class Example {
getMethod () {
return 'value'
}
}
const example = new Example()
console.log(example.getMethod())

Related

Unable to have get and set method in Object Constructor (not in class) in JavaScript? [duplicate]

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 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
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
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);
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)
#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.

Cannot call subclass function in Gnome Shell Extension ES6

I am trying to write a GNOME shell extension. I have a class which extends a GNOME shell type. Whenever I call class method on this subclass, it throws an error. If I do not extend the class, then the method is invoked without a problem.
Example:
This works
var Dummy = class Dummy {
constructor() {}
foo() {
log("foo!")
}
};
let d = new Dummy();
d.foo();
--> RESULT: log shows "foo!"
However, this does not work
const St = imports.gi.St;
var Dummy = class Dummy extends St.Bin {
constructor() {
super();
}
foo() {
log("foo!")
}
};
let d = new Dummy();
d.foo();
--> RESULT: TypeError: d.foo is not a function
I'm not very proficient in Javascript, and I'm having trouble Googling my way out of this situation.
Any help is appreciated. Thanks!
Unfortunately there is a bit of mix of Class styles, for couple reasons, but namely because GJS existed before ES6 classes. You should avoid usage of the Lang module if at all possible.
Sometimes it won't be clear if the object you're subclassing is a native JS class or a GObject. Any object coming from imports.gi (GObject Introspection) will be a GObject, while objects coming from say imports.ui.popupMenu (an import in GNOME Shell) could be either and you may have to check the source.
If you are subclassing a GObject, this is the proper way to subclass:
var Bin = GObject.registerClass({
// This is optional, but useful to avoid namespace collisions. By
// convention you prefix your GType's like StBin or GtkBin.
GTypeName: 'PohoBin',
// Custom GProperties
Properties: {
'example-prop': GObject.ParamSpec.string(
'example-prop', // property name
'ExampleProperty', // nickname
'An example read write property', // description
(GObject.ParamFlags.READWRITE | // READABLE/READWRITE/CONSTRUCT/etc
GObject.ParamFlags.CONSTRUCT),
null // implement defaults manually
)
}
}, class Bin extends St.Bin {
// Currently GObjects use `constructor()` for bootstrapping; you
// should not call this function in GObject subclasses.
//
// Instead, you should chain-up to the parent class's _init() function,
// which takes a dictionary of properties
_init(params = {}) {
// Chaining up. If you need to, you can use this opportunity to
// pull properties from the #params dictionary
super._init(params);
}
get example_prop() {
if (this._example_prop === undefined)
this._example_prop = null;
return this._example_prop;
}
set example_prop(value) {
if (this.example_prop !== value) {
this._example_prop = value;
this.notify('example-prop');
}
}
foo() {
log(this.example_prop);
}
});
let obj = new Bin({
visible: true, // Clutter.Actor property
x_align: St.Align.MIDDLE, // St.Bin property
example_prop: 'example' // custom property
});
obj.foo();
// expected output: 'example'
There is more information about mapping GObject to JavaScript here:
https://gitlab.gnome.org/GNOME/gjs/wikis/Mapping
There are also a number of more complete examples here:
https://gitlab.gnome.org/GNOME/gjs/tree/master/examples

Get method name from within a typescript method

I want to get the name of the current method from within an instance method of a class in Typescript.
(Pseudocode, doesn't work):
class Foo {
bar() {
console.log(something); //what should something be?
}
}
new Foo().bar();
I expect 'something' to return 'bar'. I realize that this can give me the class, and I could somehow get the class and its attributes from it, but I do not know how to get 'this function' (i.e, the method bar, not the class Foo).
I have seen several other questions related to finding the class name, etc. but not one that addresses getting the current method name.
Besides the arguments.callee.name there is no straightforward way of getting this.
I propose 2 other methods:
Use decorators to inject the method name:
function annotateName(target, name, desc) {
var method = desc.value;
desc.value = function () {
var prevMethod = this.currentMethod;
this.currentMethod = name;
method.apply(this, arguments);
this.currentMethod = prevMethod;
}
}
class Foo {
currentMethod: string;
#annotateName
bar() {
alert(this.currentMethod);
this.tux();
alert(this.currentMethod);
}
#annotateName
tux() {
alert(this.currentMethod);
}
}
new Foo().bar();
The downside is that you have to annotate all the functions you want to get the name from. You could instead just annotate the class and in the decorator you would iterate over all prototype functions and apply the same idea.
My second option is not standardised and need more care to get consistent results across browsers. It relies on creating an Error object and getting it's stack trace.
class Foo {
bar() {
console.log(getMethodName());
}
}
function getMethodName() {
var err = new Error();
return /at \w+\.(\w+)/.exec(err.stack.split('\n')[2])[1] // we want the 2nd method in the call stack
}
new Foo().bar();
Not sure if this would help, but:
class Foo {
bar() {
console.log(Object.getOwnPropertyNames(Foo.prototype)); // ["constructor", "bar"]
}
}
new Foo().bar();
Here is my solution to get the method name.
/**
* #description Get log invoker name
* #return {string} The invoker name
*/
private static callerName(): string {
try {
throw new Error();
} catch (e) {
try {
return e.stack.split('at ')[3].split(' ')[0];
} catch (e) {
return '';
}
}
}
I was looking for a solution as well, try this:
class Foo {
bar() {
console.log(this.bar.name); // <-- Print out the function name.
}
}
new Foo().bar();
What is nice is that you'll get an error if you change the function name, but forget to update the console statement.
Keep in mind that during compilation and minification you might lose the actual name of what you're trying to use. You might consider looking into the ts-nameof babel macro that reads the name of virtually anything during compilation and returns it's actual string representation.
for class name - Foo.name
for method name - this.bar.name
Just to answer the question with another interesting take, you could do (but shouldn't do) something like this:
class Foo {
constructor(private http: HttpClient) {
const apiUrl = 'http://myapi.com/api/';
{
const functionName = 'getBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
{
const functionName = 'postBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
{
const functionName= 'putBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
{
const functionName= 'deleteBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
}
}
It certainly is not an elegant solution, and I can't really imagine a good use case for doing something like this, as I'm pretty sure the compiler doesn't recognize new Foo(http).deleteBar(). Maybe someone can come up with an elegant solution with this idea, I'll leave that as an experiment for someone.
But with this pattern (if you employ some kind of devops scaffolding or "strong copy-paste skills") you can always access your method's name via functionName
function getFunctionName() {
return getFunctionName.caller.name
}
function foobar() {
console.log(getFunctionName())
}
foobar() // logs 'foobar' as the currently running function
You can use the .caller property:
A Function object's caller property accessor property represents the function that invoked the specified function. For strict, async function, and generator function callers, accessing the caller property throws an exception.
Although non standard, in my experience the caller property is supported everywhere I have used it (mostly node.js). Check for compatibility before using it. I have only every used it for debugging purposes. For more information, please see
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller

Split a Javascript class (ES6) over multiple files?

I have a Javascript class (in ES6) that is getting quite long. To organize it better I'd like to split it over 2 or 3 different files. How can I do that?
Currently it looks like this in a single file:
class foo extends bar {
constructor(a, b) {} // Put in file 1
methodA(a, b) {} // Put in file 1
methodB(a, b) {} // Put in file 2
methodC(a, b) {} // Put in file 2
}
Thanks!
When you create a class
class Foo extends Bar {
constructor(a, b) {
}
}
you can later add methods to this class by assigning to its prototype:
// methodA(a, b) in class Foo
Foo.prototype.methodA = function(a, b) {
// do whatever...
}
You can also add static methods similarly by assigning directly to the class:
// static staticMethod(a, b) in class Foo
Foo.staticMethod = function(a, b) {
// do whatever...
}
You can put these functions in different files, as long as they run after the class has been declared.
However, the constructor must always be part of the class declaration (you cannot move that to another file). Also, you need to make sure that the files where the class methods are defined are run before they are used.
Here's my solution. It:
uses regular modern classes and .bind()ing, no prototype. (EDIT: Actually, see the comments for more on this, it may not be desirable.)
works with modules. (I'll show an alternative option if you don't use modules.)
supports easy conversion from existing code.
yields no concern for function order (if you do it right).
yields easy to read code.
is low maintenance.
unfortunately does not play well with static functions in the same class, you'll need to split those off.
First, place this in a globals file or as the first <script> tag etc.:
BindToClass(functionsObject, thisClass) {
for (let [ functionKey, functionValue ] of Object.entries(functionsObject)) {
thisClass[functionKey] = functionValue.bind(thisClass);
}
}
This loops through an object and assigns and binds each function, in that object, by its name, to the class. It .bind()'s it for the this context, so it's like it was in the class to begin with.
Then extract your function(s) from your class into a separate file like:
//Use this if you're using NodeJS/Webpack. If you're using regular modules,
//use `export` or `export default` instead of `module.exports`.
//If you're not using modules at all, you'll need to map this to some global
//variable or singleton class/object.
module.exports = {
myFunction: function() {
//...
},
myOtherFunction: function() {
//...
}
};
Finally, require the separate file and call BindToClass like this in the constructor() {} function of the class, before any other code that might rely upon these split off functions:
//If not using modules, use your global variable or singleton class/object instead.
let splitFunctions = require('./SplitFunctions');
class MySplitClass {
constructor() {
BindToClass(splitFunctions, this);
}
}
Then the rest of your code remains the same as it would if those functions were in the class to begin with:
let msc = new MySplitClass();
msc.myFunction();
msc.myOtherFunction();
Likewise, since nothing happens until the functions are actually called, as long as BindToClass() is called first, there's no need to worry about function order. Each function, inside and outside of the class file, can still access any property or function within the class, as usual.
I choose to have all privte variables/functions in an object called private, and pass it as the first argument to the external functions.
this way they have access to the local variables/functions.
note that they have implicit access to 'this' as well
file: person.js
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
// delegate getAge to PersonGetAge in an external file
// pass this,privates,args
this.getAge=function(...args) {
return PersonGetAge.apply(this,[privates].concat(args));
}
// delegate setAge to PersonSetAge in an external file
// pass this,privates,args
this.setAge=function(...args) {
return PersonSetAge.apply(this,[privates].concat(args));
}
}
file: person_age_functions.js
exports.PersonGetAge =function(privates)
{
// note: can use 'this' if requires
return privates.age;
}
exports.PersonSetAge =function(privates,age)
{
// note: can use 'this' if requires
privates.age=age;
}
file: main.js
const { Person } = require('./person.js');
let me = new Person();
me.setAge(17);
console.log(`I'm ${me.getAge()} years old`);
output:
I'm 17 years old
note that in order not to duplicate code on person.js, one can assign all functions in a loop.
e.g.
person.js option 2
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
{
// assign all external functions
let funcMappings={
getAge:PersonGetAge,
setAge:PersonSetAge
};
for (const local of Object.keys(funcMappings))
{
this[local]=function(...args) {
return funcMappings[local].apply(this,[privates].concat(args));
}
}
}
}
You can add mixins to YourClass like this:
class YourClass {
ownProp = 'prop'
}
class Extension {
extendedMethod() {
return `extended ${this.ownProp}`
}
}
addMixins(YourClass, Extension /*, Extension2, Extension3 */)
console.log('Extended method:', (new YourClass()).extendedMethod())
function addMixins() {
var cls, mixin, arg
cls = arguments[0].prototype
for(arg = 1; arg < arguments.length; ++ arg) {
mixin = arguments[arg].prototype
Object.getOwnPropertyNames(mixin).forEach(prop => {
if (prop == 'constructor') return
if (Object.getOwnPropertyNames(cls).includes(prop))
throw(`Class ${cls.constructor.name} already has field ${prop}, can't mixin ${mixin.constructor.name}`)
cls[prop] = mixin[prop]
})
}
}
TypeScript Solution
foo-methods.ts
import { MyClass } from './class.js'
export function foo(this: MyClass) {
return 'foo'
}
bar-methods.ts
import { MyClass } from './class.js'
export function bar(this: MyClass) {
return 'bar'
}
class.ts
import * as barMethods from './bar-methods.js'
import * as fooMethods from './foo-methods.js'
const myClassMethods = { ...barMethods, ...fooMethods }
class _MyClass {
baz: string
constructor(baz: string) {
this.baz = baz
Object.assign(this, myClassMethods);
}
}
export type MyClass = InstanceType<typeof _MyClass> &
typeof myClassMethods;
export const MyClass = _MyClass as unknown as {
new (
...args: ConstructorParameters<typeof _MyClass>
): MyClass;
};
My solution is similar to the one by Erez (declare methods in files and then assign methods to this in the constructor), but
it uses class syntax instead of declaring constructor as a function
no option for truly private fields - but this was not a concern for this question anyway
it does not have the layer with the .apply() call - functions are inserted into the instance directly
one method per file: this is what works for me, but the solution can be modified
results in more concise class declaration
1. Assign methods in constructor
C.js
class C {
constructor() {
this.x = 1;
this.addToX = require('./addToX');
this.incX = require('./incX');
}
}
addToX.js
function addToX(val) {
this.x += val;
return this.x;
}
module.exports = addToX;
incX.js
function incX() {
return this.addToX(1);
}
module.exports = incX;
2. Same, but with instance fields syntax
Note that this syntax is a Stage 3 proposal as of now.
But it works in Node.js 14 - the platform I care about.
C.js
class C {
x = 1;
addToX = require('./addToX');
incX = require('./incX');
}
Test
const c = new C();
console.log('c.incX()', c.incX());
console.log('c.incX()', c.incX());

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?
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
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
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);
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)
#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.

Categories

Resources