Tại sao cần thiết phải xây dựng nguyên mẫu?


294

Trong phần về kế thừa trong bài viết MDN Giới thiệu về Javascript hướng đối tượng , tôi nhận thấy họ đặt nguyên mẫu.constructor:

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

Điều này có phục vụ mục đích quan trọng nào không? Có thể bỏ qua nó không?


23
Vui mừng bạn đã hỏi điều này: Tôi đã đọc tài liệu tương tự ngày hôm qua và tò mò về lý do đằng sau việc thiết lập rõ ràng các nhà xây dựng là tốt.
Wylie

6
Tôi chỉ cần chỉ ra điều này, câu hỏi này hiện được liên kết trong bài viết bạn liên kết!
Marie

7
không có gì là cần thiết
nothingisnecessary

1
Ý subclass.prototype.constructorchí sẽ chỉ đến parent_classnếu bạn không viết subclass.prototype.constructor = subclass; Đó là, sử dụng subclass.prototype.constructor()trực tiếp sẽ tạo ra một kết quả bất ngờ.
KuanYu Chu

Câu trả lời:


263

Nó không phải lúc nào cũng cần thiết, nhưng nó có công dụng của nó. Giả sử chúng ta muốn tạo một phương thức sao chép trên Personlớp cơ sở . Như thế này:

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

Bây giờ điều gì xảy ra khi chúng ta tạo một cái mới Studentvà sao chép nó?

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

Bản sao không phải là một ví dụ Student. Điều này là do (không có kiểm tra rõ ràng), chúng tôi không có cách nào để trả về một Studentbản sao từ lớp "cơ sở". Chúng tôi chỉ có thể trả lại a Person. Tuy nhiên, nếu chúng tôi đã thiết lập lại hàm tạo:

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

... sau đó mọi thứ hoạt động như mong đợi:

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

34
Lưu ý: constructorThuộc tính không có bất kỳ ý nghĩa đặc biệt nào trong JS, vì vậy bạn cũng có thể gọi nó bananashake. Sự khác biệt duy nhất là động cơ tự động khởi constructorvề f.prototypebất cứ khi nào bạn khai báo một hàm f. Tuy nhiên, nó có thể được ghi đè bất cứ lúc nào.
user123444555621

58
@ Pumbaa80 - tôi nhận được quan điểm của bạn, nhưng thực tế là động cơ tự động khởi constructornghĩa rằng nó không có ý nghĩa đặc biệt trong JS, khá nhiều theo định nghĩa.
Wayne

13
Tôi chỉ muốn làm rõ rằng lý do tại sao hành vi bạn nói hoạt động là vì bạn sử dụng return new this.constructor(this.name);thay vì return new Person(this.name);. Vì this.constructorStudenthàm (vì bạn đặt nó với Student.prototype.constructor = Student;), nên copyhàm kết thúc việc gọi Studenthàm. Tôi không chắc ý định của bạn là gì với //just as badbình luận.
CEGRD

12
@lwburk bạn có ý gì khi "// cũng tệ như vậy"?
CEGRD

6
Tôi nghĩ rằng tôi nhận được nó. Nhưng, điều gì sẽ xảy ra nếu hàm Studenttạo đã thêm một đối số bổ sung như : Student(name, id)? Sau đó chúng ta có phải ghi đè copychức năng, gọi Personphiên bản từ bên trong nó và sau đó sao chép thuộc tính bổ sung idkhông?
snapfractalpop

76

Điều này có phục vụ mục đích quan trọng nào không?

Có và không.

Trong ES5 và trước đó, JavaScript không sử dụng constructorcho bất cứ điều gì. Nó đã định nghĩa rằng đối tượng mặc định trên thuộc tính của hàm prototypesẽ có nó và nó sẽ quay trở lại hàm đó, và đó là nó . Không có gì khác trong đặc điểm kỹ thuật đề cập đến nó cả.

Điều đó đã thay đổi trong ES2015 (ES6), bắt đầu sử dụng nó liên quan đến hệ thống phân cấp thừa kế. Chẳng hạn, Promise#thensử dụng thuộc constructortính của lời hứa mà bạn gọi nó (thông qua SpeciesConstructor ) khi xây dựng lời hứa mới để trả lại. Nó cũng liên quan đến việc phân mảng các mảng (thông qua ArraySpeciesCreate ).

Bên ngoài ngôn ngữ, đôi khi mọi người sẽ sử dụng nó khi cố gắng xây dựng các hàm "nhân bản" chung chung hoặc chỉ nói chung khi họ muốn đề cập đến những gì họ tin sẽ là hàm tạo của đối tượng. Kinh nghiệm của tôi là sử dụng nó rất hiếm, nhưng đôi khi mọi người sử dụng nó.

Có thể bỏ qua nó không?

Theo mặc định, bạn chỉ cần đặt nó trở lại khi bạn thay thế đối tượng trên thuộc tính của hàm prototype:

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

Nếu bạn không làm điều này:

Student.prototype.constructor = Student;

... sau đó Student.prototype.constructorkế thừa từ Person.prototypeđó (có lẽ) có constructor = Person. Vì vậy, nó gây hiểu lầm. Và tất nhiên, nếu bạn phân lớp thứ gì đó sử dụng nó (như Promisehoặc Array) và không sử dụng class(xử lý việc này cho bạn), bạn sẽ muốn đảm bảo rằng bạn đặt chính xác. Về cơ bản: Đó là một ý tưởng tốt.

Không sao nếu không có gì trong mã của bạn (hoặc mã thư viện bạn sử dụng) sử dụng nó. Tôi luôn đảm bảo rằng nó được nối dây chính xác.

Tất nhiên, với từ khóa ES2015 (còn gọi là ES6) class, hầu hết thời gian chúng tôi sẽ sử dụng nó, chúng tôi không phải làm thế nữa, bởi vì nó xử lý cho chúng tôi khi chúng tôi làm

class Student extends Person {
}

¹ "... nếu bạn đang phân lớp thứ gì đó sử dụng nó (như Promisehoặc Array) và không sử dụng class..."  - Có thể làm điều đó, nhưng đó là một nỗi đau thực sự (và hơi ngớ ngẩn). Bạn phải sử dụng Reflect.construct.


12

TLDR; Không phải là siêu cần thiết, nhưng có thể sẽ giúp về lâu dài, và chính xác hơn là làm như vậy.

LƯU Ý: Đã chỉnh sửa nhiều vì câu trả lời trước của tôi được viết một cách khó hiểu và có một số lỗi mà tôi đã bỏ qua khi vội vàng trả lời. Cảm ơn những người đã chỉ ra một số lỗi nghiêm trọng.

Về cơ bản, đó là kết nối phân lớp chính xác trong Javascript. Khi chúng ta phân lớp, chúng ta phải thực hiện một số điều thú vị để đảm bảo rằng ủy nhiệm nguyên mẫu hoạt động chính xác, bao gồm ghi đè một prototypeđối tượng. Ghi đè một prototypeđối tượng bao gồm constructor, do đó chúng ta cần sửa tham chiếu.

Hãy nhanh chóng xem xét cách 'các lớp' trong ES5 hoạt động.

Giả sử bạn có một hàm tạo và nguyên mẫu của nó:

//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',
}

Khi bạn gọi hàm tạo để khởi tạo, hãy nói Adam:

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

Các newtừ khóa gọi với 'Người' về cơ bản sẽ chạy constructor Person với một vài dòng bổ sung mã:

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

Nếu chúng ta console.log(adam.species), việc tra cứu sẽ thất bại trong adamtrường hợp đó, và tìm kiếm chuỗi nguyên mẫu cho nó .prototype, đó là Person.prototype- và Person.prototype một .speciestài sản, vì vậy việc tra cứu sẽ thành công Person.prototype. Sau đó nó sẽ đăng nhập 'human'.

Ở đây, Person.prototype.constructorsẽ chính xác chỉ đến Person.

Vì vậy, bây giờ là phần thú vị, cái gọi là 'phân lớp'. Nếu chúng tôi muốn tạo một Studentlớp, đó là một lớp con của Personlớp với một số thay đổi bổ sung, chúng tôi sẽ cần đảm bảo rằng các Student.prototype.constructorđiểm cho Sinh viên chính xác.

Nó không tự làm điều này. Khi bạn phân lớp, mã trông như thế này:

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: {}

Gọi new Student()ở đây sẽ trả về một đối tượng với tất cả các thuộc tính chúng ta muốn. Ở đây, nếu chúng tôi kiểm tra eve instanceof Person, nó sẽ trở lại false. Nếu chúng ta cố gắng truy cập eve.species, nó sẽ trở lại undefined.

Nói cách khác, chúng ta cần kết nối ủy quyền để eve instanceof Persontrả về giá trị true và sao cho các trường hợp Studentủy nhiệm chính xác Student.prototype, và sau đó Person.prototype.

NHƯNG vì chúng ta đang gọi nó bằng newtừ khóa, hãy nhớ những gì mà lời gọi đó thêm vào? Nó sẽ gọi Object.create(Student.prototype), đó là cách chúng tôi thiết lập mối quan hệ ủy thác giữa StudentStudent.prototype. Lưu ý rằng ngay bây giờ, Student.prototypelà trống rỗng. Vì vậy, nhìn lên .speciesmột thể hiện của Studentsẽ thất bại vì nó đại biểu duy nhất Student.prototype , và các .speciestài sản không tồn tại trên Student.prototype.

Khi chúng ta làm assign Student.prototypeđể Object.create(Person.prototype), Student.prototypebản thân sau đó các đại biểu đến Person.prototype, ngước mắt lên eve.speciessẽ trở lại humannhư chúng ta mong đợi. Có lẽ chúng tôi muốn nó kế thừa từ Student.prototype AND Person.prototype. Vì vậy, chúng ta cần khắc phục tất cả điều đó.

/* 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);

Bây giờ phái đoàn làm việc, nhưng chúng tôi ghi đè Student.prototypebằng một Person.prototype. Vì vậy, nếu chúng ta gọi Student.prototype.constructor, nó sẽ chỉ đến Personthay vì Student. Đây là lý do tại sao chúng ta cần sửa nó.

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

Trong ES5, thuộc tính của chúng tôi constructorlà một tham chiếu đề cập đến một chức năng mà chúng tôi đã viết với mục đích trở thành một 'nhà xây dựng'. Ngoài những gì newtừ khóa mang lại cho chúng ta, thì hàm tạo còn là một hàm 'đơn giản'.

Trong ES6, constructorgiờ đây được xây dựng theo cách chúng ta viết các lớp - như trong, nó được cung cấp như một phương thức khi chúng ta khai báo một lớp. Đây chỉ đơn giản là đường cú pháp nhưng nó phù hợp với chúng ta một số tiện ích như truy cập vào superkhi chúng ta đang mở rộng một lớp hiện có. Vì vậy, chúng tôi sẽ viết mã ở trên như thế này:

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 Studenttrở về true. Xem stackoverflow.com/questions/35537995/ khăn để giải thích. Ngoài ra khi bạn nói which is, at the moment, nothingnhững gì bạn đang đề cập đến? Mỗi chức năng đều có một nguyên mẫu vì vậy nếu tôi kiểm tra Student.prototypenó là một cái gì đó.
Aseem Bansal

Lỗi của tôi. Nó nên đọc 'eve instanceof Person' sẽ trả về false. Tôi sẽ sửa đổi phần đó. Bạn đúng rằng mọi chức năng đều có thuộc tính nguyên mẫu. Tuy nhiên , không gán nguyên mẫu cho Object.create(Person.prototype), Student.prototypethì trống rỗng. Vì vậy, nếu chúng ta đăng nhập eve.species, nó sẽ không ủy quyền chính xác cho siêu lớp của nó, Person và nó sẽ không đăng nhập 'human'. Có lẽ, chúng tôi muốn mọi lớp con kế thừa từ nguyên mẫu của nó và cũng là nguyên mẫu siêu của nó.
bthehuman

Để làm rõ, bởi which is, at the moment, nothing, tôi có nghĩa là Student.prototypeđối tượng là trống rỗng.
bthehuman

Thông tin thêm về các nguyên mẫu: Nếu không có sự phân công Student.prototypeđến Object.create(Person.prototype)- đó là, nếu bạn gọi lại, giống như cách tất cả các trường hợp của Người được thành lập để đại biểu đến Person.prototype- nhìn lên một tài sản trên một thể hiện của Studentsẽ uỷ thác cho chỉ Student.prototype . Vì vậy, eve.speciessẽ thất bại tra cứu của nó. Nếu chúng ta chỉ định nó, thì Student.prototypechính nó sẽ ủy thác Person.prototypevà tìm kiếm eve.speciessẽ trở lại human.
bthehuman

Dường như có khá nhiều điều sai ở đây: "Cần thiết khi bạn cố gắng mô phỏng 'phân lớp' [...] để khi bạn kiểm tra xem một cá thể có là Trình instancetạo 'lớp con' hay không, nó sẽ chính xác." Không, instanceofkhông sử dụng constructor. "Tuy nhiên, nếu chúng ta tra cứu .prototype.constructor của học sinh, nó vẫn sẽ trỏ đến Người" Không, nó sẽ như vậy Student. Tôi không hiểu ý của ví dụ này. Gọi một hàm trong một constructor không phải là sự kế thừa. "Trong ES6, hàm tạo bây giờ là một hàm thực tế thay vì tham chiếu đến hàm" Uh gì?
Felix Kling

10

Tôi không đồng ý. Không cần thiết phải đặt nguyên mẫu. Lấy mã chính xác đó nhưng loại bỏ dòng mẫu.constructor. Có gì thay đổi không? Không. Bây giờ, thực hiện các thay đổi sau:

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

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

và ở cuối mã kiểm tra ...

alert(student1.favoriteColor);

Màu sẽ là màu xanh.

Theo tôi, một thay đổi đối với nguyên mẫu. Người xây dựng, sẽ không làm được gì nhiều trừ khi bạn làm những việc rất cụ thể, rất phức tạp mà có lẽ không phải là cách thực hành tốt :)

Chỉnh sửa: Sau khi tìm hiểu trên web một chút và thực hiện một số thử nghiệm, có vẻ như mọi người đã đặt công cụ xây dựng sao cho nó trông giống như thứ đang được xây dựng với 'mới'. Tôi đoán tôi sẽ tranh luận rằng vấn đề với điều này là javascript là ngôn ngữ nguyên mẫu - không có thứ gọi là kế thừa. Nhưng hầu hết các lập trình viên đến từ một nền tảng lập trình đẩy sự kế thừa là 'con đường'. Vì vậy, chúng tôi nghĩ ra đủ thứ để thử và biến ngôn ngữ nguyên mẫu này thành ngôn ngữ 'cổ điển' .. chẳng hạn như mở rộng 'lớp học'. Thực sự, trong ví dụ họ đưa ra, một học sinh mới là một người - đó không phải là 'mở rộng' từ một học sinh khác .. học sinh là tất cả về người đó, và bất cứ điều gì người đó cũng là học sinh. Mở rộng sinh viên, và bất cứ điều gì bạn '

Crockford hơi điên rồ và quá nhiệt tình, nhưng hãy đọc một số thứ nghiêm túc về một số thứ anh ấy viết .. nó sẽ khiến bạn nhìn những thứ này rất khác.


8
Điều này không kế thừa chuỗi nguyên mẫu.
Cypher

1
@Cypher vỗ tay chậm chào mừng cuộc trò chuyện, bốn năm sau. Vâng, chuỗi nguyên mẫu được kế thừa, bất kể bạn có ghi đè lên nguyên mẫu hay không. Hãy thử kiểm tra nó.
Stephen

7
Bạn đang thiếu mã kế thừa nguyên mẫu. Chào mừng bạn đến với internet.
Cypher

1
Đoạn mã @Cypher được dựa trên mã trong bài viết được liên kết. Chào mừng bạn đến đọc toàn bộ câu hỏi. Oh. Chờ đợi.
Stephen

1
@macher Ý tôi là đó là sự kế thừa cổ điển. Lựa chọn từ ngữ kém về phía tôi.
Stephen

9

Điều này có một cạm bẫy lớn mà nếu bạn viết

Student.prototype.constructor = Student;

nhưng sau đó nếu có một Giáo viên có nguyên mẫu cũng là Người và bạn đã viết

Teacher.prototype.constructor = Teacher;

sau đó, sinh viên xây dựng bây giờ là giáo viên!

Chỉnh sửa: Bạn có thể tránh điều này bằng cách đảm bảo rằng bạn đã đặt các nguyên mẫu Học sinh và Giáo viên bằng cách sử dụng các phiên bản mới của lớp Person được tạo bằng Object.create, như trong ví dụ Mozilla.

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

1
Student.prototype = Object.create(...)được giả định trong câu hỏi này. Câu trả lời này không thêm gì ngoài sự nhầm lẫn có thể.
André Chalella

3
@ AndréNeves Tôi thấy câu trả lời này hữu ích. Object.create(...)được sử dụng trong bài viết MDN sinh ra câu hỏi, nhưng không phải trong chính câu hỏi. Tôi chắc chắn nhiều người không nhấp qua.
Alex Ross

Bài viết được liên kết được tham chiếu trong câu hỏi alreay sử dụng Object.create (). Câu trả lời này và Chỉnh sửa nếu câu trả lời không thực sự phù hợp và thật khó hiểu khi nói ít nhất :-)
Drenai

1
Điểm rộng hơn là có những vấn đề rắc rối sẽ khiến mọi người mới sử dụng các nguyên mẫu Javascript. Nếu chúng ta đang thảo luận vào năm 2016, thì bạn thực sự nên sử dụng các lớp ES6, Babel và / hoặc Bản in. Nhưng nếu bạn thực sự muốn xây dựng các lớp theo cách thủ công theo cách này, sẽ giúp hiểu cách các chuỗi nguyên mẫu thực sự hoạt động để tận dụng sức mạnh của chúng. Bạn có thể sử dụng bất kỳ đối tượng nào làm nguyên mẫu và có thể bạn không muốn làm mới một đối tượng riêng biệt. Hơn nữa, trở lại trước khi HTML 5 được phổ biến rộng rãi, Object.create không phải lúc nào cũng có sẵn, do đó dễ dàng hơn để thiết lập một lớp không chính xác.
James D

5

Cho đến nay sự nhầm lẫn vẫn còn đó.

Theo ví dụ ban đầu, khi bạn có một đối tượng hiện có student1là:

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

Giả sử bạn không muốn biết nó student1được tạo như thế nào , bạn chỉ muốn một đối tượng khác giống như vậy, bạn có thể sử dụng thuộc tính constructor student1như:

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

Ở đây nó sẽ không nhận được các thuộc tính từ Studentnếu thuộc tính constructor không được đặt. Thay vào đó nó sẽ tạo ra một Personđối tượng.


2

Có một ví dụ mã đẹp về lý do tại sao thực sự cần thiết để thiết lập hàm tạo nguyên mẫu ..

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

createNewCarPhương pháp của bạn là tạo ra các nhà máy!? Ngoài ra, điều này có vẻ như nó nên được sử dụng var audiFactory = new CarFactory("Audi")thay vì sử dụng thừa kế.
Bergi

Ví dụ của bạn đang sử dụng this.constructornội bộ, vì vậy không có gì đáng ngạc nhiên khi nó phải được đặt. Bạn có ví dụ nào mà không có nó không?
Dmitri Zaitsev

1

Không cần chức năng có đường 'lớp' hoặc sử dụng 'Mới' những ngày này. Sử dụng đối tượng bằng chữ.

Nguyên mẫu Object đã là một 'lớp'. Khi bạn xác định một đối tượng bằng chữ, nó đã là một thể hiện của đối tượng nguyên mẫu. Chúng cũng có thể hoạt động như một nguyên mẫu của đối tượng khác, v.v.

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]

Điều này đáng để đọc :

Các ngôn ngữ hướng đối tượng dựa trên lớp, như Java và C ++, được thành lập dựa trên khái niệm hai thực thể riêng biệt: lớp và thể hiện.

...

Một ngôn ngữ dựa trên nguyên mẫu, chẳng hạn như JavaScript, không tạo ra sự khác biệt này: đơn giản là nó có các đối tượng. Một ngôn ngữ dựa trên nguyên mẫu có khái niệm về một đối tượng nguyên mẫu, một đối tượng được sử dụng làm khuôn mẫu để lấy các thuộc tính ban đầu cho một đối tượng mới. Bất kỳ đối tượng nào cũng có thể chỉ định các thuộc tính của riêng nó, khi bạn tạo nó hoặc trong thời gian chạy. Ngoài ra, bất kỳ đối tượng nào cũng có thể được liên kết làm nguyên mẫu cho một đối tượng khác, cho phép đối tượng thứ hai chia sẻ các thuộc tính của đối tượng thứ nhất


1

Nó là cần thiết khi bạn cần một giải pháp thay thế toStringmà không cần ghép lại:

//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"]);


Những gì được cho là phải làm gì? foo.constructor()??
Ry-

0

EDIT, tôi đã thực sự sai. Nhận xét dòng ra không thay đổi hành vi của nó cả. (Tôi đã thử nó)


Vâng, nó là cần thiết. Khi bạn làm

Student.prototype = new Person();  

Student.prototype.constructortrở thành Person. Do đó, việc gọi Student()sẽ trả về một đối tượng được tạo bởi Person. Nếu bạn sau đó làm

Student.prototype.constructor = Student; 

Student.prototype.constructorđược đặt lại Student. Bây giờ khi bạn gọi Student()nó thực thi Student, gọi hàm xây dựng cha Parent(), nó sẽ trả về đối tượng được kế thừa chính xác. Nếu bạn không thiết lập lại Student.prototype.constructortrước khi gọi nó, bạn sẽ nhận được một đối tượng không có bất kỳ thuộc tính nào được đặt Student().


3
Cấu trúc nguyên mẫu có thể trở thành một người, nhưng điều đó là phù hợp vì nó được kế thừa tất cả các thuộc tính và phương thức từ Người. Tạo một Sinh viên mới () mà không cần đặt nguyên mẫu.con constructor gọi một cách thích hợp hàm tạo của chính nó.
Stephen

0

Cho hàm xây dựng đơn giản:

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(){...}

Theo mặc định (từ đặc tả https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor ), tất cả các nguyên mẫu sẽ tự động lấy một thuộc tính được gọi là hàm tạo mà nó là một tài sản. Tùy thuộc vào hàm tạo, các thuộc tính và phương thức khác có thể được thêm vào nguyên mẫu không phải là một cách làm rất phổ biến nhưng vẫn được phép mở rộng.

Vì vậy, chỉ cần trả lời: chúng ta cần đảm bảo rằng giá trị trong nguyên mẫu.constructor được đặt chính xác theo yêu cầu của thông số kỹ thuật.

Chúng ta phải luôn luôn đặt chính xác giá trị này? Nó giúp gỡ lỗi và làm cho cấu trúc bên trong phù hợp với đặc điểm kỹ thuật. Chúng ta nên chắc chắn khi API của chúng tôi đang được sử dụng bởi các bên thứ ba, nhưng không thực sự khi mã cuối cùng được thực thi trong thời gian chạy.


0

Đây là một ví dụ từ MDN mà tôi thấy rất hữu ích để hiểu cách sử dụng của nó.

Trong JavaScript, chúng ta có async functionstrả về đối tượng AsyncFunction . AsyncFunctionkhông phải là một đối tượng toàn cầu nhưng người ta có thể lấy nó bằng cách sử dụng constructortài sản và sử dụng nó.

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

Nó không phải là cần thiết. Đó chỉ là một trong nhiều điều truyền thống, các nhà vô địch OOP làm để cố gắng biến sự kế thừa nguyên mẫu của JavaScript thành sự kế thừa cổ điển. Điều duy nhất sau đây

Student.prototype.constructor = Student; 

không, là bây giờ bạn có một tham chiếu của "hàm tạo" hiện tại.

Trong câu trả lời của Wayne, điều đó đã được đánh dấu là chính xác, bạn có thể chính xác điều tương tự như đoạn mã sau

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

với mã bên dưới (chỉ cần thay thế this.constructor bằng Person)

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

Cảm ơn Chúa rằng với những người theo chủ nghĩa thừa kế cổ điển ES6 có thể sử dụng các toán tử bản địa của ngôn ngữ như lớp, mở rộng và siêu và chúng ta không phải xem như sửa lỗi nguyên mẫu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.