Javascript: 类实例化和继承

18 浏览
0 Comments

Javascript: 类实例化和继承

以下是JavaScript中类Animal和其子类Bird的定义的示例(使用TypeScript):

class Animal {
    name: string;
    numberOfLegs: number = 4;
    aboutMe: string;
    constructor (theName: string) {
        this.name = theName;
        this.init();
    }
    init() {
        this.aboutMe = `我是${this.name},有${this.numberOfLegs}条腿`;
    }
}
class Bird extends Animal {
    numberOfLegs: number = 2;
    constructor (theName: string) {
        super(theName);
    }
}
var bird = new Bird('Bimbo');
console.log(bird.aboutMe);

正确的`bird.aboutMe`属性的期望值应为`我是Bimbo,有2条腿`,但实际上你将得到`我是Bimbo,有4条腿`。当你将上述TypeScript代码编译成纯JavaScript代码时,[在这里](http://www.typescriptlang.org/play/index.html)就很明显为什么它不能正确工作。

我的问题是:如何正确编写JavaScript类的初始化逻辑,以使其也适用于继承,并且以我们在其他面向对象语言中习惯的方式工作?TypeScript试图弥补JavaScript和其他面向对象语言之间的差距,但即使在这样一个简单的情况下也失败了。我是否遗漏了什么?

为了证明我对正确结果的期望是有效的,我已经将上述代码重写为PHP:

class Animal {
    protected $name;
    protected $numberOfLegs = 4;
    public $aboutMe;
    public function __construct ($theName) {
        $this->name = $theName;
        $this->init();
    }
    protected function init() {
        $this->aboutMe = "我是{$this->name},有{$this->numberOfLegs}条腿";
    }
}
class Bird extends Animal {
    protected $numberOfLegs = 2;
    public function __construct ($theName) {
        parent::__construct($theName);
    }
}
$bird = new Bird('Bimbo');
echo $bird->aboutMe;

上述PHP代码输出的结果为`我是Bimbo,有2条腿`。

编辑1:当然,我知道如何使上述代码正常工作。我的需求不是使这段简单的代码工作,而是找到一种处理JS类实例初始化的方式,使其在复杂情况下也能正常工作。也许可以通过TypeScript来达到这个目的,我希望它的工作方式与C风格的类定义类似。有办法实现这个目标吗?

编辑2:下面由Emil S. Jørgensen提供了一种非常好的通用解决方案。即使在更长的继承链中也可以工作(例如`Bird extends Animal`和`CityBird extends Bird`)。我在他的答案中添加了一些额外的代码,以显示在每个级别上,您可以重用父(超级)类的`init()`方法,并在需要时添加自己的初始化逻辑:

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

这种解决方案的缺点是在使用严格模式(`'use strict'`)时无法使用,因为在严格模式下,不能在函数上访问`caller`、`callee`和`arguments`属性(参见[这里](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee#Why_was_arguments.callee_removed_from_ES5_strict_mode))。

编辑3:与严格模式和ES6类兼容的解决方案(避免使用严格模式禁用的`callee`)基于比较`this.construct`和类(函数)本身(参见[这里](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor#Description))。只有当这两者相等时,才会启动`init()`方法 - 这意味着`init()`方法只在实例化类的构造函数中调用。以下是从编辑2中重写的代码:

/*
// TYPESCRIPT 
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);

这种解决方案也可以与新的ES6 `class` 语法一起使用,该语法强制在类定义中使用严格模式,并禁止使用`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);

0