1

Say I have this "class":

function Car()
{
}
Object.defineProperty(Car.prototype, "Make", 
  {
    get:function() { return this._make; }, 
    set:function(value) { this._make = value; } 
  });
Object.prototype.Drive = function Drive() { console.log("Car.Drive"); }

Now I want to make a "child class" using prototype inheritance:

function Sedan()
{
}
Sedan.prototype = new Car();
Sedan.prototype.constructor = Sedan;
Sedan.prototype.Drive = function Drive() { Car.prototype.Drive.call(this); console.log("Sedan.Drive"); }

Then I can instantiate a car or a sedan, and drive both. Notice how with sedans, Drive also calls base class (Car) Drive:

var car = new Car(); car.Drive(); var carMake = car.Make;
var sedan = new Sedan(); sedan.Drive(); var sedanMake = sedan.Make;

Is it possible to achieve something similar with properties?

Object.defineProperty(Sedan.prototype, "Make", 
  { 
    get: function() { return Car.prototype.Make.<<CALL_GETTER>>(this) + " - Sedan"; },
    set: function(value) { Car.prototype.Make.<<CALL_SETTER>>(this, value.replace(" - Sedan", "")); } 
  });

The only idea I could come up with is something like this:

Car.prototype.get_Make = function get_Make() { return this._make; }
Car.prototype.set_Make = function set_Make(value) { this._make = value; }
Object.defineProperty(Car.prototype, "Make", 
  {
    get:function() { return this.get_Make(); }, 
    set:function(value) { this.set_Make(value); } 
  });

Then the explicit get_Make and set_Make can be overridden similar to Drive. However, this is clunky. Sure, this boilerplate can be extracted into a helper function which defines the get_ and set_ methods and the property in one shot.

function DefineVirtualProperty(obj, name, getter, setter)
{
  obj["get_" + name] = getter;
  obj["set_" + name] = setter;
  Object.defineProperty(obj, name, 
    {
      get:function() { return this["get_" + name](); },
      set: function(value) { this["set_" + name](value); }
    });
}

DefineVirtualProperty(Car.prototype, "Make", function() { return this._make; }, function(value) { this._make = value; });

However the overriding still looks a big ugly.

1 Answer 1

3

You can use Object.getOwnPropertyDescriptor to get the property descriptor of the parent property.
Then you can use .call() to invoke it, e.g.:

function Car() {}
Object.defineProperty(Car.prototype, "Make", {
  get() {
    return this._make;
  },
  set(value) {
    this._make = value;
  }
});

function Sedan() {}
Sedan.prototype = Object.create(Car);
Sedan.prototype.constructor = Sedan;

Object.defineProperty(Sedan.prototype, "Make", {
  get() {
    console.log("Sedan Make get");
    let desc = Object.getOwnPropertyDescriptor(Car.prototype, "Make");
    return desc.get.call(this);
  },
  set(value) {
    console.log("Sedan Make set");
    let desc = Object.getOwnPropertyDescriptor(Car.prototype, "Make");
    return desc.set.call(this, value);
  }
});

let sedan = new Sedan();
sedan.Make = 12;
console.log(sedan.Make);

A few minor tips:

  • Ideally you should use Object.create for prototype creation, since it doesn't call the constructor when creating the object
  • Prefer to use Object.defineProperty instead of directly creating properties on the prototype (so you can set enumerable to false)

If you can use ES6 classes this becomes a lot nicer.
You can just use super with them to access the parent property:

class Car {
  get Make() {
    return this._make;
  }

  set Make(value) {
    this._make = value;
  }
}

class Sedan extends Car {
  get Make() {
    console.log("Sedan Make get");
    return super.Make;
  }

  set Make(value) {
    console.log("Sedan Make set");
    super.Make = value;
  }
}


let sedan = new Sedan();
sedan.Make = 12;
console.log(sedan.Make);

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the great answer! The whole getOwnPropertyDescriptor rigamarole is still clunky, but at least you don't end up with get_ and set_ methods. Unless somebody posts a better answer, I'll mark yours as correct. Can't wait to start using ES6 in production. Thanks for Object.create tip. Regarding JS object properties being enumerated by for..in, I don't really care about that to be honest - I only use the for..in on DTO objects that don't have anything else define anyway.
@Mr.TA if you can use es6 classes, the last example would be the "clearest" one, since it simply uses super to access the parent property

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.