Thừa kế và đa hình trong JavaScript
1. Thừa kế trong ECMAScript
Trước khi bắt đầu học về "Thừa kế trong ECMAScript", hãy đảm bảo rằng bạn đã có khái niệm về "Lớp và đối tượng", nếu không hãy tìm hiểu nó:
ECMAScript cho phép bạn tạo một lớp mở rộng từ một hoặc nhiều lớp khác. Lớp này được gọi là lớp dẫn xuất (Derived class), hoặc đơn giản gọi là lớp con.
Lớp con sẽ thừa kế các property, phương thức và các thành viên khác từ lớp cha. Nó cũng có thể ghi đè (override) các phương thức từ lớp cha. Trong ECMAScript mỗi lớp chỉ có duy nhất 1 constructor. Nếu một lớp không tự định nghĩa một constructor của riêng nó, nó sẽ được thừa kế constructor của lớp cha. Ngược lại nếu lớp con định nghĩa một constructor, nó sẽ không được thừa kế constructor của lớp cha.
Khác với Java, CSharp và một vài ngôn ngữ khác, ECMAScript cho phép đa thừa kế. Một lớp có thể được mở rộng (extends) từ một hoặc nhiều lớp cha.
Chúng ta cần một vài class tham gia vào minh họa.
- Animal: Class mô phỏng một lớp Động vật.
- Duck: Class mô phỏng lớp vịt, là một class con của Animal.
- Cat: Class mô phỏng lớp mèo, là một class con của Animal
- Mouse: Class mô phỏng lớp chuột, là một class con của Animal.
Trong ECMAScript, constructor sử dụng để tạo một đối tượng, và gán giá trị cho các property. Constructor của lớp con bao giờ cũng gọi tới constructor của lớp cha để gán giá trị cho các property của lớp cha, sau đó nó mới gán giá trị cho các property của nó.
Hãy xem ví dụ:
Cat là lớp con thừa kế từ lớp Animal, nó cũng có các property riêng của nó.
inherit-example1.js
class Animal {
constructor(name) {
this.name = name;
}
showInfo() {
console.log("I'm " + this.name);
}
move() {
console.log("Moving..");
}
}
class Cat extends Animal {
constructor(name, age, height) {
super(name);
// Cat's properties:
this.age = age;
this.height = height;
}
// Ghi đè (override) phương thức cùng tên của lớp cha.
showInfo() {
console.log("My name is " + this.name);
}
// Other method...
sleep() {
console.log("Sleeping..");
}
}
// ------------- TEST --------------------
let tom = new Cat("Tom", 3, 20);
console.log("Call move() method");
tom.move();
console.log("\n");
console.log("Call showInfo() method");
tom.showInfo();
Output:
Call move() method
Moving..
Call showInfo() method
My name is Tom
Điều gì đã xẩy ra khi bạn khởi tạo một đối tượng từ constructor?. Nó sẽ gọi lên constructor của lớp cha như thế nào? Bạn hãy xem hình minh họa dưới đây:
Với hình minh họa trên bạn thấy rằng, constructor của lớp cha được gọi trong constructor của lớp con, nó sẽ gán giá trị cho các property của lớp cha, sau đó các property của lớp con cũng được gán giá trị.
2. Ghi đè phương thức
Mặc định lớp con được thừa kế các phương thức từ lớp cha, tuy nhiên lớp con có thể ghi đè (override) phương thức của lớp cha.
inheritance-example2.js
class Animal {
constructor(name) {
this.name = name;
}
showInfo() {
console.log("I'm " + this.name);
}
move() {
console.log("Moving..");
}
}
class Mouse extends Animal {
constructor(name, age, height) {
super(name);
this.age = age;
this.height = height;
}
// Ghi đè (override) phương thức cùng tên của lớp cha.
showInfo() {
// Gọi phương thức showInfo() của lớp cha.
super.showInfo();
console.log ("Age: " + this.age);
console.log ("Height: " + this.height);
}
// Ghi đè (override) phương thức cùng tên của lớp cha.
move() {
console.log("Mouse Moving..");
}
}
// ------------- TEST --------------------
let jerry = new Mouse("Jerry", 3, 5);
console.log("Call move() method");
jerry.move();
console.log("\n");
console.log("Call showInfo() method");
jerry.showInfo();
Output:
Call move() method
Mouse Moving..
Call showInfo() method
I'm Jerry
Age: 3
Height: 5
3. Phương thức trừu tượng
Khái niệm về một phương thức trừu tượng (abstract method) hoặc một lớp trừu tượng.(abstract class) có trong các ngôn ngữ như Java, C#. Nhưng nó không có một cách rõ ràng trong ECMAScript. Tuy nhiên chúng ta có cách để định nghĩa nó.
Một lớp được gọi là trừu tượng (abstract) định nghĩa ra các phương thức trừu tượng và lớp con phải ghi đè (override) các phương thức này nếu muốn sử dụng chúng. Các phương thức trừu tượng luôn ném ra ngoại lệ "Not Implemented Error".
abstract-example.js
// Một lớp trừu tượng (Abstract class).
class AbstractDocument {
constructor(name) {
this.name = name
}
// Một phương thức không thể sử dụng được, vì nó luôn ném ra lỗi.
show() {
// Not Implemented Error
throw "Subclass must implement abstract method";
}
}
class PDF extends AbstractDocument {
// Ghi đè phương thức của lớp cha
show() {
console.log("Show PDF document:", this.name);
}
}
class Word extends AbstractDocument {
show() {
console.log("Show Word document:", this.name)
}
}
// -------------------- TEST -------------------------
let doc1 = new PDF("Python tutorial");
let doc2 = new Word("Java IO Tutorial");
let doc3 = new PDF("ECMAScript Date & Time Tutorial");
let documents = [doc1, doc2, doc3];
for (let i = 0; i < documents.length; i++) {
documents[i].show();
}
let doc4 = new AbstractDocument("An Abstract Document");
doc4.show(); // Throw Error!!!
Ví dụ ở trên thể hiện tính đa hình (Polymorphism) trong ECMAScript. Một đối tượng Document (tài liệu) có thể được thể hiện ở các hình thái khác nhau (PDF, Word, Excel, ...).
Một ví dụ khác minh họa về tính đa hình: Khi tôi nói về một người Châu Á, nó khá trừu tượng, anh ta có thể là một người Nhật Bản, người Việt Nam, hoặc một người Ấn Độ, ... Tuy nhiên đều có đặc điểm đặc trưng của người Châu Á.
4. Toán tử instanceof, typeof
instanceof
Toán tử instanceof giúp bạn kiểm tra xem một "cái gì đó" có phải là một đối tượng của một lớp nào đó hay không.
instanceof-example.js
class Person {
}
// A Child class of Person
class AsianPerson extends Person {
}
class Triangle {
}
let person = new Person();
let asianPerson = new AsianPerson();
let triangle = new Triangle();
let isPerson = person instanceof Person;
console.log( isPerson ); // true
console.log( asianPerson instanceof Person ); // true
console.log( person instanceof AsianPerson ); // false
console.log( triangle instanceof Person ); // false
console.log( triangle instanceof AsianPerson ); // false
typeof
Toán tử typeof được sử dụng để kiểm tra kiểu của một "cái gì đó", kết quả nhận được là tên của kiểu.
undefined | "undefined" |
null | "object" |
boolean | "boolean" |
number | "number" |
String | "string" |
Symbol (new in ECMAScript 6) | "symbol" |
Host object (provided by the JS environment) | Implementation-dependent |
Function object (implements [[Call]] in ECMA-262 terms) | "function" |
Any other object | "object" |
typeof-example.js
// A Class:
class Person {
}
// An Object:
let person = new Person();
console.log( typeof Person ); // function
console.log( typeof person ); // object
// null:
console.log( typeof null ); // object
// A Number:
let intValue = 100;
console.log( typeof intValue ); // number
// NaN (Not a Number)
console.log( typeof NaN); // number
// A String
let strValue = "Hello";
console.log( typeof strValue ); // string
// A Boolean:
let boolValue = true;
console.log( typeof boolValue ); // boolean
// undefined
console.log( typeof undefined); // undefined
// An Array
let years = [1980, 2003, 2018];
console.log( typeof years ); // object
// A Function
let myFunc = function() {
}
console.log( typeof myFunc ); // function
// A Symbol
let mySymbol = Symbol();
console.log( typeof mySymbol ); // symbol
Is Sub Class?
Giả sử bạn có 2 lớp A & B. Ví dụ dưới đây giúp bạn kiểm tra xem A có phải là lớp con của B hay không.
let isChild = A === B || A.prototype instanceof B;
issubclass-example.js
class Animal {
}
class Cat extends Animal {
}
class AsianCat extends Cat {
}
// ------------- TEST --------------------
console.log("AsianCat === Animal? " + (AsianCat === Animal)); // false
let isSubClass1 = AsianCat === Animal || AsianCat.prototype instanceof Animal;
console.log("AsianCat is child of Animal? " + isSubClass1); // true
let isSubClass2 = AsianCat === Animal || Animal.prototype instanceof AsianCat;
console.log("Animal is child of AsianCat? " + isSubClass2); // false
Output:
isinstance('abc', object): True
ininstance(123, object): True
b = B()
a = A()
isinstance(b, A): True
isinstance(b, B): True
isinstance(a, B): False
isinstance(B, A): True
isinstance(A, B): False
5. Đa hình với hàm
Các ngôn ngữ như Java, C# rất chặt chẽ về kiểu dữ liệu. Vì vậy khi bạn gọi một phương thức (Hàm) bạn phải truyền vào đúng kiểu dữ liệu ứng với các tham số. Trong ECMAScript khi gọi một hàm bạn có thể truyền vào tham số với kiểu dữ liệu bất kỳ, rủi ro có thể xẩy ra, và người lập trình phải tự quản lý các rủi ro đó.
Ở đây tôi tạo ra 2 lớp English và French. Cả hai lớp này đều có phương thức greeting(). Cả hai tạo ra các lời chào khác nhau. Tạo ra 2 đối tượng tương ứng từ 2 lớp trên và gọi hành động của 2 đối tượng này trong cùng một hàm (Hàm intro)
polymorphism-example.js
class English {
greeting() {
console.log("Hello");
}
}
class French {
greeting() {
console.log("Bonjour");
}
}
function intro(language) {
language.greeting();
}
// -------------- TEST ----------------------
let flora = new English();
let aalase = new French();
// Call function:
intro(flora);
intro(aalase);
let someObject = {};
intro(someObject);// Error!!!
Chạy ví dụ:
OK, Sửa code của ví dụ trên, thêm vào các kiểm tra cần thiết để tránh các rủi ro cho ứng dụng của bạn:
polymorphism-example2.js
class English {
greeting() {
console.log("Hello");
}
}
class French {
greeting() {
console.log("Bonjour");
}
}
function intro(language) {
// Check type of 'language' object.
if(language instanceof English || language instanceof French) {
language.greeting();
}
}
// -------------- TEST ----------------------
let flora = new English();
let aalase = new French();
// Call function:
intro(flora);
intro(aalase);
let someObject = {};
intro(someObject);
Áp dụng kiến trúc "Lớp & Thừa kế" giúp ứng dụng của bạn dễ dàng quản lý hơn và tránh các sai sót.
polymorphism-example3.js
// A Base class
class Language {
}
class English extends Language {
greeting() {
console.log("Hello");
}
}
class French extends Language {
greeting() {
console.log("Bonjour");
}
}
function intro(language) {
// Check type of 'language' object.
if(language instanceof Language) {
language.greeting();
}
}
// -------------- TEST ----------------------
let flora = new English();
let aalase = new French();
// Call function:
intro(flora);
intro(aalase);
let someObject = {};
intro(someObject);
Các hướng dẫn ECMAScript, Javascript
- Giới thiệu về Javascript và ECMAScript
- Bắt đầu nhanh với Javascript
- Hộp thoại Alert, Confirm, Prompt trong Javascript
- Bắt đầu nhanh với JavaScript
- Biến (Variable) trong JavaScript
- Các toán tử Bitwise
- Mảng (Array) trong JavaScript
- Vòng lặp trong JavaScript
- Hàm trong JavaScript
- Hướng dẫn và ví dụ JavaScript Number
- Hướng dẫn và ví dụ JavaScript Boolean
- Hướng dẫn và ví dụ JavaScript String
- Câu lệnh rẽ nhánh if/else trong JavaScript
- Câu lệnh rẽ nhánh switch trong JavaScript
- Hướng dẫn xử lý lỗi trong JavaScript
- Hướng dẫn và ví dụ JavaScript Date
- Hướng dẫn và ví dụ JavaScript Module
- Lịch sử phát triển của module trong JavaScript
- Hàm setTimeout và setInterval trong JavaScript
- Hướng dẫn và ví dụ Javascript Form Validation
- Hướng dẫn và ví dụ JavaScript Web Cookie
- Từ khóa void trong JavaScript
- Lớp và đối tượng trong JavaScript
- Kỹ thuật mô phỏng lớp và kế thừa trong JavaScript
- Thừa kế và đa hình trong JavaScript
- Tìm hiểu về Duck Typing trong JavaScript
- Hướng dẫn và ví dụ JavaScript Symbol
- Hướng dẫn JavaScript Set Collection
- Hướng dẫn JavaScript Map Collection
- Tìm hiểu về JavaScript Iterable và Iterator
- Hướng dẫn sử dụng biểu thức chính quy trong JavaScript
- Hướng dẫn và ví dụ JavaScript Promise, Async Await
- Hướng dẫn và ví dụ Javascript Window
- Hướng dẫn và ví dụ Javascript Console
- Hướng dẫn và ví dụ Javascript Screen
- Hướng dẫn và ví dụ Javascript Navigator
- Hướng dẫn và ví dụ Javascript Geolocation API
- Hướng dẫn và ví dụ Javascript Location
- Hướng dẫn và ví dụ Javascript History API
- Hướng dẫn và ví dụ Javascript Statusbar
- Hướng dẫn và ví dụ Javascript Locationbar
- Hướng dẫn và ví dụ Javascript Scrollbars
- Hướng dẫn và ví dụ Javascript Menubar
- Hướng dẫn xử lý JSON trong JavaScript
- Xử lý sự kiện (Event) trong Javascript
- Hướng dẫn và ví dụ Javascript MouseEvent
- Hướng dẫn và ví dụ Javascript WheelEvent
- Hướng dẫn và ví dụ Javascript KeyboardEvent
- Hướng dẫn và ví dụ Javascript FocusEvent
- Hướng dẫn và ví dụ Javascript InputEvent
- Hướng dẫn và ví dụ Javascript ChangeEvent
- Hướng dẫn và ví dụ Javascript DragEvent
- Hướng dẫn và ví dụ Javascript HashChangeEvent
- Hướng dẫn và ví dụ Javascript URL Encoding
- Hướng dẫn và ví dụ Javascript FileReader
- Hướng dẫn và ví dụ Javascript XMLHttpRequest
- Hướng dẫn và ví dụ Javascript Fetch API
- Phân tích XML trong Javascript với DOMParser
- Giới thiệu về Javascript HTML5 Canvas API
- Làm nổi bật Code với thư viện Javascript SyntaxHighlighter
- Polyfill là gì trong khoa học lập trình?
Show More