Javascriptの継承:スーパーコンストラクターを呼び出すか、プロトタイプチェーンを使用しますか?


82

ごく最近、MDCでのJavaScript呼び出しの使用法について読みました

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call

以下に示す例の1つのリンク、私はまだ理解していません。

なぜ彼らはここでこのように継承を使用しているのですか

Prod_dept.prototype = new Product();

これは必要ですか?でスーパーコンストラクターへの呼び出しがあるため

Prod_dept()

とにかく、このように

Product.call

これは一般的な動作から外れているだけですか?スーパーコンストラクターの呼び出しを使用するか、プロトタイプチェーンを使用する方がよいのはいつですか?

function Product(name, value){
  this.name = name;
  if(value >= 1000)
    this.value = 999;
  else
    this.value = value;
}

function Prod_dept(name, value, dept){
  this.dept = dept;
  Product.call(this, name, value);
}

Prod_dept.prototype = new Product();

// since 5 is less than 1000, value is set
cheese = new Prod_dept("feta", 5, "food");

// since 5000 is above 1000, value will be 999
car = new Prod_dept("honda", 5000, "auto");

物事を明確にしてくれてありがとう


使用方法はほぼ正しいですが、newキーワードを使用してベースをインスタンス化する代わりにObject.create()を使用することをお勧めします(ベースコンストラクターに引数が必要な場合に問題が発生する可能性があります)。:私は私のブログの詳細持たncombo.wordpress.com/2013/07/11/...
ジョン

2
Product()は事実上2回呼び出されることにも注意してください。
event_jr 2014年

回答:


109

本当の質問に対する答えは、両方を行う必要があるということです。

  • プロトタイプを親のインスタンスに設定すると、プロトタイプチェーン(継承)が初期化されます。これは1回だけ実行されます(プロトタイプオブジェクトが共有されているため)。
  • 親のコンストラクターを呼び出すと、オブジェクト自体が初期化されます。これは、インスタンス化のたびに実行されます(オブジェクトを作成するたびに異なるパラメーターを渡すことができます)。

したがって、継承を設定するときに親のコンストラクターを呼び出さないでください。別のオブジェクトから継承するオブジェクトをインスタンス化する場合のみ。

Chris Morganの答えはほぼ完全で、細部(コンストラクターのプロパティ)が欠落しています。継承を設定する方法を提案させてください。

function extend(base, sub) {
  // Avoid instantiating the base class just to setup inheritance
  // Also, do a recursive merge of two prototypes, so we don't overwrite 
  // the existing prototype, but still maintain the inheritance chain
  // Thanks to @ccnokes
  var origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (var key in origProto)  {
     sub.prototype[key] = origProto[key];
  }
  // The constructor property was set wrong, let's fix it
  Object.defineProperty(sub.prototype, 'constructor', { 
    enumerable: false, 
    value: sub 
  });
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

Dog.prototype = {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
}    

// Setup the prototype chain the right way
extend(Animal, Dog);

// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog

クラスを作成する際の構文上の糖衣構文については、私のブログ投稿を参照してください。http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Ext-JSとhttp://www.uselesspickles.com/class_library/からコピーされたテクニックとhttps://stackoverflow.com/users/1397311/ccnokesからのコメント


6
EcmaScript5 +(すべての最新のブラウザー)では、Object.defineProperty(sub.protoype, 'constructor', { enumerable: false, value: sub }); このように定義すると、列挙不可能にすることができます。このようにして、javascriptが関数の新しいインスタンスを作成するときとまったく同じ「動作」を取得します(コンストラクターは列挙可能として設定されます) =自動的にfalse)
Adaptabi 2013年

2
extendsメソッドを2行に単純化できませんか?つまり、sub.prototype = Object.create(base.prototype); sub.prototype.constructor = sub;
Appetere 2013年

@Steveはい、できます。私が最初にこれを書いたときObject.createは、あまりサポートされていませんでした...更新しています。ほとんどのObject.createポリフィルは、最初に示した手法を使用して実装されていることに注意してください。
フアンメンデス

1
したがって、子オブジェクトとそのインスタンス(この場合は「Dog」オブジェクト)のみにメソッドを追加したい場合は、次のように拡張関数内で2つのプロトタイプをマージしますか?jsfiddle.net/ccnokes/75f9P
ccnokes 2014

1
@ elad.chen回答チェーンで説明したアプローチはプロトタイプをチェーンします。ミックスインは通常、すべてのプロパティをプロトタイプではなくインスタンスにコピーします。stackoverflow.com/questions/7506210/…を
Juan Mendes

30

これを行う理想的な方法は、コンストラクターを呼び出すため、実行しないことです。したがって、理想的な方法は、コンストラクターを除いて、次のようにクローンを作成することです。Prod_dept.prototype = new Product();Product

function Product(...) {
    ...
}
var tmp = function(){};
tmp.prototype = Product.prototype;

function Prod_dept(...) {
    Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;

次に、構築時にスーパーコンストラクターが呼び出されます。これは、パラメーターを渡すこともできるためです。

グーグルクロージャーライブラリのようなものを見ると、それが彼らのやり方であることがわかります。


継承の設定に使用されるコンストラクターを代理コンストラクターと呼びます。あなたの例では、継承を設定した後でもコンストラクタープロパティをリセットするのを忘れているため、コンストラクターを適切に検出できます
Juan Mendes 2010

1
@Juan:OK、更新して追加しましたProd_dept.prototype.constructor = Prod_dept;
クリスモーガン

@ChrisMorganサンプルの最後の行を理解するのに問題があります:Prod_dept.prototype.constructor = Prod_dept;。まず第一に、なぜそれが必要なのか、そしてなぜそれがProd_dept代わりに指すのProductですか?
ラッセクリスチャンセン

1
@ LasseChristiansen-sw_lasse:Prod_dept.prototypeの出力のプロトタイプとして使用されるものですnew Prod_dept()。(通常、そのプロトタイプはとして利用できますがinstance.__proto__、これは実装の詳細です。)理由についてconstructorは—これは言語の標準部分であるため、一貫性を保つために提供する必要があります。デフォルトでは正しいですが、プロトタイプを完全に置き換えるため、正しい値を再度割り当てる必要があります。そうしないと、正常でないものがあります(この場合、Prod_deptインスタンスにが含まれることになりますがthis.constructor == Product、これは悪いことです)。
クリスモーガン

6

JavaScriptでオブジェクト指向プログラミングを行ったことがある場合は、次のようにクラスを作成できることがわかります。

Person = function(id, name, age){
    this.id = id;
    this.name = name;
    this.age = age;
    alert('A new person has been accepted');
}

これまでのところ、クラスの人には2つのプロパティしかないので、いくつかのメソッドを与えます。これを行うためのクリーンな方法は、その「プロトタイプ」オブジェクトを使用することです。JavaScript 1.1から、プロトタイプオブジェクトがJavaScriptに導入されました。これは、オブジェクトのすべてのインスタンスにカスタムプロパティとメソッドを追加するプロセスを簡素化する組み込みオブジェクトです。次のように、「prototype」オブジェクトを使用して、クラスに2つのメソッドを追加しましょう。

Person.prototype = {
    /** wake person up */
    wake_up: function() {
        alert('I am awake');
    },

    /** retrieve person's age */
    get_age: function() {
        return this.age;
    }
}

これで、クラスPersonを定義しました。Personからいくつかのプロパティを継承するManagerという別のクラスを定義したい場合はどうでしょうか。Managerクラスを定義するときに、このすべてのプロパティを再定義する意味はありません。クラスPersonから継承するように設定するだけです。JavaScriptには継承が組み込まれていませんが、次のように継承を実装する手法を使用できます。

Inheritance_Manager = {};//継承マネージャークラスを作成します(名前は任意です)

次に、継承クラスに、baseClass引数とsubClassas引数をとるextendというメソッドを与えましょう。extendメソッド内に、継承関数inheritance(){}という内部クラスを作成します。この内部クラスを使用する理由は、baseClassプロトタイプとsubClassプロトタイプの間の混乱を避けるためです。次に、次のコードのように、継承クラスのプロトタイプがbaseClassプロトタイプを指すようにします。inheritance.prototype= baseClass。プロトタイプ; 次に、継承プロトタイプを次のようにsubClassプロトタイプにコピーします。subClass.prototype= new Heritage(); 次に、subClassのコンストラクターを次のように指定します。subClass.prototype.constructor= subClass; サブクラスのプロトタイピングが終了したら、次の2行のコードを指定して、いくつかの基本クラスのポインターを設定できます。

subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;

拡張関数の完全なコードは次のとおりです。

Inheritance_Manager.extend = function(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype = baseClass.prototype;
    subClass.prototype = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.baseConstructor = baseClass;
    subClass.superClass = baseClass.prototype;
}

継承を実装したので、それを使用してクラスを拡張できます。この場合、次のようにPersonクラスをManagerクラスに拡張します。

Managerクラスを定義します

Manager = function(id, name, age, salary) {
    Person.baseConstructor.call(this, id, name, age);
    this.salary = salary;
    alert('A manager has been registered.');
}

フォームPersonを継承させます

Inheritance_Manager.extend(Manager, Person);

お気づきの方もいらっしゃると思いますが、Inheritance_Managerクラスのextendメソッドを呼び出して、この場合はsubClass Managerを渡し、次にbaseClassPersonを渡しました。ここでは順序が非常に重要であることに注意してください。それらを交換すると、継承は意図したとおりに機能しなくなります。また、実際にサブクラスを定義する前に、この継承を指定する必要があることに注意してください。次に、サブクラスを定義しましょう。

以下のようにメソッドを追加できます。Managerクラスは、Personクラスから継承するため、常にPersonクラスで定義されたメソッドとプロパティを持ちます。

Manager.prototype.lead = function(){
   alert('I am a good leader');
}

それをテストするために、2つのオブジェクトを作成しましょう。1つはクラスPersonからのもので、もう1つは継承されたクラスManagerからのものです。

var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');

http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspxで完全なコードとその他のコメントを自由に入手して ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.