Trước khi xem bài này, các bạn nên ôn lại kiến thức bằng cách xem lại 2 bài viết về object và prototype trong series JavaScript sida. Trước khi phân tích về OOP trong JavaScript, mình sẽ nhắc lại sơ 1 số khái niệm trong OOP. Bạn nào quên rồi có thể lên wiki xem lại nhé: https://vi.wikipedia.org/wiki/Lập trình hướng đối tượng.
Trong phạm vi bài viết, ta sẽ bàn về 3 đặc tính của OOP, so sánh cách hiện thực chúng trong Java và JavaScript. Vì Wiki có sẵn rồi nên mình chỉ copy vào thôi, không giải thích lại lòng vòng nhé:
- Encapsulation: Tính bao đóng và che giấu thông tin. Tính chất này không cho phép người sử dụng các đối tượng thay đổi trạng thái nội tại của một đối tượng. Chỉ có các phương thức nội tại của đối tượng cho phép thay đổi trạng thái của nó. Việc cho phép môi trường bên ngoài tác động lên các dữ liệu nội tại của một đối tượng theo cách nào là hoàn toàn tùy thuộc vào người viết mã. Đây là tính chất đảm bảo sự toàn vẹn của đối tượng.
- Inheritance: Tính kế thừa. Đặc tính này cho phép một đối tượng có thể có sẵn các đặc tính mà đối tượng khác đã có thông qua kế thừa. Điều này cho phép các đối tượng chia sẻ hay mở rộng các đặc tính sẵn có mà không phải tiến hành định nghĩa lại. Tuy nhiên, không phải ngôn ngữ định hướng đối tượng nào cũng có tính chất này.
- Polymophism: Tính đa hình. Thể hiện thông qua việc gửi các thông điệp (message). Việc gửi các thông điệp này có thể so sánh như việc gọi các hàm bên trong của một đối tượng. Các phương thức dùng trả lời cho một thông điệp sẽ tùy theo đối tượng mà thông điệp đó được gửi tới sẽ có phản ứng khác nhau. Người lập trình có thể định nghĩa một đặc tính (chẳng hạn thông qua tên của các phương thức) cho một loạt các đối tượng gần nhau nhưng khi thi hành thì dùng cùng một tên gọi mà sự thi hành của mỗi đối tượng sẽ tự động xảy ra tương ứng theo đặc tính của từng đối tượng mà không bị nhầm lẫn.
OOP trong Java
Như các bạn đã biết, Java là một ngôn ngữ hướng đối tượng, do đó việc hiện thực các đặc tính OOP rất đơn giản và nhanh gọn, dễ hiểu.
Tính bao đóng trong Java thể hiện bằng cách cho cách khai báo các trường private, chỉ có thể truy xuất trong qua các hàm get, set.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Person { | |
private String firstName; | |
private String lastName; | |
private int age; | |
public String getFirstName() { return this.firstName; } | |
public void setFirstName(String firstName) { this.firstName = firstName; } | |
public String getLastName() { return this.lastName; } | |
public void setLastName(String lastName) { this.lastName = lastName; } | |
public int getAge() { return this.age; } | |
public void setAge(int age) { this.age= age; } | |
} |
Tính kế thừa và đa hình cũng khá đơn giản, chỉ cần extend và viết hàm mới đè lên là xong (các bạn để ý access modifier private và protect nhé, tí mình sẽ so sánh nó với JavaScript).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Person { | |
private int personSecret; // Chỉ person truy xuất được | |
protected int age; // Superman try xuất được | |
public void say(String message) | |
{ | |
System.out.println("Person said + " + message); | |
} | |
} | |
// Kế thừa và override hàm say | |
public class Superman extends Person { | |
public void say(String message) | |
{ | |
System.out.println("Superman said + " + message); | |
} | |
} |
OOP trong JavaScript
JavasSript thì ngược lại, do nó vốn sida nên ta phải áp dụng một số chiêu trò để hiện thực các đặc tính này. Trong JavaScript, để hiện thực tính bao đóng , ta có thể tạo ra 1 Constructor Function, đóng gói toàn bộ các trường và hàm vào 1 object. Thông thường, các bạn hay khai báo như sau:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Person(firstName, lastName) { | |
this.firstName = firstName; | |
this.lastName = lastName; | |
this.showName = function() { | |
console.log(this.firstName + ' ' + this.lastName); | |
}; | |
} | |
var psn1 = new Person('Hoang', 'Pham'); | |
// các property khai báo vào biến this có thể bị truy xuất từ bên ngoài | |
// object không còn bao đóng nữa | |
psn1.firstName = 'changed'; | |
console.log(ps1.firstName); |
Với các khai báo này, tính bao đóng không được đảm bảo. Các property có thể bị truy cập, thay đổi từ bên ngoài. Ở đây, ta phải sử dụng biến cục bộ.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Person(firstName, lastName) { | |
var fstName = firstName; | |
var lstName = lastName; | |
this.setFirstName = function(firstName) { fstName = firstName; }; | |
this.getFirstName = function() { return fstName; }; | |
this.setLastName = function(lastName) { lstName = lastName; }; | |
this.getLastName = function() { return lstName; } | |
} | |
var psn1 = new Person('Hoang', 'Pham'); | |
console.log(ps1.fstName); // Undefined, không thể truy cập được | |
console.log(ps1.getFirstName()); // Hoang |
Tuy nhiên, biến cục bộ này chỉ có thể truy xuất trong Constructor Function, nó tương đương với các trường private trong Java. Trong javascript, không có cách nào để tạo ra các trường protected (Chỉ có thể truy cập từ class kế thừa) như Java và C# được. Việc kế thừa còn sida hơn, như mình đã nhắc tới ở bài prototype, trong JavaScript không có từ khóa extends cũng chẳng có class, ta phải sử dụng prototype để kế thừa.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Person() { | |
this.firstName = 'Per'; | |
this.lastName = 'son'; | |
this.sayName = function() { return this.firstName + ' ' + this.lastName }; | |
} | |
// Viết một Constructor Function khác | |
function SuperMan(firstName, lastName) { | |
this.firstName = firstName; | |
this.lastName = lastName; | |
} | |
// Ta muốn SuperMan sẽ kế thừa các thuộc tính của Person | |
// Sử dụng prototype để kế thừa | |
SuperMan.prototype = new Person(); | |
// Tạo một object mới bằng Constructor Function | |
var sm = new SuperMan('Hoang', 'Pham'); | |
sm.sayName(); // Hoang Pham. Hàm này kế thừa từ prototype của Person |
Kết luận
Lời khuyên cuối cùng của mình là: Nếu bạn cảm thấy khó chịu với JavaScript, bạn có thể thử học TypeScript, CoffeeScript hoặc ES6. Mình chưa dùbg TypeScript bao giờ nhưng đã xem thử code và syntax thì thấy nó khá rõ ràng, trong sáng và bớt sida hơn JavaScript nhiều (Có class có extends đủ cả).
Các bạn có thể tham khảo thêm ở đây (Mấy bài này không có so sánh Java với JavaScript như mình đâu nhé):
- http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/.
- http://www.htmlgoodies.com/beyond/javascript/class-member-encapsulation-in-javascript-data-hiding.html#fbid=Z6u8YpIFciH
- http://www.zipcon.net/~swhite/docs/computers/languages/object_oriented_JS/methods.html
- http://www.intertech.com/Blog/encapsulation-in-javascript/
Dạo này mình đang hơi bí ý tưởng cho blog, bạn nào có thắc mắc hay yêu cầu gì thì cứ hỏi tự nhiênnhé, nếu thấy yêu cầu hay thì mình sẽ viết bài trả lời. 😀
Cám ơn bạn về bài viết, mình vẫn có một thắc mắc về tính đa hình trong javascript, bạn có thể biểu diễn 1 xíu về vấn đề này giúp mình được ko
LikeLike
Javascript sử dụng duck-typing nên muốn thực hiện tính đa hình bạn chỉ cần cho object con override lại hàm của object cha là đc 😀
LikeLike
cám ơn bạn để mình tìm hiểu thử
LikeLike
Nhân đây em cũng ghóp ý với bác xíu, nếu bác hết ý tưởng thì bác có thể viết thêm một số bài về NodeJS hoặc 1 vài mẫu của Design Pattern mà bác hiểu cho mọi người cùng tìm hiểu, mình rất thích phong cách viết bài của bạn. 😀
LikeLiked by 1 person
Bạn viết một bài về session và cookie được không 😀 ví dụ như khi đăng nhập facebook thì nó lưu session thế nào và cookie thế nào (mình xin lỗi nếu nó đơn giản, mình mới chuyển sang lập trình gần đây thôi nên hơi lơ mơ). Nếu có cách sử dụng trong ASP.Net MVC luôn thì tốt ạ. Mình cảm ơn 😀
LikeLike
Session và cookie đơn giản nên có nhiều tài liệu về nó rồi bạn à, bạn google tí là ra nhé 😀
LikeLike
Anh review Entity FW so với các FW khác trong .NET theo kinh nghiệm của anh được không ạ. Em cảm ơn anh nhiều.
LikeLike
Anh ơi a có thể viết 1 bài về ajax và web api được không ạ ?
LikeLike
Anh có giải thích về RestAPI trong bài đầu của series facebook chat bot ấy, em gõ vào khung search nhé.
LikeLike
function Person(firstName, lastName) {
var fstName = firstName;
var lstName = lastName;
this.setFirstName = function(firstName) { fstName = firstName; };
this.getFirstName = function() { return fstName; };
this.setLastName = function(lastName) { lstName = lastName; };
this.getLastName = function() { return lstName; }
}
var psn1 = new Person(‘Hoang’, ‘Pham’);
console.log(ps1.fstName); // Undefined, không thể truy cập được
console.log(ps1.getFirstName()); // Hoang
A giải thích rõ hơn về cái này giúp e với, tại sao ko cần gọi hàm set mà vẫn gọi ra được??? Gọi hàm new thì thằng set nó tự chạy?
LikeLike
function Person() {
this.firstName = ‘Per’;
this.lastName = ‘son’;
this.sayName = function() { return firstName + ‘ ‘ + lastName };
}
Có phải anh thiếu this ở return firstName + ‘ ‘ + lastName không ạ
=> return this.firstName + ‘ ‘ + this.lastName
LikeLike
Ừm để a fix nhé 😉
LikeLike