Class và prototype 1- Xây dựng class trong JavaScript Trong lập trình hướng đối tượng, chúng ta thường tạo những đối tượng từ các lớp (class), trong đó các object được coi như là trường hợp cụ thể của một lớp. Cả hai ngôn ngữ Java và JavaScript đều cung...
Class và prototype
1- Xây dựng class trong JavaScript
Trong lập trình hướng đối tượng, chúng ta thường tạo những đối tượng từ các lớp (class), trong đó các object được coi như là trường hợp cụ thể của một lớp. Cả hai ngôn ngữ Java và JavaScript đều cung cấp cho chúng ta từ khóa new dùng để tạo một thể hiện của một đối tượng đã được định nghĩa trước đó.
Trong Java, mọi thứ đều là object, và được phát triển từ class nguyên thủy là java.lang.Object. Chắc hẳn nếu bạn là lập trình viên Java hẳn sẽ thấy câu lệnh sau khá quen thuộc:
[quote] MyObject myObj=new MyObject(arg1,arg2);[/quote]
Với JavaScript cũng có cách khai báo rất giống như vậy:
[quote] var myObj= new MyObject(arg1, arg2);[/quote]
Nhưng có một sự khác biệt lớn về bản chất đó là chúng ta hoàn toàn không định nghĩa lớp MyObject như trong Java mà thay vào đó là một hàm thật sự có cùng tên:
[quote] function MyObject(){
//do something here
}
[/quote]
Để minh họa cho điều này, ta xét ví dụ sau khai báo một class Animal với các thuộc tính đơn giản như name, food và phương thức đơn giản là eat:
function Animal(name, food){
//Thuộc tính
this.name=name; //Tên con vật, hoặc tên loài
this.food=food; //Loại thức ăn có thể ăn
//Phương thức
this.eat=function(something){
if(something===food){ //Nếu thức ăn phù hợp
alert('This is very delicious');
}else{
alert("I don't like this");
}
}
}
var myAnimal = new Animal("Cat","Mouse");
myAnimal.eat("Mouse");// This is very delicious
Trong ví dụ trên, một lần nữa ta sử dụng từ khóa this, với cấu trúc trên thì ta có thể hiểu this là đối tượng được tạo ra sau từ khóa new.
Tất nhiên với cách khai báo như vậy mọi việc đều OK, nhưng nếu đi sâu hơn một chút chúng ta sẽ thấy: đầu tiên, cho mỗi thể hiện của class Animal mà chúng ta tạo ra, chúng ta cũng tạo ra một hàm eat() mới, điều này dẫn đến vấn đề về bộ nhớ và tốc độ xử lý nếu bạn có ý định tạo ra rất nhiều đối tượng trong chương trình từ class Animal (ví dụ tạo một đàn kiến chẳng hạn!!!); thứ nữa, khi làm việc với các DOM (Document Object Model), chúng ta sẽ gặp phải vô số vấn đề rắc rối không mong đợi. Chính vì vậy bây giờ ta hãy thử một cách khác an toàn hơn, và nó được biết đến cái tên khá lạ tai: prototype-base.
2- Prototype
Prototype là gì vậy? Thực ra đây là một thuộc tính sẵn có trong mọi object trong JavaScript (trong JavaScrip thuộc tính cũng có thể là một đối tượng, ta sẽ đề cập chi tiết hơn về điều này sau), đây có thể coi là nét đặc trưng của JavaScript mà các ngôn ngữ hướng đối tượng khác không có. Các hàm cũng như các thuộc tính đều có thể kết hợp được với prototype. Prototype và từ khóa new có thể làm việc cùng nhau, khi một đối tượng được tạo bởi từ khóa new thì tất cả các thuộc tính và phương thức prototype đều được gắn vào đối tượng mới đó. Điều này nghe có vẻ lạ và rắc rối, nhưng khá là hiệu quả:
Ta xây dựng lại class Animal theo phong cách prototype như sau:
function Animal(name, food){
//Thuộc tính
this.name=name; //Tên con vật, hoặc tên loài
this.food=food; //Loại thức ăn có thể ăn
}
Animal.prototype.eat=function(something){
if(something===this.food){//Nếu thức ăn phù hợp
alert('This is very delicious');
}else{
alert("I don't like this");
}
}
var myAnimal = new Animal("Cat","Mouse");
myAnimal.eat("Mouse");// This is very delicious
Như vậy đầu tiên chúng ta vẫn khai báo class với các thuộc tính bình thường, sau đó đối với các phương thức ta chỉ việc gắn nó vào prototype như trên. Khi chúng ta tạo một thể hiện của class thì phương thức được gắn vào object đó mà không phải tạo mới, và từ khóa this sẽ đảm bảo rằng trường food là của object vừa tạo.
Trong việc thao tác với prototype, có một lưu ý là mọi object được tạo ra sau prototype thì sẽ được gắn các thuộc tính hoặc phương thức được khai báo kiểu prototype trước đó, ví dụ:
myObject.prototype.color="red";
var myObj1=new myObject();
myObject.prototype.color="blue";
myObject.prototype.sound="boom!";
var myObj2=new myObject();
alert(myObj1.color); //red
alert(myObj2.color); //blue
alert(myObj2.sound); //boom!
alert(myObj1.sound); //erro!
Khi đó myObj1 chỉ có thuộc tính color với giá trị là red, còn myObj2 có thêm thuộc tính sound và giá trị color là blue.
*Mở rộng một object trong JavaScript
Trên thực tế, cơ chế prototype còn có thể áp dụng cho các đối tượng xây dựng sẵn trong JavaScript, ta có thể dùng nó để mở rộng các đối tượng này. Một ví dụ rất hữu ích là mở rộng đối tượng Array như sau:
Array.prototype.indexof=function(obj){
var result=-1;
for(var i=0;i
if(this[i]==obj){
return i;
break;
}
}
return result;
}
var ary=new Array();
ary=["one","two","three"];
alert(ary.indexof("one")) //0
Ví dụ trên, ta mở rộng Array bằng cách thêm vào một phương thức indexof tính chỉ số của một phần tử trong mảng, nếu đối tượng không có trong mảng thì trả về giá trị là -1;
3- Cơ chế của prototype
Một câu hỏi sẽ được đặt ra là cơ chế hoạt động của prototype như thế nào? Tại sao với prototype ta có thể giả lập khá nhiều các đặc tính hướng đối tượng? Thật ra prototype lại có một cơ chế hoạt động khá đơn giản: Mỗi khi bạn thực hiện thao tác với thuộc tính và phương thức của một đối tượng nào đó trong JavaScript, thì trình thông dịch sẽ thực hiện các bước tuần tự sau để xác định thuộc tính hay phương thức nào được thực thi:
-
Nếu thuộc tính hay phương thức của đối tượng đang xét có giá trị hoặc đã được gán giá trị thì thuộc tính hay phương thức đó được sử dụng.
-
Nếu không thì kiểm tra giá trị của thuộc tính của prototype trong cấu trúc của object.
-
Cứ tiếp tục như vậy cho đến khi tìm thấy thuộc tính phù hợp (thuộc tính đã được gán giá trị) hoặc giá trị tìm được là kiểu Object.
Chính vì vậy bất cứ khi nào muốn thêm một thuộc tính hay gắn thêm một phương thức mới cho một object bất kỳ ta chỉ việc khai báo nó như là thuộc tính của prototype.
Để thấy rõ hơn điều này ta sẽ cùng nhau tìm hiểu sâu hơn một chút về các thuộc tính và phương thức của các đối tượng cài đặt sẵn bên trong JavaScript là Object và Function
a) Object:
Thuộc tính:
constructor
prototype
Phương thưc:
hasOwnProperty()
isPrototypeOf()
toString()
valueOf()
toLocaleString()
Ở đây chúng ta chỉ cần lưu ý phương thức khá đặc biệt đó là hasOwnProperty();
Với hasOwnProperty() sẽ trả về giá trị true nếu object đang xét có thuộc tính nào đó nhưng không phải là thuộc tính được khai báo kiểu mở rộng prototype, ví dụ:
var myObj=new Object();
myObj.firstProperty="xyz";
myObj.prototype.secondProperty="abc";
alert(myObj.hasOwnProperty("firstProperty")) //true
alert(myObj.hasOwnProperty("fdasffsdf")) //false
alert(myObj.hasOwnProperty("secondProperty")) //false
b) Function
Thuộc tính:
constructor
prototype
arguments
arity
caller
length
Phương thức:
apply()
call()
toString()
valueOf()
Với đối tượng Function, chúng ta sẽ tìm hiểu kĩ hơn một chút bởi lẽ đây là đối tượng chính dùng trong lập trình OOP với JavaScript.
Thuộc tính constructor: thuộc tính này trả về một constructor mà từ đó object được tạo ra, ví dụ:
var myFunc=new Function();
alert(myFunc.constructor); // kết quả sẽ là Function
Lưu ý rằng thuộc tính này chỉ có trong các biến có kiểu object, nên với một biến bất kỳ muốn sử dụng constructor thì ta phải kiểm tra kiểu trước khi sử dụng, ví dụ.
if(typeof myVar =="object"){
alert(myVar.constructor);
}
Thuộc tính arguments: arguments thực chất là một mảng chứa các tham số cho function, khi function được gán cho một đối số thì đối số này sẽ được đẩy vào mảng arguments, ta xét ví dụ sau:
function test(){
var arg=test.arguments;
for(var i=0;i
alert(arg[i]);
}
}
test(1,"a");// sẽ đưa ra giá trị 1 và sau đó là "a"
Để ý rằng hàm test ban đầu không khai báo tham số, nhưng khi chạy ta đã thêm hai tham số vào cho nó do vậy trong arguments sẽ có 2 giá trị.
Phương thức call() và apply(): Đây là hai phương thức của object Function, dùng để thực thi, hoặc gọi một function. Tham số đầu của hai phương thức trên thường là một object, cái mà sẽ được coi là object hiện thời và là tham số cho hàm thực thi phương thức call() hay apply(). Trong thực tế người ta thường dùng call thay cho apply bởi chức năng không có gì khác nhau, chỉ khác ở tham số truyền vào cho hai phương thức. Để minh họa điều này ta xét ví dụ sau:
function showInfo(){
alert(this.name);
}
function myObject(name){
this.name=name;
this.show=showInfo;
}
var myObj=new myObject("AxitDN");
//Cách thông thường
myObj.show(); //kết quả cho AxitDN
//Sử dụng call
showInfo.call(myObj); //kết quả cho AxitDN
Ví dụ trên cho ta thấy rằng trong JavaScript, các hàm được sử dụng một cách khá tự do, thậm trí được sử dụng với một mục đích hoàn toàn khác so với lúc nó được tạo cho đến khi kết thúc chương trình. Không những thế với thuộc tính đặc biệt prototype càng làm cho chương trình viết bằng JavaScript trở nên vô cùng sinh động.
(Còn nữa)