4

通常、人々は次のようなコードを書きます。

function Shape() {
  this.x = 0;
  this.y = 0;
}

Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
}

ただし、関数定義をコンストラクターで分離せずに、プロトタイプで関数を定義する方法を考え出そうとしていました。これが私が得たものです:

Object.prototype.method = function(name, func) {
  if (typeof(this.constructor.prototype[name]) === 'undefined') {
    this.constructor.prototype[name] = func
  }
}

これにより、次のようなことができます。

function Shape() {
  this.x = 0;
  this.y = 0;

  this.method('move', function(x, y) {
    this.x += x;
    this.y += y;
  })
}

また、何度シェイプを作成しても、関数は 1 回しか定義されません。

Object.prototype拡張が良い習慣とは見なされていないことは承知しています。しかし、それ以外に、このアプローチの欠点はありますか?

編集:

ヨハンは良い点を挙げました。method列挙できないようにするべきでした。改訂版は次のとおりです。

Object.defineProperty(Object.prototype, 'method', {
    value: function(name, func) {
        if (typeof(this.constructor.prototype[name]) === 'undefined') {
            this.constructor.prototype[name] = func
        }
    },
    enumerable: false
})
4

4 に答える 4

7

実際に両方の方法を比較して、どちらが速いか見てみましょう: http://jsperf.com/traditional-oop-vs-derek-s-oop-variant

ご覧のとおり、この方法は従来の方法よりもはるかに遅いです。理由は次のとおりです。

  1. コンストラクターが必要以上の処理を行っています。したがって、複数のインスタンスを作成すると、インスタンスを作成するための追加コストが加算されます。
  2. @Alxandrが述べたようmethodに、正当な理由もなく新しいインスタンスを作成するたびに、新しい匿名関数を作成しています。一度しか役に立たず、その後は処理能力を浪費します。
  3. prototypeコンストラクターの に特定のメソッドがあるかどうかを確認し、ない場合はメソッドを に追加する関数を呼び出してprototypeいます。これは不必要に思えます。これを行うための関数を作成する必要はありません。私見関数呼び出しは単なる追加のオーバーヘッドです。

あなたが批判を求めたので:

Object.prototype拡張が良い習慣とは見なされていないことは承知しています。しかし、それ以外に、このアプローチの欠点はありますか?

あなたのアプローチは非常に遅いだけでなく、次のような問題もあります。

  1. わかりにくい。このアプローチは直感的にわかるかもしれません。しかし、あなたのコードを読んだ人は、関数が何をするのか疑問に思うでしょうthis.methodObject.prototype.methodコードを完全に理解するには、 の定義を読む必要があります。
  2. 直感的ではない。prototype前に述べたように、コンストラクター内でプロパティを定義しても意味がありません。一度だけ必要で、その後は追加手荷物になります。prototypeコンストラクター ロジックとプロパティを分離しておくことをお勧めします。
  3. 予期しない動作につながる可能性があります。@basilikumが指摘したように、コンストラクターを呼び出さないと、prototypeプロパティは設定されません。これにより、 のプロパティにアクセスしようとすると問題が発生する可能性がありprototypeます。たとえば、prototypeプロパティを継承しない場合、基本コンストラクターが呼び出されるまで継承されません。

あなたの目標は、コンストラクターとprototypeプロパティの両方を単一のエンティティにカプセル化することだと思います。

ただし、関数定義をコンストラクターで分離せずに、プロトタイプで関数を定義する方法を考え出そうとしていました。

これを行う簡単な方法はありますか?見てみましょう、JavaScript はプロトタイプのオブジェクト指向言語です。prototypeしたがって、コンストラクターではなくにもっと焦点を当てる必要があります。

上の図は、次の回答から取得したものです: https://stackoverflow.com/a/8096017/783743

この図は次のことを示しています。

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

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

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

function Shape() {
    this.x = 0;
    this.y = 0;
}

Shape.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
};

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

var shape = {
    constructor: function () {
        this.x = 0;
        this.y = 0;
    },
    move: function (x, y) {
        this.x += x;
        this.y += y;
    }
};

これらの両方の例に含まれる情報は同じです。ただし、2 番目の例を機能させるには、少し足場を追加する必要があります。特に、次のことを行う必要があります。

var Shape = shape.constructor;
Shape.prototype = shape;

これは大きな問題ではありません。これを行う関数を作成するだけです。

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

Shapeこれで、次のように定義できます。

var Shape = defclass({
    constructor: function () {
        this.x = 0;
        this.y = 0;
    },
    move: function (x, y) {
        this.x += x;
        this.y += y;
    }
});

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

これが役に立ったことを願っています。

于 2013-07-27T02:27:55.800 に答える
1

Alxandr はすでに多くのことを言っていますが、あなたの方法の別の欠点を指摘したいと思います。

プロパティmoveは、コンストラクターを少なくとも 1 回呼び出した場合にのみ存在します。継承に関しては、これは悪いことです。たとえば、次のようにします。

function Shape() {
  this.x = 0;
  this.y = 0;

  this.method(this, 'move', function(x, y) {
    this.x += x;
    this.y += y;
  });
}
function Triangle() {
}

Triangle.prototype = Object.create(Shape.prototype);

var t = new Triangle();
t.move(); //gives you an error because move is not defined

ご覧のとおり、コンストラクターを呼び出したことがないShapeため、moveまだ存在しません。Shape通常、コンストラクター内でコンストラクターを呼び出すと、Triangleコードは正常に動作しますが、これを実行したくない状況が発生する可能性があります。

したがって、元のパターンに固執することをお勧めします。実際、慣れればそれほど醜くはありません。

于 2013-07-22T07:08:18.260 に答える
0

あなた、または将来誰かobjectが拡張された をループしようとすると、object未知のプロパティが含まれているため、結果は予測不能になり、壊れる可能性があります。ちょっと意外な落とし穴!

于 2013-07-22T06:44:04.137 に答える