开发者

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:

开发者_如何学Python
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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜