Javascript:类实例初始化和继承

时间:2017-05-22 09:21:14

标签: javascript oop typescript inheritance

以下是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 AnimalCityBird 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'模式下使用它,因为callercalleearguments属性可能无法在严格模式下访问({ {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

3 个答案:

答案 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条腿的宾博。

您正在从基类的构造函数中调用函数。并且您希望此函数观察派生类构造函数指定的属性值。但是,派生类构造函数仅在基类构造函数返回后运行。因此,您的期望是不正确的。