私はjavascriptで継承を実装しようとしています。それをサポートするために、次の最小限のコードを思いつきました。
function Base(){
this.call = function(handler, args){
handler.call(this, args);
}
}
Base.extend = function(child, parent){
parent.apply(child);
child.base = new parent;
child.base.child = child;
}
専門家、これで十分かどうか、または私が見逃したかもしれない他の重要な問題を教えてください。直面した同様の問題に基づいて、他の変更を提案してください。
完全なテストスクリプトを次に示します。
function Base(){
this.call = function(handler, args){
handler.call(this, args);
}
this.superalert = function(){
alert('tst');
}
}
Base.extend = function(child, parent){
parent.apply(child);
child.base = new parent;
child.base.child = child;
}
function Child(){
Base.extend(this, Base);
this.width = 20;
this.height = 15;
this.a = ['s',''];
this.alert = function(){
alert(this.a.length);
alert(this.height);
}
}
function Child1(){
Base.extend(this, Child);
this.depth = 'depth';
this.height = 'h';
this.alert = function(){
alert(this.height); // display current object height
alert(this.a.length); // display parents array length
this.call(this.base.alert);
// explicit call to parent alert with current objects value
this.call(this.base.superalert);
// explicit call to grandparent, parent does not have method
this.base.alert(); // call parent without overriding values
}
}
var v = new Child1();
v.alert();
alert(v.height);
alert(v.depth);
ECMAScript 5 でjavascriptの継承を実装するには、オブジェクトのプロトタイプを定義し、Object.create
継承します。必要なだけプロパティを追加/上書きすることもできます。
例:
/**
* Transform base class
*/
function Transform() {
this.type = "2d";
}
Transform.prototype.toString = function() {
return "Transform";
}
/**
* Translation class.
*/
function Translation(x, y) {
// Parent constructor
Transform.call(this);
// Public properties
this.x = x;
this.y = y;
}
// Inheritance
Translation.prototype = Object.create(Transform.prototype);
// Override
Translation.prototype.toString = function() {
return Transform.prototype.toString() + this.type + " Translation " + this.x + ":" + this.y;
}
/**
* Rotation class.
*/
function Rotation(angle) {
// Parent constructor
Transform.call(this);
// Public properties
this.angle = angle;
}
// Inheritance
Rotation.prototype = Object.create(Transform.prototype);
// Override
Rotation.prototype.toString = function() {
return Transform.prototype.toString() + this.type + " Rotation " + this.angle;
}
// Tests
translation = new Translation(10, 15);
console.log(translation instanceof Transform); // true
console.log(translation instanceof Translation); // true
console.log(translation instanceof Rotation); // false
console.log(translation.toString()) // Transform2d Translation 10:15
ジョンのように、クロックフォードのソリューションは複雑すぎると思います。両方が説明しているように見えるよりも、JavaScriptの継承を取得する方がはるかに簡単です。考慮してください:
//Classes
function A() {
B.call(this);
}
function B() {
C.call(this);
this.bbb = function() {
console.log("i was inherited from b!");
}
}
function C() {
D.call(this);
}
function D() {
E.call(this);
}
function E() {
//instance property
this.id = Math.random()
}
//set up the inheritance chain (order matters)
D.prototype = new E();
C.prototype = new D();
B.prototype = new C();
A.prototype = new B();
//Add custom functions to each
A.prototype.foo = function() {
console.log("a");
};
B.prototype.bar = function() {
console.log("b");
};
C.prototype.baz = function() {
console.log("c");
};
D.prototype.wee = function() {
console.log("d");
};
E.prototype.woo = function() {
console.log("e");
};
//Some tests
a = new A();
a.foo();
a.bar();
a.baz();
a.wee();
a.woo();
console.log(a.id);
a.bbb();
console.log(a instanceof A);
console.log(a instanceof B);
console.log(a instanceof C);
console.log(a instanceof D);
console.log(a instanceof E);
var b = new B();
console.log(b.id)
私のブログ で上記のソリューションの完全な説明を書きました。
JSオブジェクトで遊んでいると、よりミニマルな解決策が見つかりました:-)お楽しみください!
function extend(b,a,t,p) { b.prototype = a; a.apply(t,p); }
例
function A() {
this.info1 = function() {
alert("A");
}
}
function B(p1,p2) {
extend(B,A,this);
this.info2 = function() {
alert("B"+p1+p2);
}
}
function C(p) {
extend(C,B,this,["1","2"]);
this.info3 = function() {
alert("C"+p);
}
}
var c = new C("c");
c.info1(); // A
c.info2(); // B12
c.info3(); // Cc
これが最も簡単で、JSの継承を理解する最も簡単な方法を願っています。この例は、PHPプログラマー向けです。
function Mother(){
this.canSwim = function(){
console.log('yes');
}
}
function Son(){};
Son.prototype = new Mother;
Son.prototype.canRun = function(){
console.log('yes');
}
今、息子は1つのオーバーライドされたメソッドと1つの新しいメソッドを持っています
function Grandson(){}
Grandson.prototype = new Son;
Grandson.prototype.canPlayPiano = function(){
console.log('yes');
};
Grandson.prototype.canSwim = function(){
console.log('no');
}
孫には2つのオーバーライドされたメソッドと1つの新しいメソッドがあります
var g = new Grandson;
g.canRun(); // => yes
g.canPlayPiano(); // => yes
g.canSwim(); // => no
関数の代わりにオブジェクトを使用しない理由:
var Base = {
superalert : function() {
alert('tst');
}
};
var Child = Object.create(Base);
Child.width = 20;
Child.height = 15;
Child.a = ['s',''];
Child.childAlert = function () {
alert(this.a.length);
alert(this.height);
}
var Child1 = Object.create(Child);
Child1.depth = 'depth';
Child1.height = 'h';
Child1.alert = function () {
alert(this.height);
alert(this.a.length);
this.childAlert();
this.superalert();
};
そして、このように呼び出します:
var child1 = Object.create(Child1);
child1.alert();
このアプローチは、関数を使用する場合よりもはるかにクリーンです。関数での継承がJSでそれを行う適切な方法ではない理由を説明するこのブログを見つけました: http://davidwalsh.name/javascript-objects-deconstruction
編集
var Childは次のように書くこともできます。
var Child = Object.create(Base, {
width : {value : 20},
height : {value : 15, writable: true},
a : {value : ['s', ''], writable: true},
childAlert : {value : function () {
alert(this.a.length);
alert(this.height);
}}
});
Lorenzo Polidori'sanswer で説明されている標準的なプロトタイプの継承方法に基づいた、私のソリューションです。
最初に、これらのヘルパーメソッドを定義することから始めます。これにより、後から理解しやすくなり、読みやすくなります。
_Function.prototype.setSuperclass = function(target) {
// Set a custom field for keeping track of the object's 'superclass'.
this._superclass = target;
// Set the internal [[Prototype]] of instances of this object to a new object
// which inherits from the superclass's prototype.
this.prototype = Object.create(this._superclass.prototype);
// Correct the constructor attribute of this class's prototype
this.prototype.constructor = this;
};
Function.prototype.getSuperclass = function(target) {
// Easy way of finding out what a class inherits from
return this._superclass;
};
Function.prototype.callSuper = function(target, methodName, args) {
// If methodName is ommitted, call the constructor.
if (arguments.length < 3) {
return this.callSuperConstructor(arguments[0], arguments[1]);
}
// `args` is an empty array by default.
if (args === undefined || args === null) args = [];
var superclass = this.getSuperclass();
if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found.");
var method = superclass.prototype[methodName];
if (typeof method != "function") throw new TypeError("TypeError: Object " + superclass.prototype + " has no method '" + methodName + "'");
return method.apply(target, args);
};
Function.prototype.callSuperConstructor = function(target, args) {
if (args === undefined || args === null) args = [];
var superclass = this.getSuperclass();
if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found.");
return superclass.apply(target, args);
};
_
これで、SubClass.setSuperclass(ParentClass)
を使用してクラスのスーパークラスを設定できるだけでなく、SubClass.callSuper(this, 'functionName', [argument1, argument2...])
を使用してオーバーライドされたメソッドを呼び出すこともできます。
_/**
* Transform base class
*/
function Transform() {
this.type = "2d";
}
Transform.prototype.toString = function() {
return "Transform";
}
/**
* Translation class.
*/
function Translation(x, y) {
// Parent constructor
Translation.callSuper(this, arguments);
// Public properties
this.x = x;
this.y = y;
}
// Inheritance
Translation.setSuperclass(Transform);
// Override
Translation.prototype.toString = function() {
return Translation.callSuper(this, 'toString', arguments) + this.type + " Translation " + this.x + ":" + this.y;
}
/**
* Rotation class.
*/
function Rotation(angle) {
// Parent constructor
Rotation.callSuper(this, arguments);
// Public properties
this.angle = angle;
}
// Inheritance
Rotation.setSuperclass(Transform);
// Override
Rotation.prototype.toString = function() {
return Rotation.callSuper(this, 'toString', arguments) + this.type + " Rotation " + this.angle;
}
// Tests
translation = new Translation(10, 15);
console.log(translation instanceof Transform); // true
console.log(translation instanceof Translation); // true
console.log(translation instanceof Rotation); // false
console.log(translation.toString()) // Transform2d Translation 10:15
_
確かに、ヘルパー関数を使用しても、ここの構文はかなり厄介です。ありがたいことに、ECMAScript 6では、より簡潔にするために、いくつかの構文糖( maximally minimal classes )が追加されました。例えば。:
_/**
* Transform base class
*/
class Transform {
constructor() {
this.type = "2d";
}
toString() {
return "Transform";
}
}
/**
* Translation class.
*/
class Translation extends Transform {
constructor(x, y) {
super(); // Parent constructor
// Public properties
this.x = x;
this.y = y;
}
toString() {
return super(...arguments) + this.type + " Translation " + this.x + ":" + this.y;
}
}
/**
* Rotation class.
*/
class Rotation extends Transform {
constructor(angle) {
// Parent constructor
super(...arguments);
// Public properties
this.angle = angle;
}
toString() {
return super(...arguments) + this.type + " Rotation " + this.angle;
}
}
// Tests
translation = new Translation(10, 15);
console.log(translation instanceof Transform); // true
console.log(translation instanceof Translation); // true
console.log(translation instanceof Rotation); // false
console.log(translation.toString()) // Transform2d Translation 10:15
_
ECMAScript 6はこの時点ではまだドラフト段階であり、私が知る限り、どの主要なWebブラウザーにも実装されていないことに注意してください。ただし、必要に応じて Traceurコンパイラ のようなものを使用して_ECMAScript 6
_を従来の_ECMAScript 5
_ベースのJavaScriptにコンパイルできます。 Traceur here を使用してコンパイルした上記の例を見ることができます。
//This is an example of how to override a method, while preserving access to the original.
//The pattern used is actually quite simple using JavaScripts ability to define closures:
this.somefunction = this.someFunction.override(function(args){
var result = this.inherited(args);
result += this.doSomethingElse();
return result;
});
//It is accomplished through this piece of code (courtesy of Poul Krogh):
/***************************************************************
function.override overrides a defined method with a new one,
while preserving the old method.
The old method is only accessible from the new one.
Use this.inherited() to access the old method.
***************************************************************/
Function.prototype.override = function(func)
{
var remember = this;
var f = function()
{
var save = this.inherited;
this.inherited = remember;
var result = func.apply(this, Array.prototype.slice.call(arguments));
this.inherited = save;
return result;
};
return f;
}
上記のすべての答えに同意しますが、ほとんどの場合、JavaScriptはオブジェクト指向である必要はなく、(継承を避ける)代わりに オブジェクトベースのアプローチ で十分であると感じています。
私は、オブジェクト指向プログラミングでオブジェクト指向プログラミングについて Eloquent JavaScript が 第8章 を開始する方法が好きです。継承を実装する最良の方法を解読する代わりに、JavaScriptの機能的側面を学習するためにより多くのエネルギーを費やす必要があるため、機能的プログラミングの 第6章 がより興味深いことに気付きました。
JavaScriptで継承を行うシンプルで効果的な方法は、次の2行を使用することです。
_B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
_
これはこれを行うことに似ています:
_B.prototype = new A();
_
両者の主な違いは、 _Object.create
_ を使用すると、A
のコンストラクターが実行されないことです。 。
A
のコンストラクターに追加することにより、B
の新しいインスタンスを作成するときに、オプションでB
のコンストラクターをオプションで実行することを選択できます。
_function B(arg1, arg2) {
A(arg1, arg2); // This is optional
}
_
B
のすべての引数をA
に渡したい場合は、 Function.prototype.apply()
を使用することもできます。
_function B() {
A.apply(this, arguments); // This is optional
}
_
別のオブジェクトをB
のコンストラクタチェーンにミックスインしたい場合、_Object.create
_と _Object.assign
_ を組み合わせることができます。
_B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;
_
_function A(name) {
this.name = name;
}
A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;
function B() {
A.apply(this, arguments);
this.street = "Downing Street 10";
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
function mixin() {
}
mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;
mixin.prototype.getProperties = function() {
return {
name: this.name,
address: this.street,
year: this.year
};
};
function C() {
B.apply(this, arguments);
this.year = "2018"
}
C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;
var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());
_
コード全体でほぼ同じ2ライナーを記述したくない場合は、次のような基本的なラッパー関数を作成できます。
_function inheritance() {
var args = Array.prototype.slice.call(arguments);
var firstArg = args.shift();
switch (args.length) {
case 0:
firstArg.prototype = Object.create(Object.prototype);
firstArg.prototype.constructor = firstArg;
break;
case 1:
firstArg.prototype = Object.create(args[0].prototype);
firstArg.prototype.constructor = firstArg;
break;
default:
for(var i = 0; i < args.length; i++) {
args[i] = args[i].prototype;
}
args[0] = Object.create(args[0]);
var secondArg = args.shift();
firstArg.prototype = Object.assign.apply(Object, args);
firstArg.prototype.constructor = firstArg;
}
}
_
このラッパーの仕組み:
Object
から継承します。_function inheritance() {
var args = Array.prototype.slice.call(arguments);
var firstArg = args.shift();
switch (args.length) {
case 0:
firstArg.prototype = Object.create(Object.prototype);
firstArg.prototype.constructor = firstArg;
break;
case 1:
firstArg.prototype = Object.create(args[0].prototype);
firstArg.prototype.constructor = firstArg;
break;
default:
for(var i = 0; i < args.length; i++) {
args[i] = args[i].prototype;
}
args[0] = Object.create(args[0]);
var secondArg = args.shift();
firstArg.prototype = Object.assign.apply(Object, args);
firstArg.prototype.constructor = firstArg;
}
}
function A(name) {
this.name = name;
}
inheritance(A);
function B() {
A.apply(this, arguments);
this.street = "Downing Street 10";
}
inheritance(B, A);
function mixin() {
}
inheritance(mixin);
mixin.prototype.getProperties = function() {
return {
name: this.name,
address: this.street,
year: this.year
};
};
function C() {
B.apply(this, arguments);
this.year = "2018"
}
inheritance(C, B, mixin);
var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());
_
_Object.create
_は、IE9 +を含む最新のすべてのブラウザーで安全に使用できます。 _Object.assign
_は、IEまたは一部のモバイルブラウザのどのバージョンでも動作しません。 polyfill _Object.create
_および/またはそれらを使用し、それらを実装しないブラウザをサポートする場合は、_Object.assign
_。
_Object.create
_ here のポリフィルと_Object.assign
_ here のポリフィルを見つけることができます。
このシンプルなアプローチはどうですか
function Body(){
this.Eyes = 2;
this.Arms = 2;
this.Legs = 2;
this.Heart = 1;
this.Walk = function(){alert(this.FirstName + ' Is Walking')};
}
function BasePerson() {
var BaseBody = new Body(this);
BaseBody.FirstName = '';
BaseBody.LastName = '';
BaseBody.Email = '';
BaseBody.IntroduceSelf = function () { alert('Hello my name is ' + this.FirstName + ' ' + this.LastName); };
return BaseBody;
}
function Person(FirstName,LastName)
{
var PersonBuild = new BasePerson();
PersonBuild.FirstName = FirstName;
PersonBuild.LastName = LastName;
return PersonBuild;
}
var Person1 = new Person('Code', 'Master');
Person1.IntroduceSelf();
Person1.Walk();
//
// try this one:
//
// function ParentConstructor() {}
// function ChildConstructor() {}
//
// var
// SubClass = ChildConstructor.xtendz( ParentConstructor );
//
Function.prototype.xtendz = function ( SuperCtorFn ) {
return ( function( Super, _slice ) {
// 'freeze' Host fn
var
baseFn = this,
SubClassCtorFn;
// define child ctor
SubClassCtorFn = function ( /* child_ctor_parameters..., parent_ctor_parameters[] */ ) {
// execute parent ctor fn on Host object
// pass it last ( array ) argument as parameters
Super.apply( this, _slice.call( arguments, -1 )[0] );
// execute child ctor fn on Host object
// pass remaining arguments as parameters
baseFn.apply( this, _slice.call( arguments, 0, -1 ) );
};
// establish proper prototype inheritance
// 'inherit' methods
SubClassCtorFn.prototype = new Super;
// (re)establish child ctor ( instead of Super ctor )
SubClassCtorFn.prototype.constructor = SubClassCtorFn;
// return built ctor
return SubClassCtorFn;
} ).call( this, SuperCtorFn, Array.prototype.slice );
};
// declare parent ctor
function Sup( x1, x2 ) {
this.parent_property_1 = x1;
this.parent_property_2 = x2;
}
// define some methods on parent
Sup.prototype.hello = function(){
alert(' ~ h e l l o t h e r e ~ ');
};
// declare child ctor
function Sub( x1, x2 ) {
this.child_property_1 = x1;
this.child_property_2 = x2;
}
var
SubClass = Sub.xtendz(Sup), // get 'child class' ctor
obj;
// reserve last array argument for parent ctor
obj = new SubClass( 97, 98, [99, 100] );
obj.hello();
console.log( obj );
console.log('obj instanceof SubClass -> ', obj instanceof SubClass );
console.log('obj.constructor === SubClass -> ', obj.constructor === SubClass );
console.log('obj instanceof Sup -> ', obj instanceof Sup );
console.log('obj instanceof Object -> ', obj instanceof Object );
//
// Object {parent_property_1: 99, parent_property_2: 100, child_property_1: 97, child_property_2: 98}
// obj instanceof SubClass -> true
// obj.constructor === SubClass -> true
// obj instanceof Sup -> true
// obj instanceof Object -> true
//