JavaScript .prototype hoạt động như thế nào?


2041

Tôi không biết điều đó bằng các ngôn ngữ lập trình động nhưng tôi đã viết phần mã JavaScript hợp lý của mình. Tôi chưa bao giờ thực sự hiểu về lập trình dựa trên nguyên mẫu này, có ai biết nó hoạt động như thế nào không?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Tôi nhớ rất nhiều cuộc thảo luận tôi đã có với mọi người trước đây (tôi không chắc chắn chính xác những gì tôi đang làm) nhưng theo tôi hiểu thì không có khái niệm về một lớp học. Nó chỉ là một đối tượng và các thể hiện của các đối tượng đó là bản sao của bản gốc, phải không?

Nhưng mục đích chính xác của thuộc tính ".prototype" này trong JavaScript là gì? Làm thế nào nó liên quan đến các đối tượng khởi tạo?

Cập nhật: cách chính xác

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

Ngoài ra những slide này thực sự đã giúp rất nhiều.


78
John Resig có một vài slide về các nguyên mẫu hàm hữu ích cho tôi khi xem xét chủ đề (bạn cũng có thể thay đổi mã và xem điều gì xảy ra ...) http://ejohn.org/apps/learn/#64
John Foster

5
Tài liệu tham khảo tuyệt vời, với mục đích giữ thông tin cho câu hỏi này có lẽ đặt một số ý kiến ​​từ trang web của John vào câu trả lời của bạn trong trường hợp trang web của anh ấy thay đổi theo cách liên kết của bạn không còn nữa. Dù bằng cách nào +1, đã giúp tôi.
Chris

95
+1 cho liên kết của bạn đến slide Ninja Ninja của John Resig # 64 . Bắt đầu từ đó thực sự hữu ích, và tôi cảm thấy mình hiểu đúng nguyên mẫu.
một mọt sách được trả tiền vào

4
Chúng ta có thực sự cần một đối tượng chức năng để áp dụng nguyên mẫu không? nếu có hơn tại sao?
Anshul

6
Điều này có thể giúp bạn: webdeveasy.com/javascript-prototype
Naor

Câu trả lời:


1007

Mỗi đối tượng JavaScript có một "khe" bên trong được gọi là [[Prototype]]giá trị của chúng là nullhoặc object. Bạn có thể nghĩ về một vị trí như một thuộc tính trên một đối tượng, bên trong công cụ JavaScript, ẩn khỏi mã bạn viết. Các dấu ngoặc vuông xung quanh [[Prototype]]là có chủ ý và là quy ước đặc tả ECMAScript để biểu thị các vị trí bên trong.

Giá trị được chỉ ra bởi [[Prototype]]một đối tượng, được gọi chung là "nguyên mẫu của đối tượng đó".

Nếu bạn truy cập một thuộc tính thông qua ký hiệu dấu chấm ( obj.propName) hoặc ngoặc ( obj['propName']) và đối tượng không trực tiếp có thuộc tính đó (ví dụ: một thuộc tính riêng , có thể kiểm tra qua obj.hasOwnProperty('propName')), thời gian chạy sẽ tìm một thuộc tính có tên đó trên đối tượng được tham chiếu bằng cách [[Prototype]]thay thế. Nếu [[Prototype]] cũng không có một tài sản như vậy, nó [[Prototype]]sẽ được kiểm tra lần lượt, v.v. Theo cách này, chuỗi nguyên mẫu của đối tượng ban đầu được thực hiện cho đến khi tìm thấy kết quả khớp hoặc kết thúc của nó. Ở đầu chuỗi nguyên mẫu là nullgiá trị.

Việc triển khai JavaScript hiện đại cho phép đọc và / hoặc ghi quyền truy cập [[Prototype]]theo các cách sau:

  1. Các newnhà điều hành (cấu hình chuỗi nguyên mẫu trên đối tượng mặc định trở về từ một hàm constructor),
  2. Các extendstừ khóa (cấu hình chuỗi nguyên mẫu khi sử dụng cú pháp lớp),
  3. Object.createsẽ đặt đối số được cung cấp làm [[Prototype]]đối tượng kết quả,
  4. Object.getPrototypeOfObject.setPrototypeOf (nhận / thiết lập [[Prototype]] sau khi tạo đối tượng) và
  5. Thuộc tính truy cập được tiêu chuẩn hóa (tức là getter / setter) có tên __proto__(tương tự 4.)

Object.getPrototypeOfObject.setPrototypeOfđược ưa thích hơn __proto__, một phần vì hành vi củao.__proto__ không bình thường khi một đối tượng có nguyên mẫu null.

Một đối tượng [[Prototype]]ban đầu được thiết lập trong quá trình tạo đối tượng.

Nếu bạn tạo một đối tượng mới thông qua new Func(), đối tượng đó[[Prototype]] , theo mặc định, sẽ được đặt thành đối tượng được tham chiếu bởi Func.prototype.

Lưu ý rằng, do đó, tất cả các lớp và tất cả các hàm có thể được sử dụng với newtoán tử, đều có một thuộc tính được đặt tên .prototypengoài [[Prototype]]khe cắm bên trong của riêng chúng .Việc sử dụng kép từ "nguyên mẫu" này là nguồn gốc của sự nhầm lẫn vô tận giữa những người mới tiếp cận với ngôn ngữ này.

Sử dụng new với các hàm tạo cho phép chúng ta mô phỏng kế thừa cổ điển trong JavaScript; mặc dù hệ thống kế thừa của JavaScript là - như chúng ta đã thấy - nguyên mẫu và không dựa trên lớp.

Trước khi giới thiệu cú pháp lớp cho JavaScript, các hàm xây dựng là cách duy nhất để mô phỏng các lớp. Chúng ta có thể nghĩ các thuộc tính của đối tượng được tham chiếu bởi thuộc tính của hàm tạo .prototypelà các thành viên được chia sẻ; I E. các thành viên giống nhau cho mỗi trường hợp. Trong các hệ thống dựa trên lớp, các phương thức được triển khai theo cùng một cách cho từng trường hợp, vì vậy các phương thức được thêm vào khái niệm vào.prototype tính; Tuy nhiên, các trường của một đối tượng là đặc thù và do đó được thêm vào chính đối tượng đó trong quá trình xây dựng.

Không có cú pháp lớp, các nhà phát triển phải tự cấu hình chuỗi nguyên mẫu để đạt được chức năng tương tự như kế thừa cổ điển. Điều này dẫn đến một ưu thế của các cách khác nhau để đạt được điều này.

Đây là một cách:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... Và đây là một cách khác:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

Cú pháp lớp được giới thiệu trong ES2015 đơn giản hóa mọi thứ, bằng cách cung cấp extends dưới dạng "một cách thực sự" để định cấu hình chuỗi nguyên mẫu để mô phỏng kế thừa cổ điển trong JavaScript.

Vì vậy, tương tự như mã ở trên, nếu bạn sử dụng cú pháp lớp để tạo một đối tượng mới như vậy:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... đối tượng kết quả [[Prototype]]sẽ được đặt thành một thể hiện của Parent, [[Prototype]]lần lượt là của ai Parent.prototype.

Cuối cùng, nếu bạn tạo một đối tượng mới thông qua Object.create(foo), đối tượng kết quả [[Prototype]]sẽ được đặt thành foo.


1
Vì vậy, tôi đang làm gì đó sai bằng cách xác định các thuộc tính mới trên thuộc tính nguyên mẫu trong đoạn trích ngắn của tôi?
John Leidegren

3
Tôi nghĩ rằng đây là ý nghĩa của việc có các đối tượng chức năng là công dân hạng nhất.
John Leidegren

8
Tôi ghét những thứ không chuẩn, đặc biệt là trong các ngôn ngữ lập trình, tại sao thậm chí còn có một proto khi nó rõ ràng không cần thiết?
John Leidegren

1
@John __proto__ chỉ cần để sử dụng nội bộ bởi trình thông dịch JS. Mỗi Đối tượng cần biết các thuộc tính và phương thức nào là một phần của nguyên mẫu và là một phần của chính Đối tượng. Trình thông dịch JS cần có khả năng gọi các phương thức được tạo mẫu trên Object.
BMiner

17
lưu ý rằng việc sử dụng [[Prototype]] là có chủ ý - ECMA-262 kèm theo tên của các thuộc tính bên trong với dấu ngoặc vuông
Christoph

1798

Trong một ngôn ngữ triển khai kế thừa cổ điển như Java, C # hoặc C ++, bạn bắt đầu bằng cách tạo một lớp - một bản thiết kế cho các đối tượng của bạn - và sau đó bạn có thể tạo các đối tượng mới từ lớp đó hoặc bạn có thể mở rộng lớp, xác định một lớp mới tăng thêm lớp gốc.

Trong JavaScript, trước tiên bạn tạo một đối tượng (không có khái niệm về lớp), sau đó bạn có thể gia tăng đối tượng của riêng mình hoặc tạo các đối tượng mới từ nó. Nó không khó, nhưng hơi xa lạ và khó chuyển hóa cho ai đó đã quen với cách cổ điển.

Thí dụ:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

Cho đến bây giờ tôi đã mở rộng đối tượng cơ sở, bây giờ tôi tạo một đối tượng khác và sau đó kế thừa từ Person.

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

Trong khi nói, tôi không thể gọi setAmountDue (), getAmountDue () trên một Người.

//The following statement generates an error.
john.setAmountDue(1000);

352
Tôi nghĩ rằng các câu trả lời trên stackoverflow không chỉ thú vị với poster gốc mà còn cho cả một cộng đồng lớn của những người khác đang rình rập hoặc đến từ các tìm kiếm. Và tôi đã là một trong số họ và tôi đã được hưởng lợi từ các bài viết cũ. Tôi nghĩ rằng tôi có thể đóng góp cho các câu trả lời khác thêm một số ví dụ mã. Về câu hỏi của bạn: nếu bạn bỏ qua cái mới, nó không hoạt động. khi tôi gọi myCustomer.sayMyName () thì nó trả về "myCustomer.sayMyName không phải là một hàm". Cách dễ nhất là thử nghiệm với con bọ lửa và xem điều gì sẽ xảy ra.
stivlo

7
Theo như tôi hiểu var Person = function (name) {...}; đang xác định hàm xây dựng có khả năng xây dựng Đối tượng Người. Vì vậy, không có đối tượng nào, chỉ có chức năng xây dựng ẩn danh được gán cho Person. Đây là một lời giải thích rất hay: helephant.com/2008/08/how-javascript-objects-work
stivlo

17
CẢNH BÁO: Câu trả lời này bỏ qua thực tế là hàm tạo của lớp cha không được gọi trên cơ sở mỗi trường hợp. Lý do duy nhất nó hoạt động là vì anh ta đã làm chính xác cùng một thứ (đặt tên) trong cả hàm tạo con và hàm cha. Để được giải thích sâu hơn về các lỗi phổ biến được thực hiện khi thử tính kế thừa trong JavaScript (và giải pháp cuối cùng), vui lòng xem: bài viết này về ngăn xếp tràn
Aaren Cordova

3
Tôi nhận thấy rằng câu trả lời này cũng không đề cập đến việc bằng cách sử dụng "new Person ()" làm nguyên mẫu, bạn thực sự đang đặt thuộc tính cá thể "name" của "Person" thành một thuộc tính tĩnh của "Khách hàng" (vì vậy tất cả Khách hàng trường hợp sẽ có cùng thuộc tính). Trong khi đó là một ví dụ cơ bản tốt, ĐỪNG LÀM ĐƯỢC. :) Tạo một hàm ẩn danh mới để hoạt động như một "cầu nối" bằng cách đặt nguyên mẫu của nó thành "Person.prototype", sau đó tạo một thể hiện từ nó và đặt "Customer.prototype" thành thể hiện ẩn danh đó.
James Wilkins

10
Về Customer.prototype = new Person();dòng, MDN hiển thị một ví dụ sử dụng Customer.prototype = Object.create(Person.prototype)và nói rằng 'Một lỗi phổ biến ở đây là sử dụng "new Person ()"' . nguồn
Rafael Eyng

186

Đây là một mô hình đối tượng dựa trên nguyên mẫu rất đơn giản sẽ được coi là một mẫu trong quá trình giải thích, chưa có bình luận nào:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

Có một số điểm quan trọng mà chúng ta phải xem xét trước khi đi qua khái niệm nguyên mẫu.

1- Cách các hàm JavaScript thực sự hoạt động:

Để thực hiện bước đầu tiên, chúng ta phải tìm hiểu xem các hàm JavaScript thực sự hoạt động như thế nào, như một lớp giống như hàm sử dụng thistừ khóa trong đó hoặc chỉ là một hàm thông thường với các đối số của nó, nó làm gì và trả về cái gì.

Giả sử chúng ta muốn tạo một Personmô hình đối tượng. nhưng trong bước này tôi sẽ cố gắng làm điều tương tự chính xác mà không cần sử dụng prototypenewtừ khóa .

Vì vậy, trong bước này functions, objectsthistừ khóa, là tất cả chúng ta có.

Câu hỏi đầu tiên là làm thế nào thistừ khóa có thể hữu ích mà không cần sử dụng newtừ khóa .

Vì vậy, để trả lời rằng giả sử chúng ta có một đối tượng trống và hai hàm như:

var person = {};
function Person(name){  this.name = name;  }

function getName(){
    console.log(this.name);
}

và bây giờ không sử dụng newtừ khóa, làm thế nào chúng ta có thể sử dụng các chức năng này. Vì vậy, JavaScript có 3 cách khác nhau để làm điều đó:

a. Cách đầu tiên là gọi hàm như một hàm thông thường:

Person("George");
getName();//would print the "George" in the console

trong trường hợp này, đây sẽ là đối tượng bối cảnh hiện tại, thường là windowđối tượng toàn cầu trong trình duyệt hoặc GLOBALtrongNode.js . Điều đó có nghĩa là chúng ta sẽ có, window.name trong trình duyệt hoặc GLOBAL.name trong Node.js, với giá trị là "George".

b. Chúng ta có thể gắn chúng vào một đối tượng, như các thuộc tính của nó

- Cách dễ nhất để làm điều này là sửa đổi personđối tượng trống , như:

person.Person = Person;
person.getName = getName;

bằng cách này chúng ta có thể gọi chúng như:

person.Person("George");
person.getName();// -->"George"

và bây giờ personđối tượng giống như:

Object {Person: function, getName: function, name: "George"}

- Cách khác để đính kèm một thuộc tính vào một đối tượng là sử dụng prototypeđối tượng đó có thể tìm thấy trong bất kỳ đối tượng JavaScript nào có tên __proto__và tôi đã cố gắng giải thích một chút về phần tóm tắt. Vì vậy, chúng ta có thể nhận được kết quả tương tự bằng cách làm:

person.__proto__.Person = Person;
person.__proto__.getName = getName;

Nhưng theo cách này, những gì chúng ta thực sự đang làm là sửa đổi Object.prototype, bởi vì bất cứ khi nào chúng ta tạo một đối tượng JavaScript bằng chữ ( { ... }), nó sẽ được tạo dựa trên Object.prototype, có nghĩa là nó được gắn vào đối tượng mới được tạo như một thuộc tính có tên __proto__, vì vậy nếu chúng ta thay đổi nó , như chúng ta đã thực hiện trên đoạn mã trước đó, tất cả các đối tượng JavaScript sẽ được thay đổi, không phải là một cách thực hành tốt. Vì vậy, những gì có thể là thực hành tốt hơn bây giờ:

person.__proto__ = {
    Person: Person,
    getName: getName
};

và bây giờ các đối tượng khác đang trong hòa bình, nhưng dường như nó vẫn không phải là một thực hành tốt. Vì vậy, chúng ta vẫn còn một giải pháp nữa, nhưng để sử dụng giải pháp này, chúng ta nên quay lại dòng mã nơi personđối tượng được tạo ( var person = {};) sau đó thay đổi nó như sau:

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

những gì nó đang tạo ra một JavaScript mới Objectvà đính kèm các propertiesObjectđến __proto__thuộc tính. Vì vậy, để đảm bảo bạn có thể làm:

console.log(person.__proto__===propertiesObject); //true

Nhưng điểm khó khăn ở đây là bạn có quyền truy cập vào tất cả các thuộc tính được xác định ở __proto__cấp độ đầu tiên của personđối tượng (đọc phần tóm tắt để biết thêm chi tiết).


như bạn thấy bằng cách sử dụng bất kỳ cách nào trong hai cách thisnày sẽ chỉ chính xác đếnperson đối tượng.

c. JavaScript có một cách khác để cung cấp hàm this, đó là sử dụng lệnh gọi hoặc áp dụng để gọi hàm.

Phương thức áp dụng () gọi một hàm với giá trị đã cho này và các đối số được cung cấp dưới dạng một mảng (hoặc một đối tượng giống như mảng).

Phương thức call () gọi một hàm với giá trị đã cho này và các đối số được cung cấp riêng lẻ.

theo cách này là sở thích của tôi, chúng ta có thể dễ dàng gọi các chức năng của mình như:

Person.call(person, "George");

hoặc là

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

3 phương thức này là các bước ban đầu quan trọng để tìm ra chức năng .prototype.


2- newTừ khóa hoạt động như thế nào?

Đây là bước thứ hai để hiểu .prototypechức năng. Đây là những gì tôi sử dụng để mô phỏng quá trình:

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

trong phần này tôi sẽ cố gắng thực hiện tất cả các bước mà JavaScript thực hiện, mà không sử dụng newtừ khóa và prototypekhi bạn sử dụng newtừ khóa. Vì vậy, khi chúng ta làm new Person("George"), Personhàm đóng vai trò là hàm tạo, Đây là những gì JavaScript làm, từng cái một:

a. trước hết, nó tạo ra một đối tượng trống, về cơ bản là một hàm băm trống như:

var newObject = {};

b. Bước tiếp theo mà JavaScript thực hiện là đính kèm tất cả các đối tượng nguyên mẫu vào đối tượng mới được tạo

chúng ta có my_person_prototypeở đây tương tự như đối tượng nguyên mẫu.

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

Đây không phải là cách mà JavaScript thực sự gắn các thuộc tính được xác định trong nguyên mẫu. Cách thực tế có liên quan đến khái niệm chuỗi nguyên mẫu.


a. & b. Thay vì hai bước này, bạn có thể có kết quả chính xác bằng cách thực hiện:

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

bây giờ chúng ta có thể gọi getNamehàm trong my_person_prototype:

newObject.getName();

c. sau đó nó đưa đối tượng đó cho hàm tạo,

chúng ta có thể làm điều này với mẫu của chúng tôi như:

Person.call(newObject, "George");

hoặc là

Person.apply(newObject, ["George"]);

sau đó hàm tạo có thể làm bất cứ điều gì nó muốn, bởi vì điều này bên trong nhà xây dựng mà là đối tượng vừa được tạo.

bây giờ là kết quả cuối cùng trước khi mô phỏng các bước khác: Object {name: "George"}


Tóm lược:

Về cơ bản, khi bạn sử dụng từ khóa mới trên một hàm, bạn đang gọi hàm đó và hàm đó đóng vai trò là hàm tạo, vì vậy khi bạn nói:

new FunctionName()

Javascript trong nội bộ làm cho một đối tượng, một băm rỗng và sau đó nó mang lại cho rằng đối tượng để các nhà xây dựng, sau đó các nhà xây dựng có thể làm bất cứ điều gì nó muốn, bởi vì này bên trong constructor đó là đối tượng vừa được tạo ra và sau đó nó mang lại cho bạn rằng đối tượng của quá trình nếu bạn chưa sử dụng câu lệnh return trong hàm của mình hoặc nếu bạn đặt dấu return undefined;ở cuối thân hàm.

Vì vậy, khi JavaScript tìm kiếm một thuộc tính trên một đối tượng, điều đầu tiên nó làm là tìm kiếm nó trên đối tượng đó. Và sau đó, có một thuộc tính bí mật [[prototype]]mà chúng ta thường có nó __proto__và thuộc tính đó là thứ JavaScript nhìn vào tiếp theo. Và khi nhìn qua __proto__, đối với nó lại là một đối tượng JavaScript khác, nó có __proto__thuộc tính riêng của nó, nó đi lên và đi lên cho đến khi đến điểm tiếp theo __proto__là null. Điểm là đối tượng duy nhất trong JavaScript mà __proto__thuộc tính của nó là null là Object.prototypeđối tượng:

console.log(Object.prototype.__proto__===null);//true

và đó là cách kế thừa hoạt động trong JavaScript.

Chuỗi nguyên mẫu

Nói cách khác, khi bạn có một thuộc tính nguyên mẫu trên một hàm và bạn gọi một thuộc tính mới trên đó, sau khi JavaScript kết thúc việc nhìn vào đối tượng mới được tạo cho các thuộc tính, nó sẽ xem xét hàm đó .prototypevà cũng có thể đối tượng này có nguyên mẫu nội bộ. và như thế.


6
a) Vui lòng không giải thích các nguyên mẫu bằng cách sao chép các thuộc tính b) Đặt nội bộ [[nguyên mẫu]] xảy ra trước khi hàm xây dựng được áp dụng trên ví dụ, vui lòng thay đổi thứ tự đó c) jQuery hoàn toàn không chính xác trong câu hỏi này
Bergi

1
@Bergi: cảm ơn vì đã chỉ ra, tôi sẽ được đánh giá cao nếu bạn cho tôi biết nếu bây giờ vẫn ổn.
Mehran Hatami

7
Bạn có thể vui lòng làm cho nó đơn giản? Bạn đúng về tất cả các điểm, nhưng những sinh viên đọc lời giải thích này có thể thực sự bối rối lần đầu tiên. chọn bất kỳ ví dụ đơn giản hơn và để mã tự giải thích hoặc thêm một loạt các bình luận để làm rõ ý của bạn.
PM

2
@PM: Cảm ơn phản hồi của bạn. Tôi đã cố gắng làm cho nó đơn giản nhất có thể nhưng tôi nghĩ bạn đúng nó vẫn còn một số điểm mơ hồ. Vì vậy, tôi sẽ cố gắng sửa đổi nó và cũng có thể mô tả nhiều hơn. :)
Mehran Hatami

1
@AndreaMattioli bởi vì theo cách này, bạn đang tạo một đối tượng hoàn toàn mới và ghi đè lên đối tượng cũ có thể được chia sẻ giữa các đối tượng khác. Bằng cách thay thế, __proto__trước tiên bạn sẽ xóa sạch tất cả các thuộc tính nguyên mẫu cấp cao nhất và sau đó bạn có một cơ sở proto mới không được chia sẻ nữa trừ khi bạn chia sẻ nó.
Mehran Hatami

77

Bảy chiếc nguyên mẫu

Khi Ciro San xuống Núi Lửa Fox sau khi thiền sâu, tâm trí anh minh mẫn và bình yên.

Tuy nhiên, bàn tay anh ta bồn chồn, và tự mình cầm lấy một cây cọ và ghi lại những ghi chú sau.


0) Hai thứ khác nhau có thể được gọi là "nguyên mẫu":

  • tài sản nguyên mẫu, như trong obj.prototype

  • tài sản nội bộ nguyên mẫu, ký hiệu là [[Prototype]] trong ES5 .

    Nó có thể được lấy thông qua ES5 Object.getPrototypeOf().

    Firefox làm cho nó có thể truy cập thông qua __proto__tài sản như một phần mở rộng. ES6 hiện đề cập đến một số yêu cầu tùy chọn cho __proto__.


1) Những khái niệm tồn tại để trả lời câu hỏi:

Khi tôi làm obj.property, JS tìm kiếm ở .propertyđâu?

Theo trực giác, thừa kế cổ điển sẽ ảnh hưởng đến việc tra cứu tài sản.


2)

  • __proto__được sử dụng để .tra cứu thuộc tính dấu chấm như trong obj.property.
  • .prototypekhông được sử dụng để tra cứu trực tiếp, gián tiếp chỉ vì nó quyết định __proto__tại đối tượng sáng tạo với new.

Thứ tự tra cứu là:

  • objthuộc tính được thêm bằng obj.p = ...hoặcObject.defineProperty(obj, ...)
  • tính chất của obj.__proto__
  • tính chất của obj.__proto__.__proto__ , v.v.
  • nếu một số __proto__null, trở lại undefined.

Đây là cái gọi là chuỗi nguyên mẫu .

Bạn có thể tránh .tra cứu với obj.hasOwnProperty('key')Object.getOwnPropertyNames(f)


3) Có hai cách chính để thiết lập obj.__proto__:

  • new:

    var F = function() {}
    var f = new F()

    sau đó newđã thiết lập:

    f.__proto__ === F.prototype

    Đây là nơi .prototypeđược sử dụng.

  • Object.create:

     f = Object.create(proto)

    bộ:

    f.__proto__ === proto

4) Mã:

var F = function(i) { this.i = i }
var f = new F(1)

Tương ứng với sơ đồ sau (một số Numbernội dung bị bỏ qua):

(Function)       (  F  )                                      (f)----->(1)
 |  ^             | | ^                                        |   i    |
 |  |             | | |                                        |        |
 |  |             | | +-------------------------+              |        |
 |  |constructor  | |                           |              |        |
 |  |             | +--------------+            |              |        |
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |[[Prototype]]   |[[Prototype]]   |prototype   |constructor   |[[Prototype]]
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |  |             |                | +----------+              |        |
 |  |             |                | |                         |        |
 |  |             |                | | +-----------------------+        |
 |  |             |                | | |                                |
 v  |             v                v | v                                |
(Function.prototype)              (F.prototype)                         |
 |                                 |                                    |
 |                                 |                                    |
 |[[Prototype]]                    |[[Prototype]]          [[Prototype]]|
 |                                 |                                    |
 |                                 |                                    |
 | +-------------------------------+                                    |
 | |                                                                    |
 v v                                                                    v
(Object.prototype)                                       (Number.prototype)
 | | ^
 | | |
 | | +---------------------------+
 | |                             |
 | +--------------+              |
 |                |              |
 |                |              |
 |[[Prototype]]   |constructor   |prototype
 |                |              |
 |                |              |
 |                | -------------+
 |                | |
 v                v |
(null)           (Object)

Sơ đồ này cho thấy nhiều nút đối tượng được xác định trước bằng ngôn ngữ:

  • null
  • Object
  • Object.prototype
  • Function
  • Function.prototype
  • 1
  • Number.prototype(có thể được tìm thấy với (1).__proto__, bắt buộc dấu ngoặc đơn để đáp ứng cú pháp)

2 dòng mã của chúng tôi chỉ tạo ra các đối tượng mới sau đây:

  • f
  • F
  • F.prototype

ibây giờ là một tài sản của fvì khi bạn làm:

var f = new F(1)

nó đánh giá Fvới thisgiá trị newsẽ trả về, sau đó được gán cho f.


5) .constructor thường xuất phát từ F.prototypeviệc .tra cứu:

f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor

Khi chúng tôi viết f.constructor, JavaScript sẽ .tra cứu như sau:

  • f không có .constructor
  • f.__proto__ === F.prototype.constructor === F, vì vậy hãy lấy nó

Kết quả f.constructor == Flà chính xác theo trực giác, vì Fđược sử dụng để xây dựng f, ví dụ như đặt các trường, giống như trong các ngôn ngữ OOP cổ điển.


6) Cú pháp kế thừa cổ điển có thể đạt được bằng cách thao tác các chuỗi nguyên mẫu.

ES6 thêm classextendscác từ khóa, chủ yếu là đường cú pháp cho sự điên rồ thao túng nguyên mẫu có thể trước đây.

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Sơ đồ đơn giản hóa mà không cần tất cả các đối tượng được xác định trước:

(c)----->(1)
 |   i
 |
 |
 |[[Prototype]]
 |
 |
 v    __proto__
(C)<--------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |[[Prototype]] 
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|[[Prototype]]    (D.prototype)--------> (inc2 function object)
| |                |             inc2
| |                |
| |                |[[Prototype]]
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
|                inc
v
Function.prototype

Chúng ta hãy dành một chút thời gian để nghiên cứu cách thức hoạt động sau đây:

c = new C(1)
c.inc() === 2

Dòng đầu tiên được đặt c.ithành 1như được giải thích trong "4)".

Trên dòng thứ hai, khi chúng tôi làm:

c.inc()
  • .incđược tìm thấy thông qua [[Prototype]]chuỗi: c-> C-> C.prototype->inc
  • khi chúng ta gọi một hàm trong Javascript là X.Y(), JavaScript sẽ tự động đặt thisbằng nhau Xtrong lệnh Y()gọi hàm!

Logic chính xác tương tự cũng giải thích d.incd.inc2.

Bài viết này https://javascript.info/ class#not-just-a-syntax-sugar đề cập đến những ảnh hưởng classđáng giá hơn nữa . Một số trong số chúng có thể không đạt được nếu không có classtừ khóa (kiểm tra TODO):


1
@tomasb cảm ơn! "Tôi không biết bạn đã lấy thứ này ở đâu": sau khi tôi thấy một vài ngôn ngữ động đó, tôi nhận thấy điều quan trọng nhất trong hệ thống lớp của họ là cách .tra cứu hoạt động (và có bao nhiêu bản sao của dữ liệu được tạo ra) . Vì vậy, tôi đặt ra để hiểu điểm đó. Phần còn lại là các bài đăng trên blog của Google + + một thông dịch viên Js. :)
Ciro Santilli 冠状 病毒 审查 六四 事件

1
Tôi vẫn không hiểu lý do tại sao g.constructor === Đối tượng vì bạn nói rằng "4) Khi bạn làm f = new F, mới cũng đặt f.constructor = F". Bạn có thể giải thích cho tôi nhiều hơn? Dù sao đây là câu trả lời tốt nhất mà tôi đang tìm kiếm. Cảm ơn bạn rất nhiều!
nguyenngoc101

@ nguyenngoc101 cảm ơn! Phần sets f.constructor = Fbị sai một cách trắng trợn và mâu thuẫn với các phần tiếp theo: .constructorđược tìm thấy thông qua việc .tra cứu trên chuỗi nguyên mẫu. Đã sửa nó ngay.
Ciro Santilli 冠状 病毒 审查 事件

từ tất cả các cuộc thảo luận về những gì tôi nhận được (xuất phát từ sự kế thừa cổ điển) nếu tôi tạo hàm xây dựng và cố gắng tạo cá thể của nó bằng toán tử mới, tôi sẽ chỉ nhận các phương thức và thuộc tính được gắn vào đối tượng proto, do đó cần phải đính kèm tất cả phương thức và các thuộc tính cho đối tượng proto nếu chúng ta muốn kế thừa, phải không?
blackHawk

1
@CiroSantilli 死 六四 事件 Tôi không nghĩ đó là một lỗi trong Chromium. Tôi nghĩ rằng đó chỉ là một triệu chứng mà fnguyên mẫu chỉ được thiết lập từ Flúc xây dựng; fsẽ không biết hoặc quan tâm F.prototypebất cứ lúc nào sau khi nó được xây dựng lần đầu tiên.
John Glassmyer

76

prototypecho phép bạn thực hiện các lớp học. nếu bạn không sử dụngprototype thì nó trở thành tĩnh.

Đây là một ví dụ ngắn.

var obj = new Object();
obj.test = function() { alert('Hello?'); };

Trong trường hợp trên, bạn có kiểm tra cuộc gọi funcation tĩnh. Hàm này chỉ có thể được truy cập bởi obj.test nơi bạn có thể tưởng tượng obj là một lớp.

như trong đoạn mã dưới đây

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Các obj đã trở thành một lớp mà bây giờ có thể được khởi tạo. Nhiều trường hợp của obj có thể tồn tại và tất cả chúng đều cótest chức năng.

Trên đây là sự hiểu biết của tôi. Tôi đang biến nó thành một wiki cộng đồng, vì vậy mọi người có thể sửa tôi nếu tôi sai.


13
-1: prototypelà thuộc tính của hàm xây dựng, không phải phiên bản, tức là mã của bạn sai! Có lẽ bạn có nghĩa là tài sản phi tiêu chuẩn __proto__của các vật thể, nhưng đó là một con thú hoàn toàn khác ...
Christoph

@Christoph - Cảm ơn bạn đã chỉ ra. Tôi đã cập nhật mã mẫu.
Ramesh

3
Có rất nhiều điều nữa ... Thêm vào đó, JavaScript không phải là ngôn ngữ dựa trên lớp - nó liên quan đến sự kế thừa thông qua các nguyên mẫu, bạn cần đề cập đến sự khác biệt chi tiết hơn!
James

5
Tôi nghĩ rằng câu trả lời này là một chút sai lầm.
Armin Cifuentes

Có thể câu trả lời là 'sai lầm', nhưng giải thích nguyên mẫu được sử dụng cho mục đích gì và bây giờ tất cả đều rõ ràng đối với tôi, sau tất cả những 'câu trả lời' với hàng trăm 'phiếu'. Cảm ơn bạn.
Aleks

66

Sau khi đọc chủ đề này, tôi cảm thấy bối rối với Chuỗi nguyên mẫu JavaScript, sau đó tôi tìm thấy các biểu đồ này

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inherribution Thuộc tính * [[protytype]] * và <code> nguyên mẫu </ code> của các đối tượng hàm

đó là một biểu đồ rõ ràng để hiển thị Kế thừa JavaScript theo Chuỗi nguyên mẫu

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inherribution/

cái này chứa một ví dụ với mã và một vài sơ đồ đẹp.

chuỗi nguyên mẫu cuối cùng rơi trở lại Object.prototype.

chuỗi nguyên mẫu có thể được mở rộng về mặt kỹ thuật miễn là bạn muốn, mỗi lần bằng cách đặt nguyên mẫu của lớp con bằng với một đối tượng của lớp cha.

Hy vọng nó cũng hữu ích cho bạn để hiểu Chuỗi nguyên mẫu JavaScript.


Có thể có nhiều kế thừa trên Javascript không?

Foo là một đối tượng theo nghĩa đen ở đây hay đối tượng chức năng? Nếu nó là một đối tượng theo nghĩa đen, tôi tin rằng Foo.prototype sẽ không quay trở lại Foo thông qua hàm tạo.
Madhur Ahuja

@ user3376708 JavaScript chỉ hỗ trợ kế thừa duy nhất ( nguồn )
Rafael Eyng

@ Nuno_147 Ban đầu không rõ ràng, nhưng nếu bạn nhìn đủ lâu, bạn có thể nhận được một cái gì đó từ nó.
marcelocra

3
Bạn có thể giải thích những gì [[Prototype]]có nghĩa là?
CodyBugstein

40

Mọi đối tượng đều có thuộc tính bên trong, [[Prototype]] , liên kết nó với đối tượng khác:

object [[Prototype]]  anotherObject

Trong javascript truyền thống, đối tượng được liên kết là thuộc prototypetính của hàm:

object [[Prototype]]  aFunction.prototype

Một số môi trường hiển thị [[Prototype]]__proto__:

anObject.__proto__ === anotherObject

Bạn tạo liên kết [[Prototype]] khi tạo đối tượng.

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

Vì vậy, các tuyên bố này là tương đương:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

Bạn thực sự không thể thấy mục tiêu liên kết ( Object.prototype) trong một tuyên bố mới ; thay vào đó, mục tiêu được hàm ý bởi hàm tạo (Object ).

Nhớ lại:

  • Mọi đối tượng đều có một liên kết, [[Prototype]] , đôi khi được hiển thị dưới dạng __proto__ .
  • Mỗi chức năng có một thuộc prototypetính, ban đầu giữ một đối tượng trống.
  • Các đối tượng được tạo bằng mới được liên kết với thuộc prototypetính của hàm tạo của chúng.
  • Nếu một hàm không bao giờ được sử dụng như là một hàm tạo, thì hàm của nó prototype sẽ không được sử dụng.
  • Nếu bạn không cần một nhà xây dựng, hãy sử dụng Object.create thay vì new.

1
Bản sửa đổi 5 đã xóa một số thông tin hữu ích, bao gồm thông tin về Object.create (). Xem bản sửa đổi 4 .
Palec

@Palec tôi nên thêm lại những gì?
sam

2
IMO ít nhất là liên kết đến Object.create()tài liệu , @sam. Liên kết đến __proto__Object.prototypesẽ là cải tiến tốt đẹp. Và tôi thích các ví dụ của bạn về cách các nguyên mẫu làm việc với các nhà xây dựng và Object.create(), nhưng chúng có lẽ là phần dài và ít liên quan hơn mà bạn muốn loại bỏ.
Palec

từ tất cả các cuộc thảo luận về những gì tôi nhận được (xuất phát từ sự kế thừa cổ điển) nếu tôi tạo hàm xây dựng và cố gắng tạo cá thể của nó bằng toán tử mới, tôi sẽ chỉ nhận các phương thức và thuộc tính được gắn vào đối tượng proto, do đó cần phải đính kèm tất cả phương thức và các thuộc tính cho đối tượng proto nếu chúng ta muốn kế thừa, phải không?
blackHawk

29

Javascript không có sự kế thừa theo nghĩa thông thường, nhưng nó có chuỗi nguyên mẫu.

chuỗi nguyên mẫu

Nếu một thành viên của một đối tượng không thể được tìm thấy trong đối tượng, nó sẽ tìm kiếm nó trong chuỗi nguyên mẫu. Chuỗi bao gồm các đối tượng khác. Nguyên mẫu của một thể hiện đã cho có thể được truy cập bằng__proto__ biến. Mỗi đối tượng có một, vì không có sự khác biệt giữa các lớp và thể hiện trong javascript.

Ưu điểm của việc thêm một hàm / biến vào nguyên mẫu là nó phải ở trong bộ nhớ chỉ một lần, không phải cho mọi trường hợp.

Nó cũng hữu ích cho việc thừa kế, bởi vì chuỗi nguyên mẫu có thể bao gồm nhiều đối tượng khác.


1
FF và Chrome hỗ trợ proto , nhưng không phải IE cũng như Opera.
một số

Georg, xin vui lòng làm rõ cho một noob - "không có sự khác biệt giữa các lớp và trường hợp trong javascript." - bạn có thể giải thích? Cái này hoạt động ra sao?
Hamish Grubijan

từ tất cả các cuộc thảo luận về những gì tôi nhận được (xuất phát từ sự kế thừa cổ điển) nếu tôi tạo hàm xây dựng và cố gắng tạo cá thể của nó bằng toán tử mới, tôi sẽ chỉ nhận các phương thức và thuộc tính được gắn vào đối tượng proto, do đó cần phải đính kèm tất cả phương thức và các thuộc tính cho đối tượng proto nếu chúng ta muốn kế thừa, phải không?
blackHawk

28

Bài viết này dài. Nhưng tôi chắc chắn rằng nó sẽ xóa hầu hết các truy vấn của bạn liên quan đến bản chất "nguyên mẫu" của Kế thừa JavaScript. Va thậm chi nhiêu hơn. Xin vui lòng đọc bài viết đầy đủ.

JavaScript về cơ bản có hai loại dữ liệu

  • Không đối tượng
  • Các đối tượng

Không đối tượng

Sau đây là các kiểu dữ liệu không đối tượng

  • chuỗi
  • số (bao gồm NaN và Infinity)
  • giá trị boolean (đúng, sai)
  • chưa xác định

Các kiểu dữ liệu này trả về sau khi bạn sử dụng toán tử typeof

typeof "chuỗi ký tự" (hoặc một biến chứa chuỗi ký tự) === 'chuỗi'

typeof 5 (hoặc bất kỳ chữ số hoặc biến nào chứa chữ số hoặc NaN hoặc Infynity ) === 'number'

typeof true (hoặc false hoặc một biến chứa true hoặc false ) === 'boolean'

typeof không xác định (hoặc một biến không xác định hoặc một biến chứa không xác định ) === 'không xác định'

Các kiểu dữ liệu chuỗi , sốboolean có thể được biểu diễn cả dưới dạng Đối tượngKhông đối tượng . Khi chúng được biểu diễn dưới dạng đối tượng, kiểu của chúng luôn là === 'đối tượng'. Chúng ta sẽ quay trở lại vấn đề này một khi chúng ta hiểu các kiểu dữ liệu đối tượng.

Các đối tượng

Các kiểu dữ liệu đối tượng có thể được chia thành hai loại

  1. Đối tượng kiểu chức năng
  2. Các đối tượng không có chức năng

Các đối tượng kiểu Hàm là những đối tượng trả về chuỗi 'hàm' với toán tử typeof . Tất cả các hàm do người dùng xác định và tất cả các JavaScript được xây dựng trong các đối tượng có thể tạo các đối tượng mới bằng cách sử dụng toán tử mới thuộc danh mục này. Ví dụ.

  • Vật
  • Chuỗi
  • Con số
  • Boolean
  • Mảng
  • Mảng đánh máy
  • RegExp
  • Chức năng
  • Tất cả các đối tượng được xây dựng khác có thể tạo các đối tượng mới bằng cách sử dụng toán tử mới
  • chức năng UserDefinedFunction () {/ * mã do người dùng xác định * /}

Vì vậy, typeof (Object) === typeof (String) === typeof (Number) === typeof (Boolean) === typeof (Array) === typeof (RegExp) === typeof (Function) == = typeof (UserDefinedFunction) === 'chức năng'

Tất cả các đối tượng loại Hàm thực sự là các phiên bản của đối tượng JavaScript được tích hợp Hàm (bao gồm cả đối tượng Hàm nghĩa là nó được định nghĩa đệ quy). Như thể các đối tượng này đã được xác định theo cách sau

var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code  for object Function])
var UserDefinedFunction= new Function ("user defined code")

Như đã đề cập, các đối tượng loại Hàm có thể tạo thêm các đối tượng mới bằng cách sử dụng toán tử mới . Ví dụ: một đối tượng thuộc loại Object , String , Number , Boolean , Array , RegExp Hoặc UserDefinedFunction có thể được tạo bằng cách sử dụng

var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[]  //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction() 

Do đó, các đối tượng được tạo là tất cả các đối tượng loại Non Function và trả về typeof === 'object' của chúng . Trong tất cả các trường hợp này, đối tượng "a" không thể tạo thêm đối tượng bằng toán tử mới. Vì vậy, sau đây là sai

var b=new a() //error. a is not typeof==='function'

Toán học đối tượng tích hợp là typeof === 'object' . Do đó, một đối tượng mới của kiểu Math không thể được tạo bởi toán tử mới.

var b=new Math() //error. Math is not typeof==='function'

Cũng lưu ý rằng Object , ArrayRegExp có thể tạo một đối tượng mới mà không cần sử dụng toán tử mới . Tuy nhiên, những người không theo dõi.

var a=String() // Create a new Non Object string. returns a typeof==='string' 
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'

Các chức năng người dùng xác định là trường hợp đặc biệt.

var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.

Kể từ khi đối tượng kiểu Hàm có thể tạo các đối tượng mới, chúng còn được gọi là Con constructor .

Mỗi hàm tạo / hàm (dù được tích hợp hoặc do người dùng xác định) khi được xác định tự động có một thuộc tính được gọi là "nguyên mẫu" có giá trị theo mặc định được đặt làm đối tượng. Bản thân đối tượng này có một thuộc tính được gọi là "constructor" , theo mặc định các tham chiếu trở lại Trình xây dựng / Hàm .

Ví dụ khi chúng ta định nghĩa một hàm

function UserDefinedFunction()
{
}

sau đây tự động xảy ra

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

Đây "nguyên mẫu" bất động sản chỉ xuất hiện trong các đối tượng kiểu Function (và không bao giờ trong Đối tượng không loại Function ).

Điều này là do khi một đối tượng mới được tạo (sử dụng toán tử mới), nó kế thừa tất cả các thuộc tính và phương thức từ đối tượng nguyên mẫu hiện tại của hàm Xây dựng, tức là tham chiếu bên trong được tạo trong đối tượng mới được tạo tham chiếu đến đối tượng được tham chiếu bởi đối tượng nguyên mẫu hiện tại của hàm Constructor.

Đây "tài liệu tham khảo nội bộ" mà được tạo ra trong đối tượng để tham khảo tài sản thừa kế được gọi là nguyên mẫu của đối tượng (có sự tham khảo các đối tượng được tham chiếu bởi Constructor của "nguyên mẫu" bất động sản nhưng khác với nó). Đối với bất kỳ đối tượng nào (Hàm hoặc Hàm không), điều này có thể được truy xuất bằng phương thức Object.getPrototypeOf () . Sử dụng phương pháp này người ta có thể theo dõi chuỗi nguyên mẫu của một đối tượng.

Ngoài ra, mọi đối tượng được tạo ( Loại chức năng hoặc Loại không chức năng ) đều có thuộc tính "hàm tạo" được kế thừa từ đối tượng được tham chiếu bởi thuộc tính nguyên mẫu của hàm Xây dựng. Theo mặc định, thuộc tính "constructor" này tham chiếu hàm Con constructor đã tạo ra nó (nếu "nguyên mẫu" mặc định của Hàm xây dựng không bị thay đổi).

Đối với tất cả các đối tượng Kiểu hàm, hàm xây dựng luôn là hàm Function () {}

Đối với các đối tượng loại Không có chức năng (ví dụ: đối tượng Toán học tích hợp Javascript), hàm tạo là hàm tạo ra nó. Đối với đối tượng Math, nó là hàm Object () {} .

Tất cả các khái niệm được giải thích ở trên có thể hơi khó hiểu để hiểu mà không cần bất kỳ mã hỗ trợ. Vui lòng đi qua các dòng mã sau đây để hiểu khái niệm. Hãy cố gắng thực hiện nó để có một sự hiểu biết tốt hơn.

function UserDefinedFunction()
{ 

} 

/* creating the above function automatically does the following as mentioned earlier

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

*/


var newObj_1=new UserDefinedFunction()

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays true

alert(newObj_1.constructor) //Displays function UserDefinedFunction

//Create a new property in UserDefinedFunction.prototype object

UserDefinedFunction.prototype.TestProperty="test"

alert(newObj_1.TestProperty) //Displays "test"

alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"

//Create a new Object

var objA = {
        property1 : "Property1",
        constructor:Array

}


//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed

//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction

alert(newObj_1.TestProperty) //This shall still Display "test" 

alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"


//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();

alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.

alert(newObj_2.constructor) //Displays function Array()

alert(newObj_2.property1) //Displays "Property1"

alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"

//Create a new property in objA
objA.property2="property2"

alert(objA.property2) //Displays "Property2"

alert(UserDefinedFunction.prototype.property2) //Displays "Property2"

alert(newObj_2.property2) // Displays Property2

alert(Object.getPrototypeOf(newObj_2).property2) //Displays  "Property2"

Chuỗi nguyên mẫu của mọi đối tượng cuối cùng có dấu vết trở lại Object.prototype (bản thân nó không có bất kỳ đối tượng nguyên mẫu nào). Mã sau có thể được sử dụng để truy tìm chuỗi nguyên mẫu của một đối tượng

var o=Starting object;

do {
    alert(o + "\n" + Object.getOwnPropertyNames(o))

}while(o=Object.getPrototypeOf(o))

Chuỗi nguyên mẫu cho các đối tượng khác nhau hoạt động như sau.

  • Mọi đối tượng Hàm (bao gồm cả đối tượng Hàm được xây dựng) -> Function.prototype -> Object.prototype -> null
  • Các đối tượng đơn giản (được tạo bởi Object mới () hoặc {} bao gồm cả đối tượng Math được tích hợp) -> Object.prototype -> null
  • Đối tượng được tạo bằng new hoặc Object.create -> Một hoặc nhiều chuỗi nguyên mẫu -> Object.prototype -> null

Để tạo một đối tượng mà không có bất kỳ nguyên mẫu nào, hãy sử dụng như sau:

var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null

Mọi người có thể nghĩ rằng việc đặt thuộc tính nguyên mẫu của Trình xây dựng thành null sẽ tạo ra một đối tượng có nguyên mẫu null. Tuy nhiên, trong những trường hợp như vậy, nguyên mẫu của đối tượng mới được tạo được đặt thành Object.prototype và hàm tạo của nó được đặt thành hàm Object. Điều này được thể hiện bằng đoạn mã sau

function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)

var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype)   //Displays true
alert(o.constructor)    //Displays Function Object

Sau đây trong phần tóm tắt của bài viết này

  • Có hai loại đối tượng Kiểu chức năngLoại Không chức năng
  • Chỉ các đối tượng loại Hàm có thể tạo một đối tượng mới bằng toán tử mới . Do đó, các đối tượng được tạo là các đối tượng loại Non Function . Các đối tượng loại Non Function không thể tạo thêm một đối tượng bằng toán tử new .

  • Tất cả các đối tượng loại Hàm theo mặc định có thuộc tính "nguyên mẫu" . Đây "nguyên mẫu" tài liệu tham khảo hữu một đối tượng mà có một "nhà xây dựng" tài sản đó bằng cách tham khảo mặc định Chức năng loại đối tượng riêng của mình.

  • Tất cả các đối tượng ( kiểu Functionkhông loại Function ) có một "nhà xây dựng" tài sản đó bằng cách tham khảo mặc định loại Chức năng đối tượng / Constructor đã tạo ra nó.

  • Mỗi đối tượng được tạo bên trong đều tham chiếu đối tượng được tham chiếu bởi thuộc tính "nguyên mẫu" của Trình xây dựng đã tạo ra nó. Đối tượng này được gọi là nguyên mẫu của đối tượng được tạo (khác với thuộc tính "nguyên mẫu" của đối tượng Kiểu mà nó tham chiếu). Bằng cách này, đối tượng được tạo có thể truy cập trực tiếp vào các phương thức và thuộc tính được xác định trong đối tượng được tham chiếu bởi thuộc tính "nguyên mẫu" của Trình xây dựng (tại thời điểm tạo đối tượng).

  • Nguyên mẫu của một đối tượng (và do đó tên thuộc tính được kế thừa của nó) có thể được truy xuất bằng phương thức Object.getPrototypeOf () . Trong thực tế, phương pháp này có thể được sử dụng để điều hướng toàn bộ chuỗi nguyên mẫu của đối tượng.

  • Chuỗi nguyên mẫu của mọi đối tượng cuối cùng có dấu vết trở lại Object.prototype (Trừ khi đối tượng được tạo bằng Object.create (null) trong trường hợp đối tượng không có nguyên mẫu).

  • typeof (new Array ()) === 'object' là do thiết kế ngôn ngữ và không phải là một lỗi như được chỉ ra bởi Douglas Crockford

  • Đặt thuộc tính nguyên mẫu của Trình xây dựng thành null (hoặc không xác định, số, đúng, sai, chuỗi) sẽ không tạo đối tượng có nguyên mẫu null. Trong các trường hợp như vậy, nguyên mẫu của đối tượng mới được tạo được đặt thành Object.prototype và hàm tạo của nó được đặt thành hàm Object.

Hi vọng điêu nay co ich.


24

Khái niệm prototypalthừa kế là một trong những phức tạp nhất đối với nhiều nhà phát triển. Hãy cố gắng hiểu gốc rễ của vấn đề để hiểu rõ prototypal inheritancehơn. Hãy bắt đầu với một plainchức năng.

nhập mô tả hình ảnh ở đây

Nếu chúng ta sử dụng một newtoán tử trên Tree function, chúng ta gọi nó là một constructorhàm.

nhập mô tả hình ảnh ở đây

Mỗi JavaScriptchức năng có prototype. Khi bạn đăng nhập Tree.prototype, bạn nhận được ...

nhập mô tả hình ảnh ở đây

Nếu bạn nhìn vào console.log()đầu ra ở trên , bạn có thể thấy một thuộc tính hàm tạo Tree.prototypevà một thuộc __proto__tính cũng vậy. Điều này __proto__thể hiện prototyperằng điều này functionđược dựa trên và vì đây chỉ là một đơn vị JavaScript functionchưa inheritanceđược thiết lập, nên nó đề cập đến Object prototypecái gì đó được tích hợp sẵn trong JavaScript ...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

Cái này có những thứ như .toString, .toValue, .hasOwnPropertyv.v ...

__proto__mà đã mang mozilla của tôi bị phản đối và được thay thế bằng Object.getPrototypeOfphương pháp để có được object's prototype.

nhập mô tả hình ảnh ở đây

Object.getPrototypeOf(Tree.prototype); // Object {} 

Hãy thêm một phương thức cho chúng tôi Tree prototype.

nhập mô tả hình ảnh ở đây

Chúng tôi đã sửa đổi Rootvà thêm một functionchi nhánh cho nó.

nhập mô tả hình ảnh ở đây

Điều đó có nghĩa khi bạn tạo một instancesố Tree, bạn có thể gọi nó là của branchphương pháp.

nhập mô tả hình ảnh ở đây

Chúng tôi cũng có thể thêm primitiveshoặc objectsđể của chúng tôi Prototype.

nhập mô tả hình ảnh ở đây

Hãy thêm một child-treevào của chúng tôi Tree.

nhập mô tả hình ảnh ở đây

Ở đây, Childkế thừa prototypetừ Tree, những gì chúng ta đang làm ở đây là sử dụng Object.create()phương thức để tạo một đối tượng mới dựa trên những gì bạn vượt qua, đây là Tree.prototype. Trong trường hợp này, những gì chúng tôi đang làm là đặt nguyên mẫu của Child thành một đối tượng mới trông giống hệt với Treenguyên mẫu. Tiếp theo, chúng tôi sẽ thiết lập Child's constructor to Child, nếu chúng tôi không chỉ ra Tree().

nhập mô tả hình ảnh ở đây

Childbây giờ có cái riêng của nó prototype, __proto__điểm của nó TreeTree's prototypeđiểm đến cơ sở Object.

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

Bây giờ bạn tạo một instancesố Childvà gọi branchđó là ban đầu có sẵn trong Tree. Chúng tôi đã không thực sự xác định của chúng tôi branchtrên Child prototype. NHƯNG, trong Root prototypeđó Trẻ em được thừa hưởng từ.

nhập mô tả hình ảnh ở đây

Trong JS mọi thứ không phải là một đối tượng, mọi thứ đều có thể hoạt động như một đối tượng.

Javascriptcó những người nguyên thủy như strings, number, booleans, undefined, null.Họ không object(i.e reference types), nhưng chắc chắn có thể hành động như một object. Hãy xem xét một ví dụ ở đây.

nhập mô tả hình ảnh ở đây

Trong dòng đầu tiên của danh sách này, một primitivegiá trị chuỗi được gán cho tên. Dòng thứ hai xử lý tên như một objectvà các cuộc gọi charAt(0)bằng cách sử dụng ký hiệu chấm.

Đây là những gì xảy ra đằng sau hậu trường: // những gì JavaScriptđộng cơ làm

nhập mô tả hình ảnh ở đây

Chỉ String objecttồn tại cho một câu lệnh trước khi nó bị hủy (một quá trình được gọi là autoboxing). Chúng ta hãy trở lại với chúng ta prototypal inheritance.

  • Javascripthỗ trợ kế thừa delegationdựa trên prototypes.
  • Mỗi người Functioncó một prototypetài sản, trong đó đề cập đến một đối tượng khác.
  • properties/functionsđược nhìn từ objectchính nó hoặc thông qua prototypechuỗi nếu nó không tồn tại

A prototypetrong JS là một đối tượng mà yieldsbạn với cha mẹ của người khác object. [tức là .. ủy quyền] Delegation có nghĩa là nếu bạn không thể làm điều gì đó, bạn sẽ bảo người khác làm việc đó cho bạn.

nhập mô tả hình ảnh ở đây

https://jsfiddle.net/say0tzpL/1/

Nếu bạn tra cứu câu đố trên, con chó có quyền truy cập vào toStringphương thức, nhưng nó không có sẵn trong nó, nhưng có sẵn thông qua chuỗi nguyên mẫu mà đại biểu choObject.prototype

nhập mô tả hình ảnh ở đây

Nếu bạn nhìn vào cái bên dưới, chúng tôi đang cố gắng truy cập callphương thức có sẵn trong mỗi phương thức function.

nhập mô tả hình ảnh ở đây

https://jsfiddle.net/rknffckc/

Nếu bạn tra cứu câu đố trên, ProfileHàm có quyền truy cập vào callphương thức, nhưng hàm này không có sẵn trong đó, nhưng có sẵn thông qua chuỗi nguyên mẫu được ủy quyềnFunction.prototype

nhập mô tả hình ảnh ở đây

Lưu ý: prototype là một thuộc tính của hàm tạo, trong khi đó __proto__là thuộc tính của các đối tượng được xây dựng từ hàm tạo. Mỗi hàm đi kèm với một thuộc prototypetính có giá trị là rỗng object. Khi chúng ta tạo một thể hiện của hàm, chúng ta sẽ có một thuộc tính bên trong [[Prototype]]hoặc __proto__có tham chiếu là nguyên mẫu của Hàm constructor.

nhập mô tả hình ảnh ở đây

Sơ đồ trên có vẻ hơi phức tạp, nhưng đưa ra toàn bộ bức tranh về cách prototype chaining hoạt động. Chúng ta hãy đi qua điều này từ từ:

Có hai thể hiện b1b2, có hàm tạo Barvà cha mẹ là Foo và có hai phương thức từ chuỗi nguyên mẫu identifyspeakthông qua BarFoo

nhập mô tả hình ảnh ở đây

https://jsfiddle.net/kbp7jr7n/

Nếu bạn tra mã ở trên, chúng ta có hàm Footạo có phương thức identify()và hàm Bartạo có speakphương thức. Chúng tôi tạo hai Barthể hiện b1b2có kiểu cha mẹ là Foo. Bây giờ trong khi gọi speakphương thức Bar, chúng tôi có thể xác định người đang gọi nói qua prototypechuỗi.

nhập mô tả hình ảnh ở đây

Barbây giờ có tất cả các phương thức Foođược định nghĩa trong nó prototype. Hãy đào thêm trong việc tìm hiểu Object.prototypeFunction.prototypevà làm thế nào họ có liên quan. Nếu bạn tìm các nhà xây dựng của Foo, BarObjectFunction constructor.

nhập mô tả hình ảnh ở đây

Các prototypecủa BarFoo, prototypecủa FooObjectvà nếu bạn nhìn kỹ các prototypesố Foocó liên quan đến Object.prototype.

nhập mô tả hình ảnh ở đây

Trước khi chúng ta đóng nó xuống, chúng ta chỉ cần bọc một đoạn mã nhỏ ở đây để tóm tắt mọi thứ ở trên . Chúng tôi đang sử dụng instanceoftoán tử ở đây để kiểm tra xem một objectthuộc tính trong prototypechuỗi của nó có thuộc prototypetính constructordưới đây tóm tắt toàn bộ sơ đồ lớn hay không.

nhập mô tả hình ảnh ở đây

Tôi hy vọng phần bổ sung này là một số thông tin, tôi biết loại này có thể rất lớn để nắm bắt ... nói một cách đơn giản, đó chỉ là các đối tượng được liên kết với các đối tượng !!!!


22

mục đích chính xác của tài sản ".prototype" này là gì?

Giao diện cho các lớp tiêu chuẩn trở nên mở rộng. Ví dụ: bạn đang sử dụngArray lớp và bạn cũng cần thêm một bộ nối tiếp tùy chỉnh cho tất cả các đối tượng mảng của bạn. Bạn có dành thời gian để mã hóa một lớp con hoặc sử dụng thành phần hoặc ... Thuộc tính nguyên mẫu giải quyết điều này bằng cách cho phép người dùng kiểm soát chính xác các thành viên / phương thức có sẵn cho một lớp.

Hãy nghĩ về các nguyên mẫu như một con trỏ vtable thêm. Khi một số thành viên bị thiếu trong lớp ban đầu, nguyên mẫu được tra cứu trong thời gian chạy.


21

Nó có thể giúp phân loại chuỗi nguyên mẫu thành hai loại.

Hãy xem xét các nhà xây dựng:

 function Person() {}

Giá trị của Object.getPrototypeOf(Person)là một hàm. Trong thực tế, nó là Function.prototype. Vì Personđược tạo như một hàm, nó chia sẻ cùng một đối tượng hàm nguyên mẫu mà tất cả các hàm có. Nó giống như Person.__proto__, nhưng tài sản đó không nên được sử dụng. Dù sao, với Object.getPrototypeOf(Person)bạn có hiệu quả đi lên các bậc thang của cái được gọi là chuỗi nguyên mẫu.

Chuỗi theo hướng đi lên trông như thế này:

    PersonFunction.prototypeObject.prototype(điểm cuối)

Quan trọng là chuỗi nguyên mẫu này ít liên quan đến các đối tượng Personcó thể xây dựng . Những vật thể được xây dựng này có chuỗi nguyên mẫu riêng và chuỗi này có khả năng không có tổ tiên gần giống với chuỗi được đề cập ở trên.

Lấy ví dụ về đối tượng này:

var p = new Person();

p không có mối quan hệ chuỗi nguyên mẫu trực tiếp với Person . Mối quan hệ của họ là một khác nhau. Đối tượng p có chuỗi nguyên mẫu riêng. Sử dụng Object.getPrototypeOf, bạn sẽ tìm thấy chuỗi như sau:

    pPerson.prototypeObject.prototype(điểm cuối)

Không có đối tượng chức năng trong chuỗi này (mặc dù điều đó có thể).

Vì vậy, Persondường như liên quan đến hai loại chuỗi, sống cuộc sống của chính họ. Để "nhảy" từ chuỗi này sang chuỗi khác, bạn sử dụng:

  1. .prototype: nhảy từ chuỗi của hàm tạo sang chuỗi của đối tượng được tạo. Do đó, thuộc tính này chỉ được xác định cho các đối tượng hàm (như newchỉ có thể được sử dụng trên các hàm).

  2. .constructor: nhảy từ chuỗi của đối tượng được tạo sang chuỗi của hàm tạo.

Dưới đây là một bản trình bày trực quan của hai chuỗi nguyên mẫu có liên quan, được biểu diễn dưới dạng cột:

nhập mô tả hình ảnh ở đây

Để tóm tắt:

Các prototypebất động sản không đưa ra thông tin của của đối tượng chuỗi nguyên mẫu, nhưng các đối tượng được tạo ra bởi đề tài này.

Không có gì ngạc nhiên khi tên của tài sản prototypecó thể dẫn đến nhầm lẫn. Nó có thể đã rõ ràng hơn nếu tài sản này đã được đặt tên prototypeOfConstructedInstanceshoặc một cái gì đó dọc theo dòng đó.

Bạn có thể nhảy qua lại giữa hai chuỗi nguyên mẫu:

Person.prototype.constructor === Person

Sự đối xứng này có thể bị phá vỡ bằng cách gán rõ ràng một đối tượng khác cho prototype tính (sẽ nói thêm về điều đó sau).

Tạo một hàm, lấy hai đối tượng

Person.prototypelà một đối tượng được tạo cùng lúc chức năng Personđược tạo. Nó có Personvai trò là constructor, mặc dù constructor đó chưa thực sự thực thi. Vì vậy, hai đối tượng được tạo ra cùng một lúc:

  1. Các chức năng Personchính nó
  2. Đối tượng sẽ đóng vai trò là nguyên mẫu khi hàm được gọi là hàm tạo

Cả hai đều là các đối tượng, nhưng chúng có các vai trò khác nhau: đối tượng hàm xây dựng , trong khi đối tượng khác đại diện cho nguyên mẫu của bất kỳ đối tượng nào mà hàm sẽ xây dựng. Đối tượng nguyên mẫu sẽ trở thành cha mẹ của đối tượng được xây dựng trong chuỗi nguyên mẫu của nó.

Vì một hàm cũng là một đối tượng, nó cũng có cha mẹ riêng trong chuỗi nguyên mẫu của riêng nó, nhưng nhớ lại rằng hai chuỗi này là về những thứ khác nhau.

Dưới đây là một số điểm tương đồng có thể giúp nắm bắt vấn đề - tất cả các bản in này true:

function Person() {};

// This is prototype chain info for the constructor (the function object):
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// Step further up in the same hierarchy:
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
console.log(Person.__proto__ === Function.prototype);
// Here we swap lanes, and look at the constructor of the constructor
console.log(Person.constructor === Function);
console.log(Person instanceof Function);

// Person.prototype was created by Person (at the time of its creation)
// Here we swap lanes back and forth:
console.log(Person.prototype.constructor === Person);
// Although it is not an instance of it:
console.log(!(Person.prototype instanceof Person));
// Instances are objects created by the constructor:
var p = new Person();
// Similarly to what was shown for the constructor, here we have
// the same for the object created by the constructor:
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(p.__proto__ === Person.prototype);
// Here we swap lanes, and look at the constructor
console.log(p.constructor === Person);
console.log(p instanceof Person);

Thêm cấp độ cho chuỗi nguyên mẫu

Mặc dù một đối tượng nguyên mẫu được tạo khi bạn tạo hàm xây dựng, bạn có thể bỏ qua đối tượng đó và gán một đối tượng khác sẽ được sử dụng làm nguyên mẫu cho bất kỳ trường hợp tiếp theo nào được tạo bởi hàm tạo đó.

Ví dụ:

function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();

Bây giờ chuỗi nguyên mẫu của t dài hơn một bước so với p :

    tpPerson.prototypeObject.prototype(điểm cuối)

Chuỗi nguyên mẫu khác không dài hơn: ThiefPersonlà anh chị em có cùng cha mẹ trong chuỗi nguyên mẫu của họ:

    Person}
    Thief  } → Function.prototypeObject.prototype(điểm cuối)

Đồ họa được trình bày trước đó sau đó có thể được mở rộng cho điều này (bản gốc Thief.prototypebị bỏ qua):

nhập mô tả hình ảnh ở đây

Các dòng màu xanh đại diện cho chuỗi nguyên mẫu, các dòng màu khác đại diện cho các mối quan hệ khác:

  • giữa một đối tượng và hàm tạo của nó
  • giữa một hàm tạo và đối tượng nguyên mẫu sẽ được sử dụng để xây dựng các đối tượng


16

Tôi thấy thật hữu ích khi giải thích "chuỗi nguyên mẫu" là quy ước đệ quy khi obj_n.prop_Xđược tham chiếu:

nếu obj_n.prop_Xkhông tồn tại, hãy kiểm traobj_n+1.prop_X nơiobj_n+1 = obj_n.[[prototype]]

Nếu prop_Xcuối cùng được tìm thấy trong đối tượng nguyên mẫu thứ k thì

obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X

Bạn có thể tìm thấy một biểu đồ về mối quan hệ của các đối tượng Javascript theo các thuộc tính của chúng ở đây:

đồ thị đối tượng js

http://jsobjects.org


14

Khi một hàm tạo tạo một đối tượng, đối tượng đó sẽ tham chiếu ngầm định thuộc tính Nguyên mẫu của lớp xây dựng với mục đích giải quyết các tham chiếu thuộc tính. Thuộc tính của mẫu của nhà xây dựng có thể được tham chiếu bởi hàm constructor.prototype của chương trình và các thuộc tính được thêm vào nguyên mẫu của đối tượng được chia sẻ, thông qua kế thừa, bởi tất cả các đối tượng chia sẻ nguyên mẫu.


11

Có hai thực thể riêng biệt nhưng có liên quan ở đây cần giải thích:

  • Các .prototypetài sản của chức năng.
  • Thuộc tính [[Prototype]][1] của tất cả các đối tượng [2] .

Đây là hai điều khác nhau.

Tài [[Prototype]]sản:

Đây là một thuộc tính tồn tại trên tất cả [2] đối tượng.

Những gì được lưu trữ ở đây là một đối tượng khác, với tư cách là một đối tượng, có một đối tượng riêng [[Prototype]]của nó trỏ đến một đối tượng khác. Đó là đối tượng khác có một [[Prototype]]của riêng mình. Câu chuyện này tiếp tục cho đến khi bạn tiếp cận đối tượng nguyên mẫu cung cấp các phương thức có thể truy cập được trên tất cả các đối tượng (như .toString).

Tài [[Prototype]]sản là một phần của những gì hình thành [[Prototype]]chuỗi. Chuỗi [[Prototype]]đối tượng này là những gì được kiểm tra khi, ví dụ, [[Get]]hoặc các [[Set]]hoạt động được thực hiện trên một đối tượng:

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.b = 20    // [[Set]] consults prototype chain

Tài .prototypesản:

Đây là một tài sản chỉ được tìm thấy trên các chức năng. Sử dụng một chức năng rất đơn giản:

function Bar(){};

Tài .prototypesản giữ một đối tượng sẽ được chỉ định b.[[Prototype]]khi bạn làm var b = new Bar. Bạn có thể dễ dàng kiểm tra điều này:

// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true

Một trong những quan trọng .prototypes là của Objectchức năng . Nguyên mẫu này giữ đối tượng nguyên mẫu mà tất cả [[Prototype]]các chuỗi chứa. Trên đó, tất cả các phương thức có sẵn cho các đối tượng mới được xác định:

// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))

Bây giờ, vì .prototypelà một đối tượng, nó có một [[Prototype]]tài sản. Khi bạn không thực hiện bất kỳ công việc để Function.prototype, các .prototype's [[Prototype]]điểm đến đối tượng nguyên mẫu (Object.prototype ). Điều này được tự động thực hiện bất cứ khi nào bạn tạo một chức năng mới.

Bằng cách này, bất cứ khi nào bạn thực hiện new Bar;chuỗi nguyên mẫu được thiết lập cho bạn, bạn sẽ có được mọi thứ được xác định Bar.prototypevà mọi thứ được xác định trên Object.prototype:

var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)

Khi bạn thực hiện các bài tập cho Function.prototypetất cả những gì bạn đang làm là mở rộng chuỗi nguyên mẫu để bao gồm một đối tượng khác. Nó giống như một chèn trong một danh sách liên kết đơn.

Điều này về cơ bản làm thay đổi [[Prototype]]chuỗi cho phép các thuộc tính được xác định trên đối tượng được gán cho Function.prototypebất kỳ đối tượng nào được tạo bởi hàm.


[1: Điều đó sẽ không gây nhầm lẫn cho bất cứ ai; tạo sẵn thông qua các __proto__tài sản trong việc triển khai nhiều.
[2]: Tất cả ngoại trừ null.


10

Hãy để tôi nói cho bạn hiểu sự hiểu biết của tôi về nguyên mẫu. Tôi sẽ không so sánh sự kế thừa ở đây với các ngôn ngữ khác. Tôi ước mọi người sẽ ngừng so sánh các ngôn ngữ, và chỉ cần hiểu ngôn ngữ như chính nó. Hiểu nguyên mẫu và kế thừa nguyên mẫu rất đơn giản, như tôi sẽ chỉ cho bạn dưới đây.

Nguyên mẫu giống như một mô hình, dựa trên đó bạn tạo ra một sản phẩm. Điểm cốt yếu cần hiểu là khi bạn tạo một đối tượng bằng cách sử dụng một đối tượng khác làm nguyên mẫu, liên kết giữa nguyên mẫu và sản phẩm sẽ không ngừng tồn tại. Ví dụ:

var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

Mỗi đối tượng chứa một thuộc tính bên trong được gọi là [[nguyên mẫu]], có thể được truy cập bởi Object.getPrototypeOf()hàm. Object.create(model)tạo một đối tượng mới và đặt thuộc tính [[nguyên mẫu]] cho mô hình đối tượng . Do đó khi bạn làm Object.getPrototypeOf(product), bạn sẽ có được mô hình đối tượng .

Các thuộc tính trong sản phẩm được xử lý theo cách sau:

  • Khi một thuộc tính được truy cập để chỉ đọc giá trị của nó, nó sẽ tra cứu trong chuỗi phạm vi. Việc tìm kiếm biến bắt đầu từ sản phẩm trở lên đến nguyên mẫu của nó. Nếu một biến như vậy được tìm thấy trong tìm kiếm, tìm kiếm được dừng ngay tại đó và giá trị được trả về. Nếu một biến như vậy không thể được tìm thấy trong chuỗi phạm vi, không xác định được trả về.
  • Khi một thuộc tính được viết (thay đổi), thì thuộc tính đó luôn được ghi trên đối tượng sản phẩm . Nếu sản phẩm chưa có thuộc tính như vậy, nó được tạo và viết hoàn toàn.

Liên kết các đối tượng sử dụng thuộc tính nguyên mẫu được gọi là kế thừa nguyên mẫu. Ở đó, nó đơn giản như vậy, đồng ý?


Không phải lúc nào cũng viết trên sản phẩm trên bài tập. Bạn không nói rõ rằng các thành viên cụ thể phải được khởi tạo và các thành viên được chia sẻ có thể đi trên nguyên mẫu. Đặc biệt là khi bạn có các thành viên có thể thay đổi cụ thể: stackoverflow.com/questions/16063394/NH
HMR

HMR: Trong ví dụ của bạn trong câu trả lời của bạn, ben.food.push ("Hamburger"); dòng làm thay đổi thuộc tính của đối tượng nguyên mẫu do các yếu tố sau: 1.) Đầu tiên ben.food được tra cứu và mọi hành động tra cứu sẽ chỉ đơn giản là tra cứu chuỗi phạm vi. 2.) Hàm đẩy của đối tượng ben.food đó được thực thi. Bằng cách viết chế độ trong câu trả lời của tôi, ý tôi là khi bạn đặt giá trị cho nó một cách rõ ràng, như trong: ben.food = ['Idly']; Điều này sẽ luôn tạo một thuộc tính mới (nếu chưa có) trên đối tượng sản phẩm và sau đó gán giá trị cho nó.
Aravind

HMR: Cảm ơn bình luận của bạn, nó làm tôi suy nghĩ và kiểm tra sự hiểu biết của tôi.
Aravind

Khi gán lại ben.food, nó sẽ phủ bóng thành viên thực phẩm trừ khi thực phẩm được tạo bằng Object.defineProperty, Object.defineProperies hoặc Object.create với đối số thứ hai (không phải lúc nào cũng vậy). Bạn thậm chí có thể thay đổi nguyên mẫu với (giao diện giống như) một phép gán lại khi bạn tạo trình thiết lập getter. Khi nói đến các kiểu thừa kế tôi hiểu hàm xây dựng là khó hiểu và có một số vấn đề lớn nhưng thật tốt nếu bạn hiểu nó. Kế thừa trong JavaScript không bắt đầu và kết thúc bằng việc thiết lập một nguyên mẫu, khởi tạo (các hàm tạo) cũng sẽ được sử dụng lại.
HMR

Câu trả lời của bạn rất tốt trong việc giải thích nguyên mẫu nhưng có thể bị hiểu sai bằng cách đơn giản hóa việc thừa kế trong JavaScript và các thành viên cụ thể. Rất nhiều câu hỏi đã được hỏi tại sao việc đột biến một thành viên nguyên mẫu trên một cá thể lại ảnh hưởng đến các thể hiện khác.
HMR


10

Hãy xem xét các keyValueStoređối tượng sau :

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
        this.get = function(key) { return this.data[key]; };
        this.set = function(key, value) { this.data[key] = value; };
        this.delete = function(key) { delete this.data[key]; };
        this.getLength = function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Tôi có thể tạo một thể hiện mới của đối tượng này bằng cách thực hiện điều này:

kvs = keyValueStore.create();

Mỗi phiên bản của đối tượng này sẽ có các thuộc tính công khai sau:

  • data
  • get
  • set
  • delete
  • getLength

Bây giờ, giả sử chúng ta tạo ra 100 thể hiện của keyValueStoređối tượng này . Mặc dù get, set, delete, getLengthsẽ làm điều tương tự cho mỗi 100 trường hợp, mỗi trường hợp có bản sao riêng của mình chức năng này.

Bây giờ, hãy tưởng tượng nếu bạn có thể có chỉ là một single get, set, deletegetLengthsao chép, và mỗi trường hợp sẽ tham khảo mà chức năng tương tự. Điều này sẽ tốt hơn cho hiệu suất và cần ít bộ nhớ hơn.

Đó là nơi các nguyên mẫu xuất hiện. Một nguyên mẫu là một "bản thiết kế" của các thuộc tính được kế thừa nhưng không được sao chép bởi các thể hiện. Vì vậy, điều này có nghĩa là nó chỉ tồn tại một lần trong bộ nhớ cho tất cả các phiên bản của một đối tượng và được chia sẻ bởi tất cả các phiên bản đó.

Bây giờ, hãy xem xét các keyValueStoređối tượng một lần nữa. Tôi có thể viết lại như thế này:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
    };

    kvs.prototype = {
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  {
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Điều này thực sự giống với phiên bản trước của keyValueStoređối tượng, ngoại trừ tất cả các phương thức của nó hiện được đặt trong một nguyên mẫu. Điều này có nghĩa là, tất cả 100 trường hợp hiện nay đều chia sẻ bốn phương thức này thay vì mỗi phương thức có bản sao riêng.


9

Tóm lược:

  • Hàm là các đối tượng trong javascript và do đó có thể có các thuộc tính
  • Các hàm (Con Contortor) luôn có một thuộc tính nguyên mẫu
  • Khi một hàm được sử dụng như một hàm tạo với newtừ khóa, đối tượng sẽ lấy nguyên mẫu. Một tham chiếu đến nguyên mẫu này có thể được tìm thấy trên thuộc __proto__tính của đối tượng mới được tạo.
  • Đây __proto__sở hữu đề cập đến prototypetài sản của hàm constructor.

Thí dụ:

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

let me = new Person('willem');

console.log(Person.prototype) // Person has a prototype property

console.log(Person.prototype === me.__proto__) // the __proto__ property of the instance refers to prototype property of the function.

Tại sao điều này hữu ích:

Javascript có một cơ chế khi tìm kiếm các thuộc tính trên các Đối tượng được gọi là 'thừa kế nguyên mẫu' , đây là những gì về cơ bản là:

  • Đầu tiên được kiểm tra nếu thuộc tính nằm trên chính Object. Nếu vậy tài sản này được trả lại.
  • Nếu thuộc tính không nằm trên chính đối tượng, nó sẽ 'trèo lên protochain'. Về cơ bản, nó nhìn vào đối tượng được gọi bởi thuộc tính proto . Ở đó, nó kiểm tra nếu thuộc tính có sẵn trên đối tượng được gọi bởi proto
  • Nếu thuộc tính không nằm trên đối tượng proto, nó sẽ leo lên chuỗi proto cho đến đối tượng Object.
  • Nếu nó không thể tìm thấy thuộc tính ở đâu trên đối tượng và chuỗi nguyên mẫu của nó, nó sẽ trả về không xác định.

Ví dụ:

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

let mySelf = new Person('Willem');

console.log(mySelf.__proto__ === Person.prototype);

console.log(mySelf.__proto__.__proto__ === Object.prototype);

Cập nhật:

Các __proto__tài sản đã bị phản đối, mặc dù nó được thực hiện ở hầu hết các trình duyệt hiện đại một cách tốt hơn để có được những tài liệu tham khảo đối tượng nguyên mẫu sẽ là:

Object.getPrototypeOf()


7

Tôi luôn thích sự tương tự khi hiểu loại công cụ này. 'Kế thừa nguyên mẫu' là khá khó hiểu so với kế thừa âm trầm của lớp theo quan điểm của tôi, mặc dù các nguyên mẫu là mô hình đơn giản hơn nhiều. Trong thực tế với các nguyên mẫu, thực sự không có sự kế thừa, do đó, tên và trong đó gây hiểu lầm, nó giống như một loại 'phái đoàn'.

Hãy tưởng tượng điều này ....

Bạn đang học cấp ba, bạn đang ở trong lớp và có một bài kiểm tra sắp tới hôm nay, nhưng bạn không có bút để điền vào câu trả lời của mình. Đừng!

Bạn đang ngồi cạnh người bạn Fin của bạn, người có thể có một cây bút. Bạn hỏi, và anh ta nhìn quanh bàn làm việc không thành công, nhưng thay vì nói "Tôi không có bút", anh ta là một người bạn tốt mà anh ta kiểm tra với người bạn khác Derp nếu anh ta có bút. Derp thực sự có một cây bút dự phòng và chuyển nó lại cho Fin Pa, người chuyển nó cho bạn để hoàn thành bài kiểm tra của bạn. Derp đã giao bút cho Fin Pa, người đã ủy thác bút cho bạn sử dụng.

Điều quan trọng ở đây là Derp không đưa bút cho bạn, vì bạn không có mối quan hệ trực tiếp với anh ta.

Đây là một ví dụ đơn giản về cách thức hoạt động của các nguyên mẫu, trong đó một cây dữ liệu được tìm kiếm cho thứ bạn đang tìm kiếm.


3

một lược đồ khác hiển thị quan hệ __proto__ , nguyên mẫu và hàm tạo : nhập mô tả hình ảnh ở đây


1

Chỉ là bạn đã có một đối tượng Object.newnhưng bạn vẫn không có đối tượng khi sử dụng cú pháp hàm tạo.


1

Điều quan trọng là phải hiểu rằng có một sự khác biệt giữa nguyên mẫu của một đối tượng (có sẵn thông qua Object.getPrototypeOf(obj)hoặc thông qua thuộc tính không dùng nữa __proto__) và thuộc prototypetính trên các hàm xây dựng. Cái trước là thuộc tính trên mỗi thể hiện và cái sau là thuộc tính trên constructor. Đó là, Object.getPrototypeOf(new Foobar())đề cập đến cùng một đối tượng như Foobar.prototype.

Tham khảo: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes


0

Các thử nghiệm tạo ra đối tượng mới bằng cách nhân bản hiện có đối tượng . Vì vậy, thực sự khi chúng ta nghĩ về nguyên mẫu, chúng ta thực sự có thể nghĩ rằng nhân bản hoặc tạo một bản sao của một cái gì đó thay vì tạo ra nó.

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.