Copying Javascript getters/setters to another prototype object
Asked Answered
S

4

14
// Base class
var Base = function() {
    this._value = 'base';
};
Base.prototype = {
    constructor: Base,
    // By function
    getValue: function() {
        return this._value;
    },
    // By getter
    get value() {
        return this._value;
    }
};

// Sub class extends Base
var Sub = function() {
    this._value = 'sub';
};
Sub.prototype = {
    constructor: Sub
};
// Pass over methods
Sub.prototype.getValue = Base.prototype.getValue;
Sub.prototype.value = Base.prototype.value;

// ---

var mySub = new Sub();
alert(mySub.getValue()); // Returns 'sub'
alert(mySub.value);      // Returns 'undefined'

At first glance it seems that mySub.value should return the same as mySub.getValue(), but as you can see it instead returns undefined. Obviously the getter is not finding the parent scope as the Sub instance (mySub), but rather a non-existent Base instance.

Is there any way around this other than having to assign the same getters onto the new prototype?

Sec answered 18/5, 2011 at 3:53 Comment(2)
I know this doesn't answer your question... but why do you even need the getter? Isn't this unnecessary complexity?Variate
@Michael Well in this example, obviously it's unnecessary, I just wrote it to give a clear idea of the problem I was facing. In my actual application, when getting/setting certain properties I need to trigger other functions, and code.Sec
H
16

A more modern solution is to use the Object.defineProperty since it allows getters and setters to be handled without breaking them.

Only problem is that it takes a descriptor object, so instead of manually making one, use the Object.getOwnPropertyDescriptor function to just get it for you.

var BazValue = Object.getOwnPropertyDescriptor(Base.prototype,'value');

Object.defineProperty(Sub.prototype, 'value', BazValue);
Humidifier answered 5/1, 2016 at 18:45 Comment(2)
This helped me to polyfill MouseEvent.x: Object.defineProperty(MouseEvent.prototype, "x", Object.getOwnPropertyDescriptor(MouseEvent.prototype, "clientX")).Starks
This should be the accepted answer today. However defineProperty/getOwnPropertyDescriptor are from ECMAscript 5.1 which was defined June 2011, one month after the currently accepted answerInfusion
C
11
Sub.prototype.__defineGetter__('value', Base.prototype.__lookupGetter__('value'));

Try that.

Collete answered 18/5, 2011 at 4:0 Comment(2)
defineGetter is nonstandard and is not in ECMA-262 standard. Is there an alternate way of doing this?Dessalines
@TylerCrompton: Object.defineProperty(Sub,'value',Object.getOwnPropertyDescriptor(Base,'value'));Fly
F
6

I think it would work if you assigned

Sub.prototype = new Base()

The issue is that the constructor is never run when you assign it directly from the Base.prototype.value. That value won't exist until you have an instance of the Base class (via new)

This is my typical method for extending Function to achieve inheritance:

Function.prototype.Extend = function(superClass) {
    this.prototype = new superClass();

    this.prototype.getSuperClass = function() {
        return superClass;
    };
    this.getSuperClass = this.prototype.getSuperClass;
    return this;
};

This will properly assign all of the parent classes methods and properties to the child 'class'.

Usage looks like

var Sub = function() {}
Sub.Extend(Base)
Farewell answered 18/5, 2011 at 3:56 Comment(4)
It has never occurred to me to do inheritance like that. What a beautiful, elegant solution!Sec
Thank you :-) Let me find the source (I most surely didn't write it...)Farewell
Woah! this deserves some attention. Great solution.Contemporaneous
The only problem with this form of inheritance is, that it is no inheritance, it's fatalism. All base classes are initialized (new superClass()) on definition time (when the library is loaded which does Sub.Extend(Base)) instead of execution time (when the library is used like new Sub()). Example: Sub1.Extend(Base1) and Sub2.Extend(Base2) But Base1 needs new Sub2() and Base2 needs new Sub1(). No, there is not neccessarily a Cycle if you only run in the initialization once.Infusion
M
2

In addition to Alex Mcp's answer you could add new getters/setters to Sub after extending it using:

Function.prototype.addGetter = function(val,fn){
    this.prototype.__defineGetter__(val,fn);
    return this;    
}
Function.prototype.addSetter = function(val,fn){
    this.prototype.__defineSetter__(val,fn);
    return this;    
}
//example;
Sub.Extend(Base);
Sub.addGetter('date',function(){return +new Date;});

And to add to tylermwashburns answer: you could extend the Function prototype for that:

Function.prototype.copyGetterFrom = function(val,fromConstructor){
    this.prototype.__defineGetter__(
         val
        ,fromConstructor.prototype.__lookupGetter__(val));
    return this;   
}
//usage example.:
Sub.copyGetterFrom('value',Base);
Macklin answered 18/5, 2011 at 4:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.