MDN記事の継承に関するセクションオブジェクト指向Javascriptの紹介 で、prototype.constructorを設定していることに気付きました:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
これは重要な目的を果たしていますか?省略しても大丈夫ですか?
常に必要というわけではありませんが、用途はあります。ベース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
これは重要な目的に役立ちますか?
はいといいえ。
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
を継承します。だから誤解を招く。もちろん、それを使用するもの(Promise
やArray
など)をサブクラス化し、class
¹(これを処理する)を使用しない場合は、必ず確認する必要があります。正しく設定しました。基本的には、良いアイデアです。
コード(または使用するライブラリコード)で何も使用しなくてもかまいません。常に正しく接続されていることを常に確認しています。
もちろん、ES2015(別名ES6)のclass
キーワードを使用すると、ほとんどの場合使用していましたが、もう必要はありません。
class Student extends Person {
}
¹ "......使用するもの(Promise
やArray
など)をサブクラス化し、class
..."—それは可能ですが、それは本当の痛み(そして少し愚かな)です。 Reflect.construct
を使用する必要があります。
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
インスタンスで失敗し、プロトタイプチェーンを.prototype
(Person.prototype
-およびPerson.prototype
has.species
プロパティであるため、ルックアップはPerson.prototype
で成功します。その後、'human'
をログに記録します。
ここで、Person.prototype.constructor
はPerson
を正しく指します。
それで今、興味深い部分、いわゆる「サブクラス化」。 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)
を呼び出します。これは、Student
とStudent.prototype
の間の委任関係を設定する方法です。現時点では、Student.prototype
は空です。したがって、.species
の検索は、Student
のインスタンスがonlyStudent.prototype
と.species
プロパティはStudent.prototype
に存在しません。
Student.prototype
をObject.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.prototype
をPerson.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;
}
}
私は同意しません。プロトタイプを設定する必要はありません。まったく同じコードを使用しますが、prototype.constructor行を削除します。何か変更はありますか?いいえ。次の変更を行います。
Person = function () {
this.favoriteColor = 'black';
}
Student = function () {
Person.call(this);
this.favoriteColor = 'blue';
}
そして、テストコードの最後に...
alert(student1.favoriteColor);
色は青になります。
私の経験では、prototype.constructorの変更は、とにかく良いプラクティスではない、非常に具体的で非常に複雑なことをしている場合を除いて、ほとんど何もしません:)
編集:ウェブを少し調べて、いくつかの実験を行った後、人々が「new」で構築されているもののように「見える」ようにコンストラクタを設定しているように見えます。私はこれに関する問題はjavascriptがプロトタイプ言語であると主張するだろうと思います-継承のようなものはありません。しかし、ほとんどのプログラマーは、継承を「方法」として推進するプログラミングの背景から来ています。そこで、このプロトタイプ言語を「クラシック」言語にするためのあらゆる種類のことを考え出します。「クラス」を拡張するなど。実際、彼らが与えた例では、新しい学生は人です-それは他の学生から「拡張」していません。学生を拡大します。あなたが拡大したものは何でも中心的な学生ですが、ニーズに合わせてカスタマイズされます。
クロックフォードは少しクレイジーで熱心ですが、彼が書いたもののいくつかを真剣に読んでください。
これには大きな落とし穴があります
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);
これまでのところ、混乱がまだあります。
元の例に従って、既存のオブジェクトとしてstudent1
があります:
var student1 = new Student("Janet", "Applied Physics");
student1
の作成方法を知りたくない場合、そのような別のオブジェクトが必要な場合は、student1
のコンストラクタプロパティを使用できます。
var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");
ここで、コンストラクタープロパティが設定されていない場合、Student
からプロパティを取得できません。むしろ、Person
オブジェクトを作成します。
プロトタイプコンストラクターを設定することが本当に必要な理由の素敵なコード例を入手しました。
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 ????
*/
最近では、糖化された関数「クラス」や「新規」を使用する必要はありません。オブジェクトリテラルを使用します。
オブジェクトのプロトタイプはすでに「クラス」です。オブジェクトリテラルを定義するとき、それは既にプロトタイプ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番目のオブジェクトが最初のオブジェクトのプロパティを共有できます。
モンキーパッチなしで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"]);
はい、必要です。するとき
Student.prototype = new Person();
Student.prototype.constructor
はPerson
になります。したがって、Student()
を呼び出すと、Person
によって作成されたオブジェクトが返されます。もしそうなら
Student.prototype.constructor = Student;
Student.prototype.constructor
はStudent
にリセットされます。これで、Student()
を呼び出すと、Student
が実行され、親コンストラクタParent()
が呼び出され、正しく継承されたオブジェクトが返されます。 Student.prototype.constructor
を呼び出す前にリセットしなかった場合、Student()
で設定されたプロパティを持たないオブジェクトを取得します。
単純なコンストラクター関数が与えられた場合:
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を使用しているときは間違いなく、ランタイムでコードが最終的に実行されたときはそうではありません。
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
});