10

ノート:

回答が示すように、質問で提案されたコードは、質問と私のコメントで説明されているいくつかの問題により、実際には継承を達成しません(そうでなければ、質問ではなく回答になります..)。継承の偽物として期待どおりに機能します(プロトタイプでさえありません)。


  • 概要

    要するに、javascript ではなく一般的な OO 言語を書いているのと同じように作成しますが、継承は正しく保ちます。

  • 物語

    Object.createは、プロトタイプの継承を実現するための優れた方法ですが、型付けされた脳と新しいファンには少し混乱します。

    疑似古典的なパターンで他のオブジェクト指向言語を書くように、javascript コードを書く方法はいくつかあります。これは疑似古典的であるため、JavaScript の基本的なプロトタイプの継承を正しく処理する必要があります。

    私が見つけたいのは、クラス宣言で疑似古典的な継承を実現できるアプローチです。デモンストレーション用のコードは投稿の最後に記載されています。期待どおりに動作しますが、厄介な点がいくつかあります。

    1. クラス宣言で取り除くことができないreturnか、継承が機能しません。

    2. クラス宣言を渡す以外thisに、返されるクロージャに何が であるかを認識させる方法はありませんthis

    3. 私も を取り除きたいのですfunction (instance, _super) {が、まだ良い考えがありません。

    4. クラスの静的(独自のプロパティ) は継承されません。

    解決策は、既存のフレームワークよりもいくつかの構文糖衣であり、適切なパターンが適用されます。


_extends機能:

function _extends(baseType) {
    return function (definition) {
        var caller=arguments.callee.caller;
        var instance=this;

        if(!(instance instanceof baseType)) {
            (caller.prototype=new baseType()).constructor=caller;
            instance=new caller();
        }

        var _super=function () {
            baseType.apply(instance, arguments);
        };

        definition(instance, _super);
        return instance;
    };
}

Abcクラス:

function Abc(key, value) {
    return _extends(Object).call(this, function (instance, _super) {
        instance.What=function () {
            alert('What');
        };

        instance.getValue=function () {
            return 333+Number(value);
        };

        instance.Value=instance.getValue();
        instance.Key=key;
    });
}

Xyzクラス:

function Xyz(key, value) {
    return _extends(Abc).call(this, function (instance, _super) {
        _super(key, value);

        instance.That=function () {
            alert('That');
        };
    });
}

コード例:

var x=new Xyz('x', '123');
alert([x.Key, x.Value].join(': ')+'; isAbc: '+(x instanceof Abc));

var y=new Xyz('y', '456');
alert([y.Key, y.Value].join(': ')+'; isAbc: '+(y instanceof Abc));

var it=new Abc('it', '789');
alert([it.Key, it.Value].join(': ')+'; isAbc: '+(it instanceof Abc));
alert([it.Key, it.Value].join(': ')+'; isXyz: '+(it instanceof Xyz));

x.What();
y.That();

it.What();
it.That(); // will throw; it is not Xyz and does not have That method
4

5 に答える 5

23

いいえ、いいえ、いいえ、これはできません。JavaScript での継承はすべて間違っています。あなたのコードは私に片頭痛を与えます。

JavaScript で疑似古典的な継承パターンを作成する

JavaScript のクラスに似たものが必要な場合は、それを提供するライブラリがたくさんあります。たとえば、augmentを使用すると、次のようにコードを再構築できます。

var augment = require("augment");

var ABC = augment(Object, function () {
    this.constructor = function (key, value) {
        this.key = key;
        this.value = value;
    };

    this.what = function () {
        alert("what");
    };
});

var XYZ = augment(ABC, function (base) {
    this.constructor = function (key, value) {
        base.constructor.call(this, key, value);
    };

    this.that = function () {
        alert("that");
    };
});

あなたのことはわかりませんが、私にはこれは C++ や Java の古典的な継承によく似ています。これで問題が解決した場合は、すばらしいです。そうでない場合は、読み続けてください。

プロトタイプクラスの同形

多くの点で、プロトタイプはクラスに似ています。実際、プロトタイプとクラスは非常に似ているため、プロトタイプを使用してクラスをモデル化できます。まず、プロトタイプの継承が実際にどのように機能するかを見てみましょう。

上の写真は、次の回答から取得したものです。注意深く読むことをお勧めします。図は次のことを示しています。

  1. prototypeすべてのコンストラクターには、コンストラクター関数のプロトタイプ オブジェクトを指すというプロパティがあります。
  2. すべてのプロトタイプにはconstructor、プロトタイプ オブジェクトのコンストラクター関数を指す と呼ばれるプロパティがあります。
  3. コンストラクター関数からインスタンスを作成します。ただし、インスタンスは実際にprototypeはコンストラクターではなく から継承します。

これは非常に有益な情報です。従来、コンストラクター関数を最初に作成してから、そのprototypeプロパティを設定していました。constructorただし、この情報は、最初にプロトタイプ オブジェクトを作成してから、代わりにそのプロパティを定義できることを示しています。

たとえば、伝統的に次のように書くことができます。

function ABC(key, value) {
    this.key = key;
    this.value = value;
}

ABC.prototype.what = function() {
    alert("what");
};

ただし、新たに発見した知識を使用すると、次のように同じことを書くことができます。

var ABC = CLASS({
    constructor: function (key, value) {
        this.key = key;
        this.value = value;
    },
    what: function () {
        alert("what");
    }
});

function CLASS(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

ご覧のとおり、カプセル化は JavaScript で簡単に実現できます。あなたがする必要があるのは、横向きに考えるだけです。ただし、継承は別の問題です。継承を実現するには、もう少し作業を行う必要があります。

継承とスーパー

augment 継承を実現する方法を見てみましょう:

function augment(body) {
    var base = typeof this === "function" ? this.prototype : this;
    var prototype = Object.create(base);
    body.apply(prototype, arrayFrom(arguments, 1).concat(base));
    if (!ownPropertyOf(prototype, "constructor")) return prototype;
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

CLASS最後の 3 行は、前のセクションのものと同じであることに注意してください。

function CLASS(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

これは、プロトタイプ オブジェクトを取得したら、そのコンストラクタ プロパティを取得して返すだけでよいことを示しています。

拡張の最初の 3 行は、次の目的で使用されます。

  1. 基本クラスのプロトタイプを取得します。
  2. を使用して派生クラスのプロトタイプを作成しますObject.create
  3. 派生クラスのプロトタイプに、指定されたプロパティを設定します。

JavaScript の継承については、これですべてです。独自の古典的な継承パターンを作成したい場合は、同じ方向に沿って考える必要があります。

真のプロトタイプ継承を受け入れる

熟練した JavaScript プログラマーなら誰でも、プロトタイプの継承は従来の継承よりも優れていると言うでしょう。それにもかかわらず、古典的な継承を持つ言語から来た初心者は、常にプロトタイプの継承の上に古典的な継承を実装しようとしますが、通常は失敗します。

それらが失敗するのは、プロトタイプの継承の上に古典的な継承を実装することができないからではなく、プロトタイプの継承の上に古典的な継承を実装するには、最初に真のプロトタイプの継承がどのように機能するかを理解する必要があるためです。

ただし、真のプロトタイプ継承を理解すると、古典的な継承には戻りたくないでしょう。私も初心者として、プロトタイプの継承の上に古典的な継承を実装しようとしました。真のプロトタイプ継承がどのように機能するかを理解したので、次のようなコードを記述します。

function extend(self, body) {
    var base = typeof self === "function" ? self.prototype : self;
    var prototype = Object.create(base, {new: {value: create}});
    return body.call(prototype, base), prototype;

    function create() {
        var self = Object.create(prototype);
        return prototype.hasOwnProperty("constructor") &&
            prototype.constructor.apply(self, arguments), self;
    }
}

上記のextend関数は と非常によく似ていaugmentます。ただし、コンストラクター関数を返す代わりに、プロトタイプ オブジェクトを返します。これは実際には、静的プロパティを継承できる非常に巧妙なトリックです。extend次のように使用してクラスを作成できます。

var Abc = extend(Object, function () {
    this.constructor = function (key, value) {
        this.value = 333 + Number(value);
        this.key = key;
    };

    this.what = function () {
        alert("what");
    };
});

継承も同様に単純です。

var Xyz = extend(Abc, function (base) {
    this.constructor = function (key, value) {
        base.constructor.call(this, key, value);
    };

    this.that = function () {
        alert("that");
    };
});

ただしextend、コンストラクター関数を返さないことに注意してください。プロトタイプ オブジェクトを返します。newこれは、キーワードを使用してクラスのインスタンスを作成できないことを意味します。new代わりに、次のようにメソッドとして使用する必要があります。

var x = Xyz.new("x", "123");
var y = Xyz.new("y", "456");
var it = Abc.new("it", "789");

これは実際には良いことです。このnewキーワードは有害であると考えられ ているため、使用を中止することを強くお勧めします。たとえば、キーワードと一緒に使用することはできません。ただし、次の方法で使用することは可能です。applynewapplynew

var it = Abc.new.apply(null, ["it", "789"]);

AbcandXyzはコンストラクター関数ではないため、オブジェクトが or のインスタンスであるかどうかをテストするために使用するinstanceofことAbcはできませんXyz。ただし、JavaScript にはisPrototypeOf、オブジェクトが別のオブジェクトのプロトタイプであるかどうかをテストするメソッドが呼び出されるため、これは問題ではありません。

alert(x.key + ": " + x.value + "; isAbc: " + Abc.isPrototypeOf(x));
alert(y.key + ": " + y.value + "; isAbc: " + Abc.isPrototypeOf(y));

alert(it.key + ": " + it.value + "; isAbc: " + Abc.isPrototypeOf(it));
alert(it.key + ": " + it.value + "; isXyz: " + Xyz.isPrototypeOf(it));

実際には、あるクラスが別のクラスを拡張するかどうかをテストできるため、isPrototypeOfより強力です。instanceof

alert(Abc.isPrototypeOf(Xyz)); // true

この小さな変更を除けば、他のすべては以前と同じように機能します。

x.what();
y.that();

it.what();
it.that(); // will throw; it is not Xyz and does not have that method

デモをご覧ください: http://jsfiddle.net/Jee96/

真のプロトタイプ継承は他に何を提供しますか? 真のプロトタイプ継承の最大の利点の 1 つは、通常のプロパティと静的プロパティの間に区別がなく、次のようなコードを記述できることです。

var Xyz = extend(Abc, function (base) {
    this.empty = this.new();

    this.constructor = function (key, value) {
        base.constructor.call(this, key, value);
    };

    this.that = function () {
        alert("that");
    };
});

を呼び出すことにより、クラス自体からクラスのインスタンスを作成できることに注意してくださいthis.new。がまだ定義されていない場合this.constructorは、初期化されていない新しいインスタンスを返します。それ以外の場合は、新しい初期化されたインスタンスを返します。

さらに、は直接Xyzアクセスできるプロトタイプ オブジェクトです(つまり、 は の静的プロパティです)。これは、静的プロパティが自動的に継承され、通常のプロパティと変わらないことも意味します。Xyz.emptyemptyXyz

最後に、クラスは としてクラス定義内からアクセスできるため、次のようthisに使用して、ネストされているクラスから継承するネストされたクラスを作成できます。extend

var ClassA = extend(Object, function () {
    var ClassB = extend(this, function () {
        // class definition
    });

    // rest of the class definition

    alert(this.isPrototypeOf(ClassB)); // true
});

デモをご覧ください: http://jsfiddle.net/Jee96/1/

于 2013-09-19T02:27:46.970 に答える
3

あなたが求めていることを行う方法についての徹底的なチュートリアルがあります。

おっとコンセプト

擬似古典パターン

オールワンコンストラクターパターン

于 2013-09-21T03:50:32.830 に答える
-2

これよりも多くの機能を探していると思いますが、別のクラスから一連のメソッドを継承したい場合は、これを行うことができます

http://cdpn.io/Jqhpc

var Parent = function Parent () {
  this.fname = 'Bob';
  this.lname = 'Jones';
};
Parent.prototype.getFname = function getFname () {
  return this.fname;
};
Parent.prototype.getLname = function getLname () {
  return this.lname;
};

var Child = function Child () {
  this.fname = 'Jim';
};
Child.prototype = Parent.prototype;

var child = new Child();
document.write(child.getFname()); //=> Jim
于 2013-10-15T17:39:38.980 に答える