Lớp và đối tượng trong JavaScript
1. Class đầu tiên của bạn
ECMAScript 5 không có khái niệm về lớp (Class) một cách tường minh, thay vào đó nó mô phỏng một lớp dựa trên 2 khái niệm là function & prototype. Bởi vì ES5 được giới thiệu năm 2011, cho đến nay cú pháp của nó rất phổ biến, tuy nhiên chúng ta không thể phủ nhận rằng tạo ra một lớp theo cách như vậy là khó hiểu đối với các lập trình viên. Trong khi đó, các ngôn ngữ như Java, C#,.. có cách hiện đại hơn để tạo ra một lớp. Phiên bản ES6 ra đời năm 2015 đã kịp thời vá vấn đề này, nó đưa ra cú pháp hiện đại để định nghĩa một lớp.
Với cú pháp mới ECMAScript 6 đã làm giảm sự phức tạp cho lập trình viên khi tạo ra một lớp, và làm giảm sự phức tạp khi tạo ra một lớp con (Subclass). Nhưng về mặt kỹ thuật không có gì thay đổi so với phiên bản trước.
Ở dưới đây tôi có một hình chữ nhật (Rectangle), nó có 2 property quan trọng đó là width (chiều rộng) và height (chiều cao). Chúng ta sẽ định nghĩa một lớp có tên Rectangle để mô phỏng nó với cú pháp ES6.
Tạo một file nguồn rectangle.js:
rectangle.js
// Define a class.
class Rectangle {
// Một Constructor có 2 tham số.
// (Được sử dụng để tạo ra đối tượng)
// this.width trỏ tới property (thuộc tính) width của lớp.
constructor (width = 5 , height = 10) {
this.width = width;
this.height = height;
}
// Phương thức dùng để tính diện tích hình chữ nhật.
getArea() {
var area = this.width * this.height
return area
}
}
// Tạo 1 đối tượng của lớp Rectangle thông qua Constructor.
var rect = new Rectangle(3, 5);
console.log("Height "+ rect.height);
console.log("Width "+ rect.width);
// Gọi phương thức
let area = rect.getArea();
console.log("Area "+ area );
Chạy ví dụ:
Height 5
Width 3
Area 15
Điều gì xẩy ra khi bạn tạo một đối tượng từ constructor của lớp?
Khi bạn gọi Constructor của lớp, một đối tượng mới sẽ được tạo ra, và các property của đối tượng sẽ được gán giá trị từ các tham số.
Trong ECMAScript mỗi lớp chỉ có nhiều nhất một constructor. Cũng giống như hàm, các tham số của constructor cũng có thể có giá trị mặc định. Vì vậy bạn có thể tạo đối tượng theo nhiều cách khác nhau.
// width = 3, height = 5
let rect = new Rectangle(3, 5);
// width = 15, height = 10 (Default)
let rect2 = new Rectangle(15);
// width = 5 (Default), height = 50
let rect3 = new Rectangle(undefined, 50);
// width = 5 (Default), height = 10 (Default)
let rect4 = new Rectangle();
2. Getter & Setter
Trước khi đưa ra khái niệm về Getter & Setter, chúng ta hãy phân tích một tình huống:
Giả sử rằng chúng ta có lớp Person, lớp này có một property là name.
Class Person
class Person {
constructor(name) {
this.name = name;
}
}
Và bạn có thể truy cập vào các property của đối tượng, hoặc gán giá trị mới cho nó mà không gặp bất cứ một vấn đề gì.
// Create an object
let person = new Person("Tom");
// Access to property name.
console.log( person.name); // Tom
// Assign new value to property name.
person.name = "Jerry"; // !!!
console.log( person.name ); // Jerry
Việc truy cập tự do vào một property và thay đổi giá trị của nó từ bên ngoài lớp là thực sự nguy hiểm. Đôi khi bạn muốn có một property mà bên ngoài lớp không thể truy cập được vào nó, hoặc bên ngoài lớp không thể gán giá trị mới cho nó. Getter/Setter cho phép bạn tạo ra một property như vậy.
Từ khóa get đặt trước một phương thức không có tham số của lớp giúp tạo ra một property với tên là tên của phương thức. Phương thức này sẽ được gọi mỗi khi khi chương trình truy cập vào property này.
getter-example1.js
class Person {
constructor (name) {
// property: __name
this.__name = name;
}
// Getter of property name
get name() {
console.log("Call getter of property 'name'!!");
return this.__name;
}
}
// ------------ TEST -----------------
let person = new Person("Tom");
// Access to property 'name' ==> Call getter
console.log( person.name); // Tom
// Assign new value to property name.
person.name = "Jerry"; // Not Working!!!!
// Access to property 'name' ==> Call getter
console.log( person.name); // Tom
Từ khóa set đặt trước một phương thức có 1 tham số của lớp giúp tạo ra một property với tên là tên của phương thức. Phương thức này sẽ được gọi mỗi khi chương trình gán giá trị mới cho property này.
setter-example1.js
class Person {
constructor (name) {
// property: __name
this.__name = name;
}
// Setter of property name
set name(newName) {
console.log("Call setter of property 'name'!!");
this.__name = newName;
}
// A method
showInfo() {
console.log("Person: " + this.__name);
}
}
// ------------ TEST -----------------
let person = new Person("Tom");
// Can not access to property 'name'
console.log( person.name); // undefined
// Set new value to property 'name' ==> Call setter
person.name = "Jerry";
person.showInfo(); // Person: Tom
Ví dụ một property với cả hai Getter & Setter:
getter-setter-example.js
class Rectangle {
constructor (width = 5 , height = 10) {
this.__width = width;
this.height = height;
}
// Getter of property 'width'
get width() {
return this.__width;
}
// Setter of property 'width'
set width(newWidth) {
if(newWidth > 0) {
this.__width = newWidth;
} else {
console.log("Invalid width " + newWidth);
}
}
}
// ------------ TEST ------------------
var rect = new Rectangle(3, 5);
console.log("Height "+ rect.height); // Height: 5
console.log("Width "+ rect.width); // Width: 3
rect.width = -100;
console.log("Height "+ rect.height); // Height: 5
console.log("Width "+ rect.width); // Width: 3
rect.width = 100;
console.log("Height "+ rect.height); // Height: 5
console.log("Width "+ rect.width); // Width: 100
Output:
Height 5
Width 3
Invalid width -100
Height 5
Width 3
Height 5
Width 100
Các property với tiếp đầu ngữ là 2 dấu gạch dưới ( __ ) thường được các lập trình viên quy ước với nhau rằng đừng sử dụng nó ở bên ngoài lớp. Tuy nhiên quy ước đó có thể bị ai đó phá vỡ. Và vì vậy sử dụng các property như vậy là nguy hiểm.
Nếu bạn muốn có một property thực sự riêng tư (Private) nó lên được đặt tên với tiếp đầu ngữ là dấu thăng (hashtag) ( # ). Tuy nhiên code này chỉ có thể chạy được với sự trợ giúp của Babel 7 hoặc mới hơn.
Private property
class Something {
constructor(){
this.#someProperty = "test";
}
}
const instance = new Something();
console.log(instance.#someProperty); // undefined
getter-example2.js
class Person {
constructor (name) {
// Private property: #name
this.#name = name;
}
// Getter of property name
get name() {
console.log("Call getter of property 'name'!!");
return this.#name;
}
}
3. Trường tĩnh (Static Field)
Từ khóa static xuất hiện trong khai báo Getter hoặc Setter giúp bạn định nghĩa một trường tĩnh (static field). Bạn có thể truy cập vào các static field thông qua tên lớp.
Các trường tĩnh (static field) có giá trị cố định (Không thể thay đổi) được gọi là các trường hằng số tĩnh (constant static field).
static-field-example1.js
class Employee {
constructor (fullName, age) {
this.fullName = fullName;
if(age < Employee.MIN_AGE || age > Employee.MAX_AGE) {
throw "Invalid Age " + age;
}
this.age = age;
}
// A static field: MIN_AGE
static get MIN_AGE() {
return 18;
}
// A static field: MAX_AGE
static get MAX_AGE() {
if(!Employee.__MAXA) {
Employee.__MAXA = 60;
}
return Employee.__MAXA;
}
static set MAX_AGE(newMaxAge) {
Employee.__MAXA = newMaxAge;
}
}
// ---- TEST ---------
console.log("Mininum Age Allowed: " + Employee.MIN_AGE);
console.log("Maximum Age Allowed: " + Employee.MAX_AGE);
// Set new Maximum Age:
Employee.MAX_AGE = 65;
console.log("Maximum Age Allowed: " + Employee.MAX_AGE);
let baby = new Employee("Some Baby", 1); // Error!!
Bạn có một cách khác để khai báo một trường tĩnh cho lớp, tuy nhiên các trường tĩnh được tạo ra bởi cách này sẽ không là một hằng số (Constant) vì giá trị của nó có thể thay đổi. Dưới đây là một ví dụ:
static-field-example2.js
class Employee {
constructor (fullName, age) {
this.fullName = fullName;
if(age < Employee.MIN_AGE || age > Employee.MAX_AGE) {
throw "Invalid Age " + age;
}
this.age = age;
}
}
Employee.MIN_AGE = 18;
Employee.MAX_AGE = 60;
// ---- TEST ---------
console.log("Mininum Age Allowed: " + Employee.MIN_AGE);
console.log("Maximum Age Allowed: " + Employee.MAX_AGE);
// Set new Maximum Age:
Employee.MAX_AGE = 65;
console.log("Maximum Age Allowed: " + Employee.MAX_AGE);
let baby = new Employee("Some Baby", 1); // Error!!
4. Phương thức tĩnh
Từ khóa static xuất hiện trong khai báo một phương thức của lớp giúp bạn định nghĩa một phương thức tĩnh (static method). Bạn có thể gọi phương thức tĩnh thông qua tên của lớp. Phương thức tĩnh không thể gọi thông qua đối tượng của lớp. Phương thức tĩnh thường được sử dụng như là một hàm tiện ích của ứng dụng.
point.js
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
// Tính khoảng cách giữa 2 điểm
static distance( point1, point2) {
const dx = point1.x - point2.x;
const dy = point1.y - point2.y;
return Math.hypot(dx, dy);
}
}
// --- TEST ---
let point1 = new Point( 5, 10);
let point2 = new Point( 15, 20);
// Distance
let d = Point.distance(point1, point2);
console.log("Distance between 2 points: " + d);
Output:
Distance between 2 points: 14.142135623730951
5. Toán tử so sánh đối tượng
Trong ECMAScript, khi bạn tạo một đối tượng thông qua constructor sẽ có một thực thể thực sự được tạo ra nằm trên bộ nhớ, nó có một địa chỉ xác định.
Một phép toán gán đối tượng AA bởi một đối tượng BB không tạo ra thêm thực thể trên bộ nhớ, nó chỉ là trỏ địa chỉ của AA tới địa chỉ của BB.
Một phép toán gán đối tượng AA bởi một đối tượng BB không tạo ra thêm thực thể trên bộ nhớ, nó chỉ là trỏ địa chỉ của AA tới địa chỉ của BB.
Toán tử === dùng để so sánh địa chỉ 2 đối tượng trỏ đến, nó trả về true nếu cả 2 đối tượng cùng trỏ tới cùng một địa chỉ trên bộ nhớ. Toán tử !== cũng sử dụng để so sánh 2 địa chỉ của 2 đối tượng trỏ đến, nó trả về true nếu 2 đối tượng trỏ tới 2 địa chỉ khác nhau.
identify-operator-example.js
// Define a class.
class Rectangle {
constructor (width = 5 , height = 10) {
this.width = width;
this.height = height;
}
getArea() {
var area = this.width * this.height;
return area;
}
}
// Tạo đối tượng: r1
let r1 = new Rectangle( 20, 10);
// Tạo đối tượng: r2
let r2 = new Rectangle( 20, 10);
let r3 = r1;
let b12 = r1 === r2; // false
let b13 = r1 === r3; // true
console.log("r1 === r2 ? " + b12); // false
console.log("r1 === r3 ? " + b13); // true
var bb12 = r1 !== r2; // true
var bb13 = r1 !== r3; // false
console.log("r1 !== r2 ? " + bb12); // true
console.log("r1 !== r3 ? " + bb13); // false
Chạy ví dụ:
r1 === r2 ? false
r1 === r3 ? true
r1 !== r2 ? true
r1 !== r3 ? false
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