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