简体   繁体   中英

JavaScript prototype inheritance with defineProperty

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM