Kiểm tra lớp với toán tử instanceof trong JavaScript

Toán tử instanceof cho phép kiểm tra một đối tượng có thuộc lớp nào đó hay không. Và toán tử này cũng tính đến kế thừa.

Việc kiểm tra kiểu của class là cần thiết trong nhiều trường hợp. Ví dụ, toán tử instanceof có thể được sử dụng để xây dựng một hàm đa hình – một hàm xử lý các đối số khác nhau tùy thuộc vào kiểu của chúng.

Toán tử instanceof

Cú pháp sử dụng toán tử instanceof là:

obj 

instanceof

Class

;

Câu lệnh trên trả về true nếu obj thuộc về Class hoặc một lớp kế thừa từ Class đó, ví dụ:

class

Rabbit

{

}

let

rabbit

=

new

Rabbit

(

)

;

console

.

log

(

rabbit

instanceof

Rabbit

)

;

Toán tử instanceof cũng hoạt động với các hàm khởi tạo như sau:

function

Rabbit

(

)

{

}

console

.

log

(

new

Rabbit

(

)

instanceof

Rabbit

)

;

Và kể cả các built-in class như Array:

let

arr

=

[

1

,

2

,

3

]

;

console

.

log

(

arr

instanceof

Array

)

;

console

.

log

(

arr

instanceof

Object

)

;

Chú ý: arr cũng thuộc về lớp Object. Đó là bởi vì Array kế thừa nguyên mẫu – kế thừa prototype từ Object.

Thông thường, toán tử instanceof dựa trên chuỗi prototype để kiểm tra class. Ngoài ra, bạn cũng có thể tùy chỉnh phương thức static Symbol.hasInstance trong class.

Bởi vì, thuật toán của obj instanceof Class hoạt động theo cách sau:

  1. Nếu class có một phương thức tĩnh là Symbol.hasInstance, thì toán tử instanceof chỉ cần gọi phương thức đó dạng Class[Symbol.hasInstance](obj). Và kết quả trả về sẽ là true hoặc false. Đó chính là cách để tùy chỉnh hoạt động của toán tử instanceof, ví dụ:
 
 

class

Animal

{

static

[

Symbol

.

hasInstance

]

(

obj

)

{

if

(

obj

.

canEat

)

{

return

true

;

}

return

false

;

}

}

let

obj

=

{

canEat

:

true

}

;

console

.

log

(

obj

instanceof

Animal

)

;

  1. Hầu hết các class không có phương thức Symbol.hasInstance. Trong trường hợp đó, logic cơ bản được sử dụng cho obj instanceOf Class là kiểm tra xem Class.prototype có bằng một trong các prototype trong chuỗi prototype của obj hay không. Nói cách khác, thuật toán để kiểm tra như sau:
 
obj

.

__proto__

===

Class

.

prototype

;

obj

.

__proto__

.

__proto__

===

Class

.

prototype

;

obj

.

__proto__

.

__proto__

.

__proto__

===

Class

.

prototype

;

Trong ví dụ trên rabbit.__proto__ === Rabbit.prototype, do đó kết quả là true ngay lập tức.

Với trường hợp kế thừa, quá trình so sánh sẽ dừng lại ở bước thứ hai:

class

Animal

{

}

class

Rabbit

extends

Animal

{

}

let

rabbit

=

new

Rabbit

(

)

;

console

.

log

(

rabbit

instanceof

Animal

)

;

Ngoài toán tử instanceof, cũng có một phương thức objA.isPrototypeOf(objB), trả về true nếu objA ở đâu đó trong chuỗi prototype của objB.

Vì vậy, obj instanceof Class có thể được thay thế bằng câu lệnh Class.prototype.isPrototypeOf(obj).

Chú ý: Bản thân hàm khởi tạo Class không tham gia vào việc kiểm tra.

Chỉ có chuỗi prototype và Class.prototype được sử dụng với toán tử instanceof.

Điều đó có thể dẫn đến những kết quả không mong muốn khi thuộc tính prototype bị thay đổi sau khi đối tượng được tạo ra, như sau:

function

Rabbit

(

)

{

}

let

rabbit

=

new

Rabbit

(

)

;

Rabbit

.

prototype

=

{

}

;

console

.

log

(

rabbit

instanceof

Rabbit

)

;

Đôi điều về Object.prototype.toString

Bạn biết rằng các object thuần (plain object) được chuyển đổi thành string dưới dạng [object Object], ví dụ:

let

obj

=

{

}

;

alert

(

obj

)

;

console

.

log

(

obj

.

toString

(

)

)

;

Đó chính là cách JavaScript triển khai phương thức toString.

Tuy nhiên, có một tính năng ẩn làm cho toString thực sự linh hoạt hơn thế. Bạn có thể sử dụng toString như một cách mở rộng của toán tử typeof để thay thế cho instanceof.

Theo đặc tả của toString, phương thức này có thể được lấy ra từ object và thực thi trong ngữ cảnh (context) của bất kỳ giá trị (kiểu dữ liệu) nào khác. Và kết quả thu được sẽ phụ thuộc vào giá trị đó.

  • Đối với một số, kết quả là [object Number].
  • Đối với boolean, kết quả là [object Boolean]
  • Đối với null là: [object Null].
  • Đối với undefined là: [object Undefined].
  • Đối với mảng là [object Array].
  • …vv (có thể tùy chỉnh).

Bạn có thể xem ví dụ sau để thấy rõ:

 

let

objectToString

=

Object

.

prototype

.

toString

;

let

arr

=

[

]

;

console

.

log

(

objectToString

.

call

(

arr

)

)

;

Cụ thể, mình đã sử dụng hàm call như được mô tả từ bài function binding trong JavaScript. Ở đây, thuật toán toString kiểm tra this và trả về kết quả tương ứng.

Ngoài ra còn các ví dụ khác:

let

s

=

Object

.

prototype

.

toString

;

console

.

log

(

s

.

call

(

123

)

)

;

console

.

log

(

s

.

call

(

null

)

)

;

console

.

log

(

s

.

call

(

console

.

log

)

)

;

Hoạt động của phương thức toString trong object có thể được tùy chỉnh bằng cách sử dụng một symbol đặc biệt là Symbol.toStringTag.

Ví dụ:

let

user

=

{

[

Symbol

.

toStringTag

]

:

"User"

,

}

;

console

.

log

(

{

}

.

toString

.

call

(

user

)

)

;

Hầu hết các đối tượng của môi trường cụ thể (trình duyệt, Node.js,…) đều có một thuộc tính như vậy. Và dưới đây là ví dụ về một số đối tượng trên trình duyệt:

console

.

log

(

window

[

Symbol

.

toStringTag

]

)

;

console

.

log

(

XMLHttpRequest

.

prototype

[

Symbol

.

toStringTag

]

)

;

console

.

log

(

{

}

.

toString

.

call

(

window

)

)

;

console

.

log

(

{

}

.

toString

.

call

(

new

XMLHttpRequest

(

)

)

)

;

Như bạn thấy, kết quả chính xác là Symbol.toStringTag (nếu tồn tại) và có dạng [object...].

Tóm lại, bạn có thể sử dụng {}.toString.call thay vì instanceof cho các đối tượng có sẵn khi muốn lấy kiểu dưới dạng string thay vì chỉ để kiểm tra.

Tổng kết

Dưới đây là tóm tắt các phương pháp kiểm tra kiểu (class):

Áp dụng cho
Kiểu trả về

typeof
Kiểu nguyên thủy
string
{}.toString
Kiểu nguyên thủy, built-in class, đối tượng có Symbol.toStringTag
string
instanceof
Các đối tượng
boolean

Như bạn có thể thấy, {}.toString về cơ bản là nâng cao hơn so với typeof.

Và toán tử instanceof thực sự hiệu quả khi bạn đang làm việc với hệ thống các class có phân cấp và muốn kiểm tra class có tính đến tính kế thừa.

Tham khảo: Class checking: “instanceof”