Cú pháp cơ bản của class trong Javascript

Khi lập trình, chúng ta thường sẽ phải tạo ra nhiều object với cùng kiểu, ví dụ như: các đối tượng người dùng hay các đối tượng sản phẩm,…

Để giải quyết vấn đề này, bạn có thể sử dụng hàm khởi tạo với từ khóa new.

Tuy nhiên, từ ES6 trở đi, JavaScript có thêm từ khóa class, với nhiều đặc điểm và tính năng hữu ích được áp dụng trong lập trình hướng đối tượng.

Cú pháp cơ bản của class trong JavaScript

Cú pháp class cơ bản là:

class

MyClass

{

constructor

(

)

{

...

}

method1

(

)

{

...

}

method2

(

)

{

...

}

method3

(

)

{

...

}

...

}

Bạn sử dụng new MyClass() để tạo mới một đối tượng chứa tất cả các phương thức được định nghĩa trên.

Phương thức constructor() được gọi một cách tự động với từ khóa new. Do đó, bạn có thể khởi tạo các thuộc tính cho object trong hàm khởi tạo.

Ví dụ class User như sau:

class

User

{

constructor

(

name

)

{

this

.

name

=

name

;

}

sayHi

(

)

{

console

.

log

(

this

.

name

)

;

}

}

let

user

=

new

User

(

"Alex"

)

;

user

.

sayHi

(

)

;

Khi new User("Alex") được gọi:

  • Một đối tượng mới được tạo ra.
  • Hàm khởi tạo constructor được gọi với giá trị tham số truyền vào là "Alex" – gán cho this.name.

Sau đó, bạn có thể gọi phương thức của object, ví dụ: user.sayHi().

📝 Chú ý: Không tồn tại dấu phẩy giữa các phương thức. Việc thêm vào dấu phẩy vào giữa các phương thức sẽ gây lỗi cú pháp.

Bạn cần chú ý để tránh nhầm lẫn giữa việc định nghĩa class với việc định nghĩa object.

Class là gì?

Trong JavaScript, class thực chất là một loại Function. Và bạn có thể xem ví dụ sau để thấy rõ điều đó:

class

User

{

constructor

(

name

)

{

this

.

name

=

name

;

}

sayHi

(

)

{

console

.

log

(

this

.

name

)

;

}

}

console

.

log

(

typeof

User

)

;

Bản chất của class User {...} như sau:

  1. Tạo mới một hàm với tên là User, nội dung của hàm được lấy từ hàm khởi tạo constructor (mặc định là empty nếu bạn không định nghĩa).
  2. Lưu các phương thức của hàm (ví dụ sayHi) trong User.prototype.

Sau khi đối tượng mới được tạo ra và gọi một phương thức, JavaScript sẽ tự động tìm kiếm phương thức đó trong prototype (như đã miêu tả trong bài F.prototype).

Ví dụ chứng minh:

class

User

{

constructor

(

name

)

{

this

.

name

=

name

;

}

sayHi

(

)

{

console

.

log

(

this

.

name

)

;

}

}

console

.

log

(

typeof

User

)

;

console

.

log

(

User

===

User

.

prototype

.

constructor

)

;

console

.

log

(

User

.

prototype

.

sayHi

)

;

console

.

log

(

Object

.

getOwnPropertyNames

(

User

.

prototype

)

)

;

Class không chỉ là “syntactic sugar”

Khái niệm syntactic sugar dùng để chỉ một cú pháp mới được sinh ra nhằm mục đích dễ đọc, dễ viết, chứ không tạo thêm những đặc điểm, tính chất mới so với cú pháp cũ.

Mọi người thường coi class là syntatic sugar của function. Vì thực chất là ta có thể định nghĩa được thứ tương tự class mà không cần từ khóa class như sau:

 

 

function

User

(

name

)

{

this

.

name

=

name

;

}

User

.

prototype

.

sayHi

=

function

(

)

{

console

.

log

(

this

.

name

)

;

}

;

let

user

=

new

User

(

"Alex"

)

;

user

.

sayHi

(

)

;

Bạn có thể thấy cách định nghĩa hàm trên cho kết quả khá giống với cách dùng class. Tuy nhiên, vẫn có một số đặc điểm khác giữa class và hàm như sau:

► Một hàm được tạo bởi từ khóa class luôn có một thuộc tính mặc định là [[IsClassConstructor]]: true. Và JavaScript engine thường dùng thuộc tính này để phân biệt giữa hàm bình thường và class.

Ví dụ class bắt buộc phải gọi với từ khóa new trong khi hàm bình thường thì không:

 

function

User1

(

)

{

}

class

User2

{

constructor

(

)

{

}

}

User1

(

)

;

User2

(

)

;

String biểu diễn class cũng luôn bắt đầu bằng class:

 

function

User1

(

)

{

}

class

User2

{

constructor

(

)

{

}

}

console

.

log

(

User1

)

;

console

.

log

(

User2

)

;

► Các phương thức của class là non-enumerable – tức là không xuất hiện trong for...in. Bởi vì class luôn gán giá trị enumerable : false cho tất cả các phương thức trong prototype.

► Code trong class luôn sử dụng ở strict mode.

Ngoài ra, class còn có nhiều cú pháp và tính năng hay ho khác nữa sẽ được trình bày ở các bài viết sau.

Class expression

Giống như function, class cũng có class expression – biểu thức class. Nghĩa là nó có thể được định nghĩa bên trong một biểu thức khác, truyền giữa các hàm, làm giá trị trả về của hàm hoặc dùng để gán cho biến,…

Sau đây là ví dụ về class expression:

let

User

=

class

{

sayHi

(

)

{

console

.

log

(

"Hello"

)

;

}

}

;

Tương tự như Named Function Expression – NFE, class expression cũng có thể có tên. Và nếu một class expression có tên thì tên đó chỉ được nhìn thấy bên trong class, ví dụ:

let

User

=

class

MyClass

{

sayHi

(

)

{

console

.

log

(

MyClass

)

;

}

}

;

new

User

(

)

.

sayHi

(

)

;

console

.

log

(

MyClass

)

;

Hoặc bạn có thể tạo động class như sau:

function

makeClass

(

message

)

{

return

class

{

sayHi

(

)

{

console

.

log

(

message

)

;

}

}

;

}

let

User

=

makeClass

(

"Hello"

)

;

new

User

(

)

.

sayHi

(

)

;

Getter/setter trong class

Class cũng có getter/setter như trong object. Ví dụ sau sử dụng user.name làm getter/setter:

class

User

{

constructor

(

name

)

{

this

.

name

=

name

;

}

get

name

(

)

{

return

this

.

_name

;

}

set

name

(

value

)

{

if

(

value

.

length

<

4

)

{

alert

(

"Name is too short."

)

;

return

;

}

this

.

_name

=

value

;

}

}

let

user

=

new

User

(

"Alex"

)

;

console

.

log

(

user

.

name

)

;

user

=

new

User

(

""

)

;

Về cơ bản, cách định nghĩa getter/setter trong class như trên cũng giống như định nghĩa getter/setter trong User.prototype.

Tạo tên phương thức qua biểu thức

Tên của phương thức trong class có thể được tạo động thông qua một biểu thức, ví dụ:

class

User

{

[

"say"

+

"Hi"

]

(

)

{

console

.

log

(

"Hello"

)

;

}

}

new

User

(

)

.

sayHi

(

)

;

Tính năng này tương tự như trong object.

Thuộc tính trong class

Trong các phần trên, mình mới đề cập đến phương thức trong class. Thực tế, bạn có thể thêm bất cứ thuộc tính nào vào class như sau:

class

User

{

name

=

"Alex"

;

sayHi

(

)

{

console

.

log

(

`

Hello,

${

this

.

name

}

!

`

)

;

}

}

new

User

(

)

.

sayHi

(

)

;

Chú ý: nhiều trình duyệt cũ không hỗ trợ cách định nghĩa thuộc tính trong class như trên.

Điểm khác nhau quan trọng giữa việc định nghĩa phương thức và thuộc tính trong class là:

  • Phương thức trong class được định nghĩa bên trong prototype.
  • Thuộc tính trong class tồn tại ở mỗi object được tạo ra từ class.

Ví dụ:

class

User

{

name

=

"Alex"

;

}

let

user

=

new

User

(

)

;

console

.

log

(

user

.

name

)

;

Bạn có thể gán giá trị cho thuộc tính thông qua một biểu thức hoặc qua gọi hàm như sau:

class

User

{

name

=

prompt

(

"Name, please?"

,

"Alex"

)

;

}

let

user

=

new

User

(

)

;

alert

(

user

.

name

)

;

Tạo phương thức bind với thuộc tính trong class

Như mình đã đề cập trong bài viết về function binding, hàm trong JavaScript xử lý this một cách rất động.

Vì vậy, khi một object được truyền qua lại các hàm và được gọi ở một ngữ cảnh khác thì this có thể được tham chiếu đến object khác với object ban đầu.

Ví dụ đoạn code sau sẽ hiển thị undefined:

class

Button

{

constructor

(

value

)

{

this

.

value

=

value

;

}

click

(

)

{

console

.

log

(

this

.

value

)

;

}

}

let

button

=

new

Button

(

"hello"

)

;

setTimeout

(

button

.

click

,

1000

)

;

Vấn đề ở đây là khi phương thức button.click được truyền vào hàm setTimeout, phương thức này sẽ được gọi bởi một đối tượng khác, không phải button.

Có ba cách để giải quyết vấn đề này là:

Cách 1: Sử dụng arrow function ở hàm setTimeout như sau:

setTimeout

(

(

)

=>

button

.

click

(

)

,

1000

)

;

Khi đó, đối tượng gọi hàm click vẫn là button. Vì vậy, kết quả hiển thị vẫn chính xác.

Cách 2: Sử dụng arrow function khi định nghĩa hàm click:

class

Button

{

constructor

(

value

)

{

this

.

value

=

value

;

}

click

=

(

)

=>

{

console

.

log

(

this

.

value

)

;

}

;

}

let

button

=

new

Button

(

"hello"

)

;

setTimeout

(

button

.

click

,

1000

)

;

Vì arrow function không có this nên khi hàm click được gọi, this sẽ được lấy ở ngữ cảnh bên ngoài hàm – đó chính là đối tượng button.

Cách 3: bind phương thức click cho đối tượng trong hàm khởi tạo.

class

Button

{

constructor

(

value

)

{

this

.

value

=

value

;

this

.

click

=

this

.

click

.

bind

(

this

)

;

}

click

(

)

{

console

.

log

(

this

.

value

)

;

}

}

let

button

=

new

Button

(

"hello"

)

;

setTimeout

(

button

.

click

,

1000

)

;

Với cách này, giá trị của this bên trong phương thức click luôn là đối tượng button.

Tổng kết

Cú pháp cơ bản của class trong JavaScript như sau:

class

MyClass

{

prop

=

value

;

constructor

(

...

)

{

}

method

(

...

)

{

}

get

something

(

...

)

{

}

set

something

(

...

)

{

}

[

Symbol

.

iterator

]

(

)

{

}

}

MyClass thực chất là một hàm với nội dung của hàm lấy từ constructor và các phương thức, getter/setter được viết trong MyClass.prototype.

Trong các bài viết sau, mình sẽ tìm hiểu nhiều hơn về class, bao gồm tính kế thừa và các tính chất khác của lập trình hướng đối tượng.

Tham khảo: Class basic syntax