JavaScript 使用 defineProperty 的原型继承

JavaScript prototype inheritance with defineProperty

说我有这个 "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"); }

现在我想"child class"使用原型继承:

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

然后我可以实例化汽车或轿车,并同时驾驶。注意对于轿车,Drive 还调用 base class (Car) Drive:

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

是否可以通过属性实现类似的功能?

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", "")); } 
  });

我能想到的唯一想法是这样的:

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

然后显式 get_Make 和 set_Make 可以被覆盖,类似于 Drive。然而,这很笨重。当然,可以将此样板文件提取到辅助函数中,该函数一次性定义了 get_ 和 set_ 方法以及 属性。

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

不过overriding还是有点丑。

您可以使用 Object.getOwnPropertyDescriptor 获取父 属性 的 属性 描述符。
然后你可以使用 .call() 来调用它,例如:

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

一些小技巧:

  • 理想情况下,您应该使用 Object.create 来创建原型,因为它在创建对象时不会调用构造函数
  • 更喜欢使用 Object.defineProperty 而不是直接在原型上创建属性(因此您可以将 enumerable 设置为 false)

如果你可以使用 ES6 类 这会变得更好。
您可以只使用 super 来访问父级 属性:

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