以下是JavaScript中Animal
类及其子类Bird
定义的示例(使用TypeScript):
class Animal {
name: string;
numberOfLegs: number = 4;
aboutMe: string;
constructor (theName: string) {
this.name = theName;
this.init();
}
init() {
this.aboutMe = `I'm ${this.name} with ${this.numberOfLegs} legs`;
}
}
class Bird extends Animal {
numberOfLegs: number = 2;
constructor (theName: string) {
super(theName);
}
}
var bird = new Bird('Bimbo');
console.log(bird.aboutMe);
属性bird.aboutMe
的正确预期值为I'm Bimbo with 2 legs
,但实际上您将获得I'm Bimbo with 4 legs
。当您将上述TypeScript代码编译为纯JavaScript here时,很明显为什么这种方法不正确。
我的问题:如何正确编写JavaScript类的初始化逻辑,以便它也可以用于继承,并以我们习惯于其他OO语言的方式工作? TypeScript试图解决JavaScript和其他OO语言之间的这种差距,但即使在这种微不足道的情况下它也会失败。我错过了什么吗?
为了证明我对正确结果的期望是有效的,我已将上述代码重写为PHP:
class Animal {
protected $name;
protected $numberOfLegs = 4;
public $aboutMe;
public function __construct ($theName) {
$this->name = $theName;
$this->init();
}
protected function init() {
$this->aboutMe = "I'm {$this->name} with {$this->numberOfLegs} legs";
}
}
class Bird extends Animal {
protected $numberOfLegs = 2;
public function __construct ($theName) {
parent::__construct($theName);
}
}
$bird = new Bird('Bimbo');
echo $bird->aboutMe;
上述PHP代码回应的结果是I'm Bimbo with 2 legs
编辑1:当然我知道如何使上面的代码正常工作。我的需要是不要让这个简单的代码工作,而是以一种方式来处理JS类实例初始化,使其在复杂的情况下也能正常工作。
也许由于TypeScript我会添加“如果TypeScript试图看起来像C风格的类定义那么它会非常明显,它也像那样”。有没有办法实现这个目标?
编辑2:提出了非常好的通用解决方案here below by Emil S. Jørgensen。即使在较长的继承链(例如Bird extends Animal
和CityBird extends Bird
)的情况下,这也适用。我在他的答案中添加了一些代码,以表明在每个级别上你可以重用父(超级)类init()
并在需要时添加你自己的初始化逻辑:
/*
// TYPESCIPT
class Animal {
static _isInheritable = true;
public name: string;
public numberOfLegs: number = 4;
public aboutMe: string;
constructor(theName: string) {
this.name = theName;
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
console.log("In Animal is ");
this.init();
} else {
console.log("Skipping Animal init() because inherited");
}
}
init() {
console.log("the Animal init() called");
this.aboutMe = `I'm ${this.name} with ${this.numberOfLegs} legs`;
}
}
class Bird extends Animal {
public numberOfLegs: number = 2;
constructor(theName: string) {
super(theName);
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
console.log("In Bird is ");
this.init();
} else {
console.log("Skipping Bird init() because inherited");
}
}
init() {
super.init();
console.log("and also some additionals in the Bird init() called");
}
}
class CityBird extends Bird {
public numberOfLegs: number = 1;
constructor(theName: string) {
super(theName);
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
console.log("In CityBird is ");
this.init();
} else {
console.log("Skipping CityBird init() because inherited");
}
}
init() {
super.init();
console.log("and also some additionals in the CityBird init() called");
}
}
var bird = new CityBird('Bimbo');
console.log(bird.aboutMe);
*/
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Animal = (function () {
function Animal(theName) {
this.numberOfLegs = 4;
this.name = theName;
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
console.log("In Animal is ");
this.init();
}
else {
console.log("Skipping Animal init() because inherited");
}
}
Animal.prototype.init = function () {
console.log("the Animal init() called");
this.aboutMe = "I'm " + this.name + " with " + this.numberOfLegs + " legs";
};
return Animal;
}());
Animal._isInheritable = true;
var Bird = (function (_super) {
__extends(Bird, _super);
function Bird(theName) {
var _this = _super.call(this, theName) || this;
_this.numberOfLegs = 2;
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
console.log("In Bird is ");
_this.init();
}
else {
console.log("Skipping Bird init() because inherited");
}
return _this;
}
Bird.prototype.init = function () {
_super.prototype.init.call(this);
console.log("and also some additionals in the Bird init() called");
};
return Bird;
}(Animal));
var CityBird = (function (_super) {
__extends(CityBird, _super);
function CityBird(theName) {
var _this = _super.call(this, theName) || this;
_this.numberOfLegs = 1;
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
console.log("In CityBird is ");
_this.init();
}
else {
console.log("Skipping CityBird init() because inherited");
}
return _this;
}
CityBird.prototype.init = function () {
_super.prototype.init.call(this);
console.log("and also some additionals in the CityBird init() called");
};
return CityBird;
}(Bird));
var bird = new CityBird('Bimbo');
console.log(bird.aboutMe);
此解决方案的缺点是您无法在'use strict'
模式下使用它,因为caller
,callee
和arguments
属性可能无法在严格模式下访问({ {3}})。
编辑3:严格模式和ES6类兼容解决方案(避免使用严格模式禁止callee
)基于比较this.construct
和类(函数)本身( see)。仅当init()
相等时才启动init()
- 这意味着仅在实例化类的构造函数中调用/*
// TYPESCIPT
class Animal {
public name: string;
public numberOfLegs: number = 4;
public aboutMe: string;
constructor(theName: string) {
this.name = theName;
if (this.constructor === Animal) {
console.log("In Animal is ");
this.init();
} else {
console.log("Skipping Animal init() because inherited");
}
}
init() {
console.log("the Animal init() called");
this.aboutMe = `I'm ${this.name} with ${this.numberOfLegs} legs`;
}
}
class Bird extends Animal {
public numberOfLegs: number = 2;
constructor(theName: string) {
super(theName);
if (this.constructor === Bird) {
console.log("In Bird is ");
this.init();
} else {
console.log("Skipping Bird init() because inherited");
}
}
init() {
super.init();
console.log("and also some additionals in the Bird init() called");
}
}
class CityBird extends Bird {
public numberOfLegs: number = 1;
constructor(theName: string) {
super(theName);
if (this.constructor === CityBird) {
console.log("In CityBird is ");
this.init();
} else {
console.log("Skipping CityBird init() because inherited");
}
}
init() {
super.init();
console.log("and also some additionals in the CityBird init() called");
}
}
var bird = new CityBird('Bimbo');
console.log(bird.aboutMe);
*/
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Animal = (function () {
function Animal(theName) {
this.numberOfLegs = 4;
this.name = theName;
if (this.constructor === Animal) {
console.log("In Animal is ");
this.init();
}
else {
console.log("Skipping Animal init() because inherited");
}
}
Animal.prototype.init = function () {
console.log("the Animal init() called");
this.aboutMe = "I'm " + this.name + " with " + this.numberOfLegs + " legs";
};
return Animal;
}());
var Bird = (function (_super) {
__extends(Bird, _super);
function Bird(theName) {
var _this = _super.call(this, theName) || this;
_this.numberOfLegs = 2;
if (_this.constructor === Bird) {
console.log("In Bird is ");
_this.init();
}
else {
console.log("Skipping Bird init() because inherited");
}
return _this;
}
Bird.prototype.init = function () {
_super.prototype.init.call(this);
console.log("and also some additionals in the Bird init() called");
};
return Bird;
}(Animal));
var CityBird = (function (_super) {
__extends(CityBird, _super);
function CityBird(theName) {
var _this = _super.call(this, theName) || this;
_this.numberOfLegs = 1;
if (_this.constructor === CityBird) {
console.log("In CityBird is ");
_this.init();
}
else {
console.log("Skipping CityBird init() because inherited");
}
return _this;
}
CityBird.prototype.init = function () {
_super.prototype.init.call(this);
console.log("and also some additionals in the CityBird init() called");
};
return CityBird;
}(Bird));
var bird = new CityBird('Bimbo');
console.log(bird.aboutMe);
。这是来自EDIT 2的重写代码:
class
此解决方案也可用于新的ES6 callee
语法,该语法在类定义中强制使用严格模式,因此禁止使用class Animal {
constructor (theName) {
this.name = theName;
this.numberOfLegs = 4;
if (this.constructor === Animal) {
console.log("In Animal is ");
this.init();
} else {
console.log("Skipping Animal init() because inherited");
}
}
init() {
console.log("the Animal init() called");
this.aboutMe = "I'm " + this.name + " with " + this.numberOfLegs + " legs";
}
}
class Bird extends Animal {
constructor (theName) {
super(theName);
this.numberOfLegs = 2;
if (this.constructor === Bird) {
console.log("In Bird is ");
this.init();
} else {
console.log("Skipping Bird init() because inherited");
}
}
init() {
super.init();
console.log("and also some additionals in the Bird init() called");
}
}
class CityBird extends Bird {
constructor (theName) {
super(theName);
this.numberOfLegs = 1;
if (this.constructor === CityBird) {
console.log("In CityBird is ");
this.init();
} else {
console.log("Skipping CityBird init() because inherited");
}
}
init() {
super.init();
console.log("and also some additionals in the CityBird init() called");
}
}
var bird = new CityBird('Bimbo');
console.log(bird.aboutMe);
:
display:none
答案 0 :(得分:1)
最简单的解决方案是从两个构造函数中调用init
。
/*
class Animal {
public name: string;
public numberOfLegs: number = 4;
public aboutMe: string;
constructor(theName: string) {
this.name = theName;
this.init();
}
init() {
console.log("init called");
this.aboutMe = `I'm ${this.name} with ${this.numberOfLegs} legs`;
}
}
class Bird extends Animal {
public name: string;
public numberOfLegs: number = 2;
constructor(theName: string) {
super(theName);
this.init();
}
}
var bird = new Bird('Bimbo');
console.log(bird.aboutMe);
*/
var __extends = (this && this.__extends) || (function() {
var extendStatics = Object.setPrototypeOf ||
({
__proto__: []
}
instanceof Array && function(d, b) {
d.__proto__ = b;
}) ||
function(d, b) {
for (var p in b)
if (b.hasOwnProperty(p)) d[p] = b[p];
};
return function(d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Animal = (function() {
function Animal(theName) {
this.numberOfLegs = 4;
this.name = theName;
this.init();
}
Animal.prototype.init = function() {
console.log("init called");
this.aboutMe = "I'm " + this.name + " with " + this.numberOfLegs + " legs";
};
return Animal;
}());
var Bird = (function(_super) {
__extends(Bird, _super);
function Bird(theName) {
var _this = _super.call(this, theName) || this;
_this.numberOfLegs = 2;
_this.init();
return _this;
}
return Bird;
}(Animal));
var bird = new Bird('Bimbo');
console.log(bird.aboutMe);

JavaScript并不像其他OO语言那样,你必须尊重原型链,以及它所暗示的固有对象创建规则。
如果需要测试继承,可以向基类添加静态属性,并只测试caller
是否继承了所述静态属性:
/*
class Animal {
static _isInheritable = true;
public name: string;
public numberOfLegs: number = 4;
public aboutMe: string;
constructor(theName: string) {
this.name = theName;
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
this.init();
} else {
console.log("Skipped because inherited");
}
}
init() {
console.log("init called");
this.aboutMe = `I'm ${this.name} with ${this.numberOfLegs} legs`;
}
}
class Bird extends Animal {
public name: string;
public numberOfLegs: number = 2;
constructor(theName: string) {
super(theName);
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
this.init();
}
}
}
var bird = new Bird('Bimbo');
console.log(bird.aboutMe);
*/
var __extends = (this && this.__extends) || (function() {
var extendStatics = Object.setPrototypeOf ||
({
__proto__: []
}
instanceof Array && function(d, b) {
d.__proto__ = b;
}) ||
function(d, b) {
for (var p in b)
if (b.hasOwnProperty(p)) d[p] = b[p];
};
return function(d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Animal = (function() {
function Animal(theName) {
this.numberOfLegs = 4;
this.name = theName;
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
this.init();
} else {
console.log("Skipped because inherited");
}
}
Animal.prototype.init = function() {
console.log("init called");
this.aboutMe = "I'm " + this.name + " with " + this.numberOfLegs + " legs";
};
return Animal;
}());
Animal._isInheritable = true;
var Bird = (function(_super) {
__extends(Bird, _super);
function Bird(theName) {
var _this = _super.call(this, theName) || this;
_this.numberOfLegs = 2;
var isInheirited = (arguments.callee.caller != null ? arguments.callee.caller._isInheritable != void 0 : false);
if (!isInheirited) {
_this.init();
}
return _this;
}
return Bird;
}(Animal));
var bird = new Bird('Bimbo');
console.log(bird.aboutMe);

答案 1 :(得分:1)
,这个
class Bird extends Animal {
name: string;
numberOfLegs: number = 2;
constructor (theName: string) {
super(theName);
}
}
相当于
class Bird extends Animal {
name: string;
numberOfLegs: number;
constructor (theName: string) {
super(theName);
this.numberOfLegs = 2;
}
}
溶液:
class Animal {
name: string;
numberOfLegs;
aboutMe: string;
constructor (theName: string, theLegs: number = 4) {
this.name = theName;
this.numberOfLegs = theLegs;
this.init();
}
init() {
this.aboutMe = `I'm ${this.name} with ${this.numberOfLegs} legs`;
}
}
class Bird extends Animal {
constructor (theName: string) {
super(theName, 2);
}
}
var bird = new Bird('Bimbo');
console.log(bird.aboutMe);
当然最好将'aboutMe'视为财产:
class Animal {
name: string;
numberOfLegs;
get aboutMe(): string {
return `I'm ${this.name} with ${this.numberOfLegs} legs`;
}
constructor (theName: string, theLegs: number = 4) {
this.name = theName;
this.numberOfLegs = theLegs;
}
}
答案 2 :(得分:0)
属性bird.aboutMe的正确预期值是我有两条腿的宾博,但实际上你会得到我有4条腿的宾博。
您正在从基类的构造函数中调用函数。并且您希望此函数观察派生类构造函数指定的属性值。但是,派生类构造函数仅在基类构造函数返回后运行。因此,您的期望是不正确的。