web-dev-qa-db-ja.com

プロトタイプコンストラクターを設定する必要があるのはなぜですか?

MDN記事の継承に関するセクションオブジェクト指向Javascriptの紹介 で、prototype.constructorを設定していることに気付きました:

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;  

これは重要な目的を果たしていますか?省略しても大丈夫ですか?

279
trinth

常に必要というわけではありませんが、用途はあります。ベースPersonクラスでcopyメソッドを作成したいとします。このような:

// define the Person Class  
function Person(name) {
    this.name = name;
}  

Person.prototype.copy = function() {  
    // return new Person(this.name); // just as bad
    return new this.constructor(this.name);
};  

// define the Student class  
function Student(name) {  
    Person.call(this, name);
}  

// inherit Person  
Student.prototype = Object.create(Person.prototype);

新しいStudentを作成してコピーするとどうなりますか?

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => false

コピーはStudentのインスタンスではありません。これは、(明示的なチェックなしで)「ベース」クラスからStudentコピーを返す方法がないためです。 Personのみを返すことができます。ただし、コンストラクタをリセットした場合:

// correct the constructor pointer because it points to Person  
Student.prototype.constructor = Student;

...その後、すべてが期待どおりに動作します:

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => true
250
Wayne Burkett

これは重要な目的に役立ちますか?

はいといいえ。

ES5以前では、JavaScript自体はconstructorを使用しませんでした。関数のprototypeプロパティのデフォルトオブジェクトがそれを持ち、関数を参照し直すことを定義し、それはそれでした 。仕様の他の何もそれを参照しませんでした。

ES2015(ES6)で変更され、継承階層に関連して使用が開始されました。たとえば、 Promise#then は、返す新しいプロミスを作成するときに、( SpeciesConstructor を介して)呼び出すプロミスのconstructorプロパティを使用します。また、配列のサブタイプ化にも関与しています( ArraySpeciesCreate を使用)。

言語自体の外では、一般的な「クローン」関数を構築しようとするとき、または一般的にオブジェクトのコンストラクター関数と思われるものを参照したいときに、人々はそれを使用することがあります。私の経験では、それを使用することはまれですが、時々人々はそれを使用します。

省略しても大丈夫ですか?

デフォルトで存在します。関数のprototypeプロパティのオブジェクトをreplaceに戻すときにのみ元に戻す必要があります。

Student.prototype = Object.create(Person.prototype);

これを行わない場合:

Student.prototype.constructor = Student;

...その後、Student.prototype.constructorは(おそらく)Person.prototypeを持つconstructor = Personを継承します。だから誤解を招く。もちろん、それを使用するもの(PromiseArrayなど)をサブクラス化し、class¹(これを処理する)を使用しない場合は、必ず確認する必要があります。正しく設定しました。基本的には、良いアイデアです。

コード(または使用するライブラリコード)で何も使用しなくてもかまいません。常に正しく接続されていることを常に確認しています。

もちろん、ES2015(別名ES6)のclassキーワードを使用すると、ほとんどの場合使用していましたが、もう必要はありません。

class Student extends Person {
}

¹ "......使用するもの(PromiseArrayなど)をサブクラス化し、class..."—それは可能ですが、それは本当の痛み(そして少し愚かな)です。 Reflect.construct を使用する必要があります。

70
T.J. Crowder

TLDR;それほど必要ではありませんが、おそらく長期的には役立つでしょう。そうする方が正確です。

注:以前の回答が混乱して書かれていて、Rushで答えを逃したいくつかのエラーがあったので、編集しました。ひどいエラーを指摘してくれた人々に感謝します。

基本的に、Javascriptでサブクラス化を正しく配線することです。サブクラスを作成するときは、prototypeオブジェクトの上書きなど、プロトタイプ委任が正しく機能することを確認するために、いくつかのファンキーなことを行う必要があります。 prototypeオブジェクトを上書きするとconstructorが含まれるため、参照を修正する必要があります。

ES5の「クラス」がどのように機能するかを簡単に見てみましょう。

コンストラクター関数とそのプロトタイプがあるとしましょう:

//Constructor Function
var Person = function(name, age) {
  this.name = name;
  this.age = age;
}

//Prototype Object - shared between all instances of Person
Person.prototype = {
  species: 'human',
}

コンストラクターを呼び出してインスタンス化するには、Adamと言います。

// instantiate using the 'new' keyword
var adam = new Person('Adam', 19);

'Person'で呼び出されたnewキーワードは、基本的に数行のコードを追加してPersonコンストラクターを実行します。

function Person (name, age) {
  // This additional line is automatically added by the keyword 'new'
  // it sets up the relationship between the instance and the prototype object
  // So that the instance will delegate to the Prototype object
  this = Object.create(Person.prototype);

  this.name = name;
  this.age = age;

  return this;
}

/* So 'adam' will be an object that looks like this:
 * {
 *   name: 'Adam',
 *   age: 19
 * }
 */

console.log(adam.species)の場合、ルックアップはadamインスタンスで失敗し、プロトタイプチェーンを.prototypePerson.prototype-およびPerson.prototypehas.speciesプロパティであるため、ルックアップはPerson.prototypeで成功します。その後、'human'をログに記録します。

ここで、Person.prototype.constructorPersonを正しく指します。

それで今、興味深い部分、いわゆる「サブクラス化」。 Studentクラス、つまりいくつかの追加の変更を加えたPersonクラスのサブクラスを作成する場合、Student.prototype.constructorが正確さのためにStudentを指していることを確認する必要があります。

それ自体はこれを行いません。サブクラス化すると、コードは次のようになります。

var Student = function(name, age, school) {
 // Calls the 'super' class, as every student is an instance of a Person
 Person.call(this, name, age);
 // This is what makes the Student instances different
 this.school = school
}

var eve = new Student('Eve', 20, 'UCSF');

console.log(Student.prototype); // this will be an empty object: {}

ここでnew Student()を呼び出すと、必要なすべてのプロパティを持つオブジェクトが返されます。ここで、eve instanceof Personをチェックすると、falseが返されます。 eve.speciesにアクセスしようとすると、undefinedが返されます。

つまり、eve instanceof Personがtrueを返し、StudentのインスタンスがStudent.prototypeに正しく委任され、次にPerson.prototypeになるように委任を結び付ける必要があります。

しかし、newキーワードで呼び出しているので、その呼び出しが追加するものを覚えていますか? Object.create(Student.prototype)を呼び出します。これは、StudentStudent.prototypeの間の委任関係を設定する方法です。現時点では、Student.prototypeは空です。したがって、.speciesの検索は、StudentのインスタンスがonlyStudent.prototype.speciesプロパティはStudent.prototypeに存在しません。

Student.prototypeObject.create(Person.prototype)に割り当てると、Student.prototype自体がPerson.prototypeに委任し、eve.speciesを検索すると、期待どおりhumanが返されます。おそらく、Student.prototype AND Person.prototypeから継承する必要があります。そのため、すべてを修正する必要があります。

/* This sets up the prototypal delegation correctly 
 *so that if a lookup fails on Student.prototype, it would delegate to Person's .prototype
 *This also allows us to add more things to Student.prototype 
 *that Person.prototype may not have
 *So now a failed lookup on an instance of Student 
 *will first look at Student.prototype, 
 *and failing that, go to Person.prototype (and failing /that/, where do we think it'll go?)
*/
Student.prototype = Object.create(Person.prototype);

これで委任は機能しますが、Student.prototypePerson.prototypeで上書きしています。したがって、Student.prototype.constructorを呼び出すと、PersonではなくStudentを指します。 これは、修正が必要な理由です。

// Now we fix what the .constructor property is pointing to    
Student.prototype.constructor = Student

// If we check instanceof here
console.log(eve instanceof Person) // true

ES5では、constructorプロパティは、「コンストラクター」を意図して作成した関数を参照する参照です。 newキーワードが提供するものは別として、コンストラクタはそれ以外の場合は「プレーン」関数です。

ES6では、constructorがクラスの記述方法に組み込まれました。たとえば、クラスを宣言するときにメソッドとして提供されます。これは単なるシンタックスシュガーですが、既存のクラスを拡張するときにsuperへのアクセスなどの便利さを提供します。したがって、上記のコードは次のように記述します。

class Person {
  // constructor function here
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  // static getter instead of a static property
  static get species() {
    return 'human';
  }
}

class Student extends Person {
   constructor(name, age, school) {
      // calling the superclass constructor
      super(name, age);
      this.school = school;
   }
}
12
bthehuman

私は同意しません。プロトタイプを設定する必要はありません。まったく同じコードを使用しますが、prototype.constructor行を削除します。何か変更はありますか?いいえ。次の変更を行います。

Person = function () {
    this.favoriteColor = 'black';
}

Student = function () {
    Person.call(this);
    this.favoriteColor = 'blue';
}

そして、テストコードの最後に...

alert(student1.favoriteColor);

色は青になります。

私の経験では、prototype.constructorの変更は、とにかく良いプラクティスではない、非常に具体的で非常に複雑なことをしている場合を除いて、ほとんど何もしません:)

編集:ウェブを少し調べて、いくつかの実験を行った後、人々が「new」で構築されているもののように「見える」ようにコンストラクタを設定しているように見えます。私はこれに関する問題はjavascriptがプロトタイプ言語であると主張するだろうと思います-継承のようなものはありません。しかし、ほとんどのプログラマーは、継承を「方法」として推進するプログラミングの背景から来ています。そこで、このプロトタイプ言語を「クラシック」言語にするためのあらゆる種類のことを考え出します。「クラス」を拡張するなど。実際、彼らが与えた例では、新しい学生は人です-それは他の学生から「拡張」していません。学生を拡大します。あなたが拡大したものは何でも中心的な学生ですが、ニーズに合わせてカスタマイズされます。

クロックフォードは少しクレイジーで熱心ですが、彼が書いたもののいくつかを真剣に読んでください。

10
Stephen

これには大きな落とし穴があります

Student.prototype.constructor = Student;

しかし、もしプロトタイプが人でもある教師がいて、あなたが書いた場合

Teacher.prototype.constructor = Teacher;

そして、StudentコンストラクタがTeacherになりました!

編集:Mozillaの例のように、Object.createを使用して作成されたPersonクラスの新しいインスタンスを使用してStudentとTeacherのプロトタイプを設定したことを確認することにより、これを回避できます。

Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
9
James D

これまでのところ、混乱がまだあります。

元の例に従って、既存のオブジェクトとしてstudent1があります:

var student1 = new Student("Janet", "Applied Physics");

student1の作成方法を知りたくない場合、そのような別のオブジェクトが必要な場合は、student1のコンストラクタプロパティを使用できます。

var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");

ここで、コンストラクタープロパティが設定されていない場合、Studentからプロパティを取得できません。むしろ、Personオブジェクトを作成します。

5
Mahavir

プロトタイプコンストラクターを設定することが本当に必要な理由の素敵なコード例を入手しました。

function CarFactory(name){ 
   this.name=name;  
} 
CarFactory.prototype.CreateNewCar = function(){ 
    return new this.constructor("New Car "+ this.name); 
} 
CarFactory.prototype.toString=function(){ 
    return 'Car Factory ' + this.name;
} 

AudiFactory.prototype = new CarFactory();      // Here's where the inheritance occurs 
AudiFactory.prototype.constructor=AudiFactory;       // Otherwise instances of Audi would have a constructor of Car 

function AudiFactory(name){ 
    this.name=name;
} 

AudiFactory.prototype.toString=function(){ 
    return 'Audi Factory ' + this.name;
} 

var myAudiFactory = new AudiFactory('');
  alert('Hay your new ' + myAudiFactory + ' is ready.. Start Producing new audi cars !!! ');            

var newCar =  myAudiFactory.CreateNewCar(); // calls a method inherited from CarFactory 
alert(newCar); 

/*
Without resetting prototype constructor back to instance, new cars will not come from New Audi factory, Instead it will come from car factory ( base class )..   Dont we want our new car from Audi factory ???? 
*/
2
user3877965

最近では、糖化された関数「クラス」や「新規」を使用する必要はありません。オブジェクトリテラルを使用します。

オブジェクトのプロトタイプはすでに「クラス」です。オブジェクトリテラルを定義するとき、それは既にプロトタイプObjectのインスタンスです。これらは、別のオブジェクトのプロトタイプなどとしても機能します。

const Person = {
  name: '[Person.name]',
  greeting: function() {
    console.log( `My name is ${ this.name || '[Name not assigned]' }` );
  }
};
// Person.greeting = function() {...} // or define outside the obj if you must

// Object.create version
const john = Object.create( Person );
john.name = 'John';
console.log( john.name ); // John
john.greeting(); // My name is John 
// Define new greeting method
john.greeting = function() {
    console.log( `Hi, my name is ${ this.name }` )
};
john.greeting(); // Hi, my name is John

// Object.assign version
const jane = Object.assign( Person, { name: 'Jane' } );
console.log( jane.name ); // Jane
// Original greeting
jane.greeting(); // My name is Jane 

// Original Person obj is unaffected
console.log( Person.name ); // [Person.name]
console.log( Person.greeting() ); // My name is [Person.name]

これは読む価値があります

JavaやC++などのクラスベースのオブジェクト指向言語は、クラスとインスタンスという2つの異なるエンティティの概念に基づいています。

...

JavaScriptなどのプロトタイプベースの言語は、この区別を行いません。単にオブジェクトを持っています。プロトタイプベースの言語には、プロトタイプオブジェクトという概念があります。これは、新しいオブジェクトの初期プロパティを取得するためのテンプレートとして使用されるオブジェクトです。オブジェクトは、作成時または実行時に独自のプロパティを指定できます。さらに、任意のオブジェクトを別のオブジェクトのプロトタイプとして関連付けることができ、2番目のオブジェクトが最初のオブジェクトのプロパティを共有できます。

1
ucsarge

モンキーパッチなしでtoStringの代替が必要な場合に必要です:

//Local
foo = [];
foo.toUpperCase = String(foo).toUpperCase;
foo.Push("a");
foo.toUpperCase();

//Global
foo = [];
window.toUpperCase = function (obj) {return String(obj).toUpperCase();}
foo.Push("a");
toUpperCase(foo);

//Prototype
foo = [];
Array.prototype.toUpperCase = String.prototype.toUpperCase;
foo.Push("a");
foo.toUpperCase();

//toString alternative via Prototype constructor
foo = [];
Array.prototype.constructor = String.prototype.toUpperCase;
foo.Push("a,b");
foo.constructor();

//toString override
var foo = [];
foo.Push("a");
var bar = String(foo);
foo.toString = function() { return bar.toUpperCase(); }
foo.toString();

//Object prototype as a function
Math.prototype = function(char){return Math.prototype[char]};
Math.prototype.constructor = function() 
  {
  var i = 0, unicode = {}, zero_padding = "0000", max = 9999;
  
  while (i < max) 
    {
    Math.prototype[String.fromCharCode(parseInt(i, 16))] = ("u" + zero_padding + i).substr(-4);

    i = i + 1;
    }    
  }

Math.prototype.constructor();
console.log(Math.prototype("a") );
console.log(Math.prototype["a"] );
console.log(Math.prototype("a") === Math.prototype["a"]);
1
Paul Sweatte

編集、私は実際に間違っていた。行をコメントアウトしても、その動作はまったく変わりません。 (テストしました)


はい、必要です。するとき

Student.prototype = new Person();  

Student.prototype.constructorPersonになります。したがって、Student()を呼び出すと、Personによって作成されたオブジェクトが返されます。もしそうなら

Student.prototype.constructor = Student; 

Student.prototype.constructorStudentにリセットされます。これで、Student()を呼び出すと、Studentが実行され、親コンストラクタParent()が呼び出され、正しく継承されたオブジェクトが返されます。 Student.prototype.constructorを呼び出す前にリセットしなかった場合、Student()で設定されたプロパティを持たないオブジェクトを取得します。

0
invisible bob

単純なコンストラクター関数が与えられた場合:

function Person(){
    this.name = 'test';
}


console.log(Person.prototype.constructor) // function Person(){...}

Person.prototype = { //constructor in this case is Object
    sayName: function(){
        return this.name;
    }
}

var person = new Person();
console.log(person instanceof Person); //true
console.log(person.sayName()); //test
console.log(Person.prototype.constructor) // function Object(){...}

デフォルトでは(仕様から https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor )、すべてのプロトタイプはコンストラクタと呼ばれるプロパティを自動的に取得しますそれがプロパティである関数を指します。コンストラクターによっては、他のプロパティとメソッドがプロトタイプに追加される場合がありますが、これはあまり一般的な方法ではありませんが、拡張には許可されています。

したがって、単純に答えます。prototype.constructorの値が、仕様で想定されているとおりに正しく設定されていることを確認する必要があります。

この値を常に正しく設定する必要がありますか?デバッグに役立ち、仕様に対して内部構造の一貫性を保ちます。サードパーティがAPIを使用しているときは間違いなく、ランタイムでコードが最終的に実行されたときはそうではありません。

0
kospiotr

MDNの使用例を理解するのに非常に役立つとわかったMDNの例を次に示します。

JavaScriptでは、 async functions があり、これは AsyncFunction オブジェクトを返します。 AsyncFunctionはグローバルオブジェクトではありませんが、constructorプロパティを使用して取得し、利用することができます。

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

// AsyncFunction constructor
var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor

var a = new AsyncFunction('a', 
                          'b', 
                          'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);');

a(10, 20).then(v => {
  console.log(v); // prints 30 after 4 seconds
});
0
Hitesh Kumar