Problem with creating objects with the constructor function:
Xem xét hàm constructor ở dưới đây :
function Human(firstName, lastName) { this.firstName = firstName, this.lastName = lastName, this.fullName = function() { return this.firstName + " " + this.lastName; }
} var person1 = new Human("Virat", "Kohli"); console.log(person1)
Hãy tạo object person1 và person2 bằng Human function constructor Human:
var person1 = new Human("Virat", "Kohli");
var person2 = new Human("Sachin", "Tendulkar");
Chạy đoạn code trên, JavaScript sẽ tạo 2 bản sao của constructor function, của person1 và persion2.
Mỗi Object sẽ tạo một constructor có properties và method bản sao của riêng chính nó. Nó không có nghĩa là tạo 2 instances của function fullname cùng làm một việc. Lưu trữ các instance của function cho mỗi object rất tốn bộ nhớ.
Chúng ta sẽ đi tiếp phần tiếp theo để giải quyết vấn đề này.
Prototypes
Khi một function được tạo trong Javascript, Javascript engine sẽ thêm prototype property nguyên mẫu vào trong function. Prototype property là một object (gọi là prototype object) có constructor property là mặc định. Con trỏ constructor property trỏ tới function mà prototype object là một thuộc tính.Chúng ta có thể truy cập function's prototyp property bằng cách dùng functionName.prototype.
Trên ảnh ở phía trên, function Human constructor có proptype property trỏ đến prototype object. Prototype object có constructor property trỏ lại function Human constructor. Hãy nhìn ví dụ dưới :
function Human(firstName, lastName) { this.firstName = firstName, this.lastName = lastName, this.fullName = function() { return this.firstName + " " + this.lastName; }
} var person1 = new Human("Virat", "Kohli"); console.log(person1)
console.log(person1)
Để truy cập prototype property của function Human constructor , bạn có thể dùng cú pháp sau :
console.log(Human.prototype)
Như ở ảnh trên prototype property của function là một object (proptype object) bao gồm 2 thuộc tính:
- constructor property trỏ đến chính nó function Human
- __ proto__ property: Chúng ta sẽ thảo luận về điều này trong kế thừa của Javascript
Creating an object using the constructor function
Khi một object được tạo trong Javascript, Javascript engine thêm __ proto__ property vào một object được gọi là duner proto.
Duner proto or __ proto__ trỏ tới property object của constructor function.
Trên ảnh trên , object person1 được tạo bằng function Human constructor có duner proto or __ proto__ property trỏ đến protyotyoe object của constructor function.
//Create an object person1 using the Human constructor function
var person1 = new Human("Virat", "Kohli");
Trên ảnh trên đối tượng person1 có duner proto or propery __ proto__ và Human.prototype là bằng nhau. Hãy check chúng bằng câu lệnh "==="
Human.prototype === person1.__proto__ //true
Điều này cho thấy dunder proto property và Human.prototype đều cùng trỏ đến một object.
Bây giờ hãy tạo một object person2 khác dùng function constructor Human:
var person2 = new Human("Sachin", "Tendulkar");
console.log(person2);
Kết quả output cũng show duner proto property bằng với property Human.prototyp và cùng trỏ đến một object.
Human.prototype === person2.__proto__ //true
person1.__proto__ === person2.__proto__ //true
Điều trên chứng tỏ rằng person1 và person2 có property duner proto trỏ đến function constructor :
Propotype object của constructor function được chia sẻ chung cho tất cả object được tạo ra bằng cách dùng constructor function.
Prototype Object
Prototype object là một object, chúng chứa property và method. Do đó, tất cả các object được tạo bằng function constructor có thể chia sẻ property và method.
Thuộc tính mới có thể được thêm vào constructor function bằng cách dùng dấu chấm hoặc bằng dấu ngoặc vuông như dưới đây :
//Dot notation
Human.prototype.name = "Ashwin";
console.log(Human.prototype.name)//Output: Ashwin //Square bracket notation
Human.prototype["age"] = 26;
console.log(Human.prototype["age"]); //Output: 26 console.log(Human.prototype);
name và age đã được thêm vào prototyoe Human.
Example
//Create an empty constructor function
function Person(){ }
//Add property name, age to the prototype property of the Person constructor function
Person.prototype.name = "Ashwin" ;
Person.prototype.age = 26;
Person.prototype.sayName = function(){ console.log(this.name);
} //Create an object using the Person constructor function
var person1 = new Person(); //Access the name property using the person object
console.log(person1.name)// Output" Ashwin
Hãy phân tích 1 chút khi in ra log console.log(person1.name). Hãy check nếu object person có property name :
console.log(person1);
Object person1 bị trống và nó không có property ngoài trừ property duner proto. Nhưng tại sao output khi in ra console.log(person1.name) là Ashwin? Khi chúng ta cố gắng truy cập một thuộc tính của một object, engine Javascript sẽ cố gắng tìm thuộc tính đấy trong object, nếu thuộc tính ấy có trên một object thì nó sẽ có value. Nhưng nếu không có thuộc tính ấy trên object thì nó sẽ tìm thuộc tính ấy prototype object hoặc dunder proto. Chuỗi tìm kiếm này sẽ tiếp tục cho đến khi dunder proto bị null.Trong trường hợp trên, output là undefined.
Nhưng khi gọi person1.name, engine Javascript sẽ check thuộc tính đấy có tồn tại trong object. Trong trường hợp này, thuộc tính name không nằm trên object person1. Bây giờ engine Javascript check nếu thuộc thuộc tính name tồn tại trong property dunder proto hoặc prototype của object person1 thì sẽ trả về value : Ashwin.
Hãy tạo object person2 bằng cách tạo function constructor Person:
var person2 = new Person();
//Access the name property using the person2 object
console.log(person2.name)// Output: Ashwin
Bây giờ hãy định nghĩa property name của object person1:
person1.name = "Anil"
console.log(person1.name)//Output: Anil
console.log(person2.name)//Output: Ashwin
Output của person1.name là Anil. Như đã nói ở trên, engine Javascript đầu tiên sẽ tìm thuộc tính ở trên object của chính nó. Trong trường hợp trên, name của object person1 ở trên chính chính nó còn thuộc tính name của object person2 lại không nằm trên chính nó mà nằm ở prototype object.
Problems with the prototype
Prototype object được chia sẽ cho tất cả các object bằng constructor function, các properties và method cũng được chia sẽ cho tất cả cá đối tượng. Nếu một object A sửa đổi property của prototype có gía trị nguyên thuỷ, các đối tượng khác không bị ảnh hưởng vì đối tượng A sẽ tạo một thuộc tính ngay trên object đó, bạn có thể xem ảnh bên dưới :
1 console.log(person1.name);//Output: Ashwin 2 console.log(person2.name);//Output: Ashwin 3 person1.name = "Ganguly" 4 console.log(perosn1.name);//Output: Ganguly
5 console.log(person2.name);//Output: Ashwin
Chúng ta sẽ thảo luận một vấn đề về prototype khi Prototype object chứa thuộc tính tham chiếu.
//Create an empty constructor function
function Person(){
}
//Add property name, age to the prototype property of the Person constructor function
Person.prototype.name = "Ashwin" ;
Person.prototype.age = 26;
Person.prototype.friends = ['Jadeja', 'Vijay'],//Arrays are of reference type in JavaScript
Person.prototype.sayName = function(){ console.log(this.name);
} //Create objects using the Person constructor function
var person1= new Person();
var person2 = new Person(); //Add a new element to the friends array
person1.friends.push("Amit"); console.log(person1.friends);// Output: "Jadeja, Vijay, Amit"
console.log(person2.friends);// Output: "Jadeja, Vijay, Amit"
Trong ví dụ ở trên, person1 và person2 cùng trỏ tới array friends của prototype object, person1 sửa đổi friends bằng cách thêm 1 phần tử string vào trong mảng.
Vì array friends tồn tại trên Person.prototype, không phải trên object person 1, các thay đổi của array friends trên person1 cũng được tham chiếu trên person2.friends (cùng trỏ đến một mảng).
Nếu ý định có 1 mảng được chia sẻ cho tất cả object thì trường hợp này ok không vấn đề gì nhưng bạn cũng nên cẩn thận trường hợp này vì nếu chia sẽ cho tất cả object thì gắp vấn đề khi một object thay đổi mảng thì mảng của tất cả object bị thay đổi khiến cho một object khác hoạt động không còn chính xác nữa.
Combine Constructor/Prototype
Để giải quyết các vấn đề về prototype và constructor, chúng ta có thể combine cả hai.
- Vấn đề về constructor : Mọi object đều có instance của function.
- Vấn đề của protptype: Sửa đổi một thuộc tính bằng cách sử dụng object khác cùng tham chiếu tới nó.
Để giải quyết 2 vấn đề trên, chúng ta có thể define tất cả các object bên trong constructor và shared tất cả các thuộc tính properties và method bên trong prototype như bên dưới :
//Define the object specific properties inside the constructor
function Human(name, age){ this.name = name, this.age = age, this.friends = ["Jadeja", "Vijay"]
}
//Define the shared properties and methods using the prototype
Human.prototype.sayName = function(){ console.log(this.name);
}
//Create two objects using the Human constructor function
var person1 = new Human("Virat", 31);
var person2 = new Human("Sachin", 40); //Lets check if person1 and person2 have points to the same instance of the sayName function
console.log(person1.sayName === person2.sayName) // true //Let's modify friends property and check
person1.friends.push("Amit"); console.log(person1.friends)// Output: "Jadeja, Vijay, Amit"
console.log(person2.friends)//Output: "Jadeja, Vijay"
Ở trên chúng tối muốn mỗi object có name, age và friends riêng. Do đó , chúng ta có thể defined các property bên trong constructor bằng cách dùng this. Tuy nhiên , đối tượng sayName được defined trên prototype object, nó sẽ share cho tất cả các object.
Trong ví dụ trên, thuộc tính friends của person2 không bị thay đổi khi thay đổi thuộc tính fiends của person1.
Tài liệu tham khảo
https://medium.com/better-programming/prototypes-in-javascript-5bba2990e04b