ทำไมจึงต้องตั้งค่าตัวสร้างต้นแบบ?


294

ในส่วนเกี่ยวกับการสืบทอดในบทความ MDN บทนำสู่จาวาสคริปต์เชิงวัตถุฉันสังเกตเห็นว่าพวกเขาตั้งต้นแบบ

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

สิ่งนี้มีจุดประสงค์สำคัญหรือไม่? มันไม่เป็นไรที่จะละเว้นหรือไม่


23
ดีใจที่คุณถามสิ่งนี้: ฉันอ่านเอกสารเดียวกันเมื่อวานนี้และอยากรู้อยากเห็นเกี่ยวกับเหตุผลที่อยู่เบื้องหลังการตั้งค่าคอนสตรัคเตอร์อย่างชัดเจน
ไวลี

6
ฉันเพิ่งจะชี้ให้เห็นคำถามนี้มีการเชื่อมโยงในบทความที่คุณเชื่อมโยง!
Marie

7
อะไรเป็นสิ่งที่จำเป็น
nothingisnecessary

1
subclass.prototype.constructorจะชี้ไปที่parent_classถ้าคุณไม่ได้เขียนsubclass.prototype.constructor = subclass; นั่นคือการใช้subclass.prototype.constructor()โดยตรงจะสร้างผลลัพธ์ที่ไม่คาดคิด
KuanYu Chu

คำตอบ:


263

มันไม่จำเป็นเสมอไป แต่มันมีประโยชน์ สมมติว่าเราต้องการสร้างวิธีการคัดลอกในPersonคลาสฐาน แบบนี้:

// 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

34
หมายเหตุ: constructorแอ็ตทริบิวต์ไม่มีความหมายพิเศษใน JS ดังนั้นคุณอาจเรียกได้bananashakeเช่นกัน แตกต่างเพียงว่าเครื่องยนต์เริ่มต้นโดยอัตโนมัติconstructorในเมื่อใดก็ตามที่คุณประกาศฟังก์ชั่นf.prototype fอย่างไรก็ตามสามารถเขียนทับได้ตลอดเวลา
user123444555621

58
@ Pumbaa80 - ฉันได้รับจุดของคุณ แต่ความจริงที่ว่าเครื่องยนต์โดยอัตโนมัติเริ่มต้นconstructorหมายความว่ามันจะมีความหมายพิเศษใน JS สวยมากโดยความหมาย
Wayne

13
ผมแค่อยากจะชี้แจงว่าเหตุผลที่ว่าทำไมการทำงานที่คุณกล่าวว่าผลงานเป็นเพราะคุณใช้แทนreturn new this.constructor(this.name); return new Person(this.name);เนื่องจากthis.constructorเป็นStudentฟังก์ชั่น (เพราะคุณตั้งค่าไว้ด้วยStudent.prototype.constructor = Student;) copyฟังก์ชั่นจะสิ้นสุดการเรียกใช้Studentฟังก์ชัน ฉันไม่แน่ใจว่าความตั้งใจของคุณคืออะไรกับ//just as badความคิดเห็น
CEGRD

12
@lwburk คุณหมายถึงอะไรโดย "// just as bad"?
CEGRD

6
ฉันคิดว่าฉันได้รับมัน แต่สิ่งที่ถ้าStudentคอนสตรัคได้เพิ่มอาร์กิวเมนต์เพิ่มเติมเช่น: Student(name, id)? จากนั้นเราจะต้องแทนที่copyฟังก์ชั่นการเรียกPersonรุ่นจากภายในแล้วคัดลอกidคุณสมบัติเพิ่มเติมหรือไม่
snapfractalpop

76

สิ่งนี้มีจุดประสงค์สำคัญหรือไม่?

ใช่และไม่.

ใน ES5 และก่อนหน้านี้ JavaScript ไม่ได้ใช้constructorเพื่ออะไร มันกำหนดว่าวัตถุเริ่มต้นในprototypeคุณสมบัติของฟังก์ชั่นจะมีมันและมันจะอ้างกลับไปที่ฟังก์ชั่นและนั่นก็คือมันที่มันไม่มีอะไรในสเปคที่อ้างถึงเลย

ที่เปลี่ยนแปลงใน ES2015 (ES6) ซึ่งเริ่มใช้มันเกี่ยวกับลำดับชั้นการสืบทอด ตัวอย่างเช่นPromise#thenใช้constructorคุณสมบัติของสัญญาที่คุณเรียกว่า (ผ่านSpeciesConstructor ) เมื่อสร้างสัญญาใหม่เพื่อส่งคืน มันยังมีส่วนร่วมในอาร์เรย์ย่อย (ผ่านArraySpeciesCreate )

นอกเหนือจากภาษานั้นบางครั้งผู้คนจะใช้มันเมื่อพยายามสร้างฟังก์ชั่น "โคลน" ทั่วไปหรือโดยทั่วไปเมื่อพวกเขาต้องการอ้างถึงสิ่งที่พวกเขาเชื่อว่าเป็นฟังก์ชันตัวสร้างของวัตถุ ประสบการณ์ของฉันคือการใช้มันหายาก แต่บางครั้งผู้คนก็ใช้มัน

มันไม่เป็นไรที่จะละเว้นหรือไม่

โดยค่าเริ่มต้นคุณจะต้องนำมันกลับมาเมื่อคุณแทนที่วัตถุในprototypeคุณสมบัติของฟังก์ชั่น:

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

หากคุณไม่ทำสิ่งนี้:

Student.prototype.constructor = Student;

... แล้วStudent.prototype.constructorสืบทอดจากPerson.prototypeที่ (น่าจะ) constructor = Personมี ดังนั้นมันจึงทำให้เข้าใจผิด และแน่นอนถ้าคุณกำลังซับคลาสสิ่งที่ใช้ (เช่นPromiseหรือArray) และไม่ได้ใช้class ¹ (ซึ่งจัดการสิ่งนี้ให้คุณ) คุณจะต้องแน่ใจว่าคุณตั้งค่าไว้อย่างถูกต้อง ดังนั้นโดยทั่วไป: มันเป็นความคิดที่ดี

มันไม่เป็นไรถ้าไม่มีอะไรในรหัสของคุณ (หรือรหัสห้องสมุดที่คุณใช้) ใช้มัน ฉันมั่นใจเสมอว่ามันถูกต่อสายอย่างถูกต้อง

แน่นอนว่าด้วยคำสำคัญของ ES2015 (aka ES6) classเวลาส่วนใหญ่ที่เราจะใช้มันเราไม่จำเป็นต้องทำอีกต่อไปเพราะมันจัดการกับเราเมื่อเราทำ

class Student extends Person {
}

¹ "... หากคุณกำลังซับคลาสสิ่งที่ใช้ (เช่นPromiseหรือArray) และไม่ใช้class... "  - เป็นไปได้ที่จะทำเช่นนั้น แต่มันเป็นความเจ็บปวดที่แท้จริง (และเซ่อเล็กน้อย) Reflect.constructคุณต้องใช้


12

TLDR; ไม่จำเป็นอย่างยิ่ง แต่อาจจะช่วยได้ในระยะยาวและแม่นยำยิ่งขึ้น

หมายเหตุ: แก้ไขมากเป็นคำตอบก่อนหน้าของฉันถูกเขียนสับสนและมีข้อผิดพลาดบางอย่างที่ฉันพลาดในการเร่งรีบของฉันที่จะตอบ ขอบคุณผู้ที่ชี้ให้เห็นข้อผิดพลาดอย่างมหันต์

โดยพื้นฐานแล้วการวางสายย่อยให้ถูกต้องใน Javascript เมื่อเรา subclass เราต้องทำสิ่งที่ขี้ขลาดเพื่อให้แน่ใจว่าการมอบหมายต้นแบบทำงานอย่างถูกต้องรวมถึงการเขียนทับ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);

newคำหลักเรียกด้วย 'คน' โดยทั่วไปจะเรียกใช้ตัวสร้างคนที่มีบรรทัดเพิ่มเติมไม่กี่รหัส:

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อินสแตนซ์และมองขึ้นห่วงโซ่ prototypal ของมัน.prototypeซึ่งเป็นPerson.prototype- และPerson.prototype มีคุณสมบัติเพื่อให้การค้นหาจะประสบความสำเร็จใน.species Person.prototypeจากนั้นจะเข้าสู่ระบบ'human'จากนั้นจะเข้าสู่ระบบ

ที่นี่ได้อย่างถูกต้องจะชี้ไปที่Person.prototype.constructorPerson

ดังนั้นตอนนี้ส่วนที่น่าสนใจที่เรียกว่า 'subclassing' หากเราต้องการสร้างStudentชั้นเรียนซึ่งเป็นคลาสย่อยของPersonชั้นเรียนที่มีการเปลี่ยนแปลงเพิ่มเติมบางอย่างเราจะต้องตรวจสอบให้แน่ใจว่าStudent.prototype.constructorคะแนนของนักเรียนนั้นถูกต้อง

มันไม่ได้ทำด้วยตัวเอง เมื่อคุณคลาสย่อยโค้ดจะมีลักษณะดังนี้:

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ผลตอบแทนที่แท้จริงและเพื่อให้กรณีของStudentผู้รับมอบสิทธิ์อย่างถูกต้องแล้วStudent.prototypePerson.prototype

แต่เนื่องจากเราเรียกมันด้วยnewคำหลักโปรดจำไว้ว่าการเรียกนั้นเพิ่มอะไร มันจะเรียกObject.create(Student.prototype)ซึ่งเป็นวิธีการที่เราตั้งค่าความสัมพันธ์ระหว่าง delegational และStudent Student.prototypeโปรดทราบว่าตอนนี้Student.prototypeว่างเปล่า ดังนั้นการค้นหา.speciesอินสแตนซ์ของStudentจะล้มเหลวเนื่องจากมอบหมายให้เท่านั้น Student.prototypeและ.speciesไม่มีคุณสมบัติอยู่Student.prototypeคุณสมบัติที่ไม่ได้อยู่ใน

เมื่อเราทำกำหนดStudent.prototypeไปObject.create(Person.prototype), Student.prototypeตัวเองแล้วมอบหมายให้Person.prototypeและมองขึ้นไปeve.speciesจะกลับมาhumanในขณะที่เราคาดหวัง สมมุติว่าเราต้องการให้สืบทอดมาจาก Student.prototype และ 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;
   }
}

eve instanceof Studenttrueกลับ ดูstackoverflow.com/questions/35537995/…สำหรับคำอธิบาย นอกจากนี้เมื่อคุณพูดในwhich is, at the moment, nothingสิ่งที่คุณหมายถึง? ทุกฟังก์ชั่นมีต้นแบบดังนั้นถ้าฉันตรวจสอบStudent.prototypeมันเป็นอะไรบางอย่าง
Aseem Bansal

ความผิดพลาดของฉัน. ควรอ่าน 'อีฟอินสแตนซ์ของบุคคล' ซึ่งจะส่งคืนค่าเท็จ ฉันจะแก้ไขส่วนนั้น คุณถูกต้องว่าทุกฟังก์ชั่นมีคุณสมบัติต้นแบบ อย่างไรก็ตามโดยไม่ต้องกำหนดต้นแบบให้กับObject.create(Person.prototype)ที่Student.prototypeว่างเปล่า ดังนั้นหากเราเข้าสู่ระบบeve.speciesก็จะไม่มอบหมายอย่างถูกต้องถึง superclass 'human'ของบุคคลและมันจะไม่ได้เข้าสู่ระบบ สมมุติว่าเราต้องการให้คลาสย่อยทุกคลาสสืบทอดจากต้นแบบและต้นแบบที่ยอดเยี่ยม
bthehuman

เพื่อชี้แจงโดยwhich is, at the moment, nothingฉันหมายถึงStudent.prototypeวัตถุนั้นว่างเปล่า
bthehuman

เพิ่มเติมเกี่ยวกับต้นแบบ: โดยไม่ต้องได้รับมอบหมายจากStudent.prototypeไปObject.create(Person.prototype)- ซึ่งเป็นถ้าคุณจำได้เช่นเดียวกับกรณีของผู้ที่ได้รับการจัดตั้งขึ้นเพื่อมอบหมายให้Person.prototype- มองขึ้นไปสถานที่ให้บริการในตัวอย่างของStudentจะมอบหมายให้เท่านั้น Student.prototypeดังนั้นeve.speciesจะล้มเหลวในการค้นหา ถ้าเราไม่กำหนดมันStudent.prototypeเองแล้วมอบหมายให้Person.prototypeและมองขึ้นไปจะกลับมาeve.species human
bthehuman

ดูเหมือนว่ามีบางสิ่งผิดปกติที่นี่: "จำเป็นเมื่อคุณพยายามเลียนแบบ 'subclassing' [... ] เพื่อที่ว่าเมื่อคุณตรวจสอบว่าอินสแตนซ์นั้นเป็นinstanceตัวสร้าง 'subclass' มันจะถูกต้องหรือไม่" Nope, ไม่ได้ใช้instanceof "แต่ถ้าเรามองขึ้น .prototype.constructor ของนักเรียนก็จะยังคงชี้ไปที่คน" Nope มันจะเป็น ฉันไม่เข้าใจประเด็นของตัวอย่างนี้ การเรียกใช้ฟังก์ชันในตัวสร้างไม่ใช่การสืบทอด "ใน ES6 ตัวสร้างอยู่ในขณะนี้เป็นฟังก์ชั่นจริงแทนการอ้างอิงถึงฟังก์ชั่น"เอ๊ะอะไร? constructorStudent
เฟลิกซ์คลิง

10

ฉันไม่เห็นด้วย ไม่จำเป็นต้องตั้งค่าต้นแบบ ใช้รหัสเดียวกันนั้น แต่ลบบรรทัด prototype.constructor มีอะไรเปลี่ยนแปลงหรือเปล่า? ไม่เลยทำการเปลี่ยนแปลงต่อไปนี้:

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

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

และในตอนท้ายของรหัสทดสอบ ...

alert(student1.favoriteColor);

สีจะเป็นสีน้ำเงิน

การเปลี่ยนแปลงตัวต้นแบบผู้สร้างในประสบการณ์ของฉันไม่ได้ทำอะไรมากนักเว้นแต่คุณจะทำสิ่งที่เฉพาะเจาะจงและซับซ้อนมากซึ่งอาจไม่ใช่แนวปฏิบัติที่ดี :)

แก้ไข: หลังจากจิ้มเว็บสักพักแล้วทำการทดลองบางอย่างดูเหมือนว่าผู้คนตั้งค่าคอนสตรัคเตอร์เพื่อให้ดูเหมือนว่าสิ่งที่กำลังสร้างด้วย 'ใหม่' ฉันเดาว่าฉันจะยืนยันว่าปัญหาของเรื่องนี้คือ javascript เป็นภาษาต้นแบบ - ไม่มีสิ่งใดที่สืบทอดมา แต่โปรแกรมเมอร์ส่วนใหญ่มาจากพื้นหลังของการเขียนโปรแกรมที่ผลักดันให้สืบทอดเป็น 'ทาง' ดังนั้นเราจึงลองทำหลาย ๆ อย่างเพื่อทำให้ภาษาต้นแบบนี้เป็นภาษา 'คลาสสิค' .. เช่นการขยาย 'คลาส' ในตัวอย่างที่พวกเขาให้นักเรียนใหม่เป็นคน - มันไม่ 'ขยาย' จากนักเรียนคนอื่น .. นักเรียนเป็นคนเกี่ยวกับบุคคลและสิ่งที่บุคคลนั้นเป็นนักเรียนก็เช่นกัน ขยายนักเรียนและสิ่งที่คุณ '

คร็อคฟอร์ดเป็นคนที่คลั่งไคล้และขี้ขลาดมาก แต่การอ่านอย่างจริงจังเกี่ยวกับสิ่งที่เขาเขียน .. มันจะทำให้คุณดูสิ่งนี้แตกต่างกันมาก


8
สิ่งนี้ไม่ได้สืบทอดสายโซ่ต้นแบบ
Cypher

1
@Cypher ตบมือช้ายินดีต้อนรับสู่การสนทนาสี่ปีต่อมา ใช่โซ่ต้นแบบนั้นสืบทอดมาไม่ว่าคุณจะเขียนทับต้นแบบหรือไม่ ลองทดสอบดูสิ
Stephen

7
คุณไม่มีรหัสที่สืบทอดต้นแบบ ยินดีต้อนรับสู่อินเทอร์เน็ต
Cypher

1
@ รหัสตัวอย่างโค้ดขึ้นอยู่กับรหัสในบทความที่เชื่อมโยง ยินดีต้อนรับสู่การอ่านคำถามทั้งหมด โอ้ รอ.
สตีเฟ่น

1
@ มาเชอร์ฉันหมายถึงมันเป็นมรดกตกทอด การเลือกใช้ถ้อยคำไม่ดีในส่วนของฉัน
Stephen

9

นี่เป็นข้อผิดพลาดที่ยิ่งใหญ่หากคุณเขียน

Student.prototype.constructor = Student;

แต่ถ้ามีครูคนหนึ่งซึ่งต้นแบบเป็นบุคคลและคุณเขียน

Teacher.prototype.constructor = Teacher;

ตอนนี้ผู้สร้างนักเรียนเป็นครู!

แก้ไข: คุณสามารถหลีกเลี่ยงสิ่งนี้ได้โดยให้แน่ใจว่าคุณได้ตั้งค่าต้นแบบนักเรียนและครูโดยใช้อินสแตนซ์ใหม่ของคลาสบุคคลที่สร้างขึ้นโดยใช้ Object.create เช่นเดียวกับตัวอย่างของ Mozilla

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

1
Student.prototype = Object.create(...)จะถือว่าอยู่ในคำถามนี้ คำตอบนี้ไม่ได้เพิ่มความสับสน แต่อย่างใด
André Chalella

3
@ AndréNevesฉันพบคำตอบนี้มีประโยชน์ Object.create(...)ใช้ในบทความ MDN ที่วางคำถาม แต่ไม่ได้อยู่ในคำถามนั้น ฉันแน่ใจว่าหลายคนไม่คลิกผ่าน
Alex Ross

บทความที่เชื่อมโยงอ้างถึงในคำถาม alreay ใช้ Object.create () คำตอบนี้และการแก้ไขคำตอบนั้นไม่เกี่ยวข้องกันจริง ๆ และทำให้สับสนว่า :-) น้อยที่สุด
Drenai

1
จุดที่กว้างขึ้นคือมี gotchas ที่จะขัดขวางคนใหม่ในต้นแบบ Javascript หากเรากำลังพูดถึงในปี 2559 คุณควรใช้คลาส ES6, Babel และ / หรือ Typescript แต่ถ้าคุณต้องการสร้างคลาสด้วยตนเองด้วยวิธีนี้จะช่วยให้เข้าใจว่าโซ่ต้นแบบนั้นทำงานอย่างไรเพื่อใช้ประโยชน์จากพลังของพวกเขา คุณสามารถใช้วัตถุใด ๆ เป็นต้นแบบและบางทีคุณไม่ต้องการสร้างวัตถุแยกต่างหากใหม่ นอกจากนี้ก่อนที่ HTML 5 จะแพร่หลายอย่างสมบูรณ์ Object.create ไม่สามารถใช้งานได้เสมอดังนั้นจึงง่ายต่อการตั้งค่าคลาสอย่างไม่ถูกต้อง
James D

5

จนถึงตอนนี้ยังมีความสับสนอยู่

ทำตามตัวอย่างดั้งเดิมเนื่องจากคุณมีวัตถุที่มีอยู่student1เป็น:

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

สมมติว่าคุณไม่ต้องการรู้วิธีการstudent1สร้างคุณแค่ต้องการวัตถุอื่นเช่นมันคุณสามารถใช้คุณสมบัตินวกรรมิกของstudent1ชอบ:

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

ที่นี่จะไม่สามารถรับคุณสมบัติได้Studentหากไม่ได้ตั้งค่าคุณสมบัติตัวสร้าง ค่อนข้างจะสร้างPersonวัตถุ


2

มีตัวอย่างโค้ดที่ดีว่าเหตุใดจึงจำเป็นต้องตั้งค่าตัวสร้างต้นแบบ

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 ???? 
*/

createNewCarวิธีการของคุณคือการสร้างโรงงาน! นอกจากนี้ดูเหมือนว่าควรมีการใช้งานvar audiFactory = new CarFactory("Audi")แทนการใช้การสืบทอด
Bergi

ตัวอย่างของคุณกำลังใช้this.constructorภายในดังนั้นจึงไม่น่าแปลกใจเลยที่จะต้องตั้งค่า คุณมีตัวอย่างหรือไม่ถ้าไม่มี
Dmitri Zaitsev

1

ไม่จำเป็นต้องใช้ 'คลาส' ของฟังก์ชัน sugared หรือใช้ 'ใหม่' ในวันนี้ ใช้ตัวอักษรวัตถุ

ต้นแบบวัตถุนั้นเป็น 'คลาส' อยู่แล้ว เมื่อคุณกำหนดวัตถุตามตัวอักษรมันเป็นตัวอย่างของวัตถุต้นแบบอยู่แล้ว สิ่งเหล่านี้ยังสามารถทำหน้าที่เป็นต้นแบบของวัตถุอื่นได้อีกด้วย

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 ++ ก่อตั้งขึ้นบนแนวคิดของเอนทิตีที่แตกต่างกันสองคลาสและอินสแตนซ์

...

ภาษาที่ใช้ต้นแบบเช่น JavaScript ไม่ได้แยกความแตกต่างนี้: มันมีเพียงวัตถุ ภาษาที่ใช้ต้นแบบมีแนวคิดของวัตถุต้นแบบซึ่งเป็นวัตถุที่ใช้เป็นแม่แบบที่จะได้รับคุณสมบัติเริ่มต้นสำหรับวัตถุใหม่ วัตถุใด ๆ สามารถระบุคุณสมบัติของตัวเองได้ไม่ว่าคุณจะสร้างมันหรือเวลาทำงาน นอกจากนี้วัตถุใด ๆ ที่สามารถเชื่อมโยงได้เป็นต้นแบบสำหรับวัตถุอื่นทำให้วัตถุที่สองสามารถแบ่งปันคุณสมบัติของวัตถุแรก


1

มีความจำเป็นเมื่อคุณต้องการทางเลือกอื่น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"]);


สิ่งนี้ควรจะทำอย่างไร? foo.constructor()??
Ry-

0

แก้ไขฉันผิดจริง การแสดงความคิดเห็นในบรรทัดไม่ได้เปลี่ยนพฤติกรรมของมันเลย (ฉันทดสอบแล้ว)


ใช่มันเป็นสิ่งที่จำเป็น เมื่อคุณทำ

Student.prototype = new Person();  

Student.prototype.constructorPersonจะกลายเป็น ดังนั้นโทรจะกลับวัตถุที่สร้างขึ้นโดยStudent() Personถ้าคุณทำแล้ว

Student.prototype.constructor = Student; 

Student.prototype.constructorStudentจะถูกรีเซ็ตกลับไป ตอนนี้เมื่อคุณเรียกStudent()มันเรียกใช้งานStudentซึ่งเรียกว่านวกรรมิกหลักParent()มันจะส่งคืนวัตถุที่สืบทอดมาอย่างถูกต้อง หากคุณไม่ได้ตั้งค่าก่อนที่จะเรียกมันว่าคุณจะได้รับวัตถุที่จะได้มีของคุณสมบัติที่กำหนดไว้ในStudent.prototype.constructorStudent()


3
โครงสร้างของต้นแบบอาจกลายเป็นบุคคล แต่ก็เหมาะสมเนื่องจากเป็นการสืบทอดคุณสมบัติและวิธีการทั้งหมดจากบุคคล การสร้าง Student ใหม่ () โดยไม่ตั้งค่า prototype.constructor ให้เรียก Constructor ของมันเองอย่างเหมาะสม
สตีเฟ่น

0

รับฟังก์ชั่นการสร้างง่าย:

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

นี่คือตัวอย่างหนึ่งจาก 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
});

-1

มันไม่จำเป็น มันเป็นเพียงหนึ่งในหลาย ๆ อย่างดั้งเดิม OOP แชมเปี้ยนทำเพื่อพยายามเปลี่ยนมรดกของจาวาสคริปต์เป็นมรดกคลาสสิก สิ่งเดียวที่ดังต่อไปนี้

Student.prototype.constructor = Student; 

ทำคือตอนนี้คุณมีการอ้างอิงของ "ตัวสร้าง" ในปัจจุบัน

ในคำตอบของ Wayne ที่ถูกทำเครื่องหมายว่าถูกต้องคุณสามารถทำสิ่งเดียวกันกับรหัสต่อไปนี้ได้

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

ด้วยรหัสด้านล่าง (เพียงแทนที่ this.constructor ด้วย Person)

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

ขอบคุณพระเจ้าที่มีผู้ฝึกสอนสืบทอดมรดกสืบทอดคลาสสิก ES6 สามารถใช้โอเปอเรเตอร์ดั้งเดิมของภาษาเช่นคลาสขยายและสุดยอดและเราไม่จำเป็นต้องมองเห็นเช่นต้นแบบการแก้ไขคอนสตรัคเตอร์และการอ้างอิงผู้ปกครอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.