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); // true
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); // true
Và kể cả các built-in class như Array
:
let arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
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:
- 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ạngClass[Symbol.hasInstance](obj)
. Và kết quả trả về sẽ làtrue
hoặcfalse
. Đó chính là cách để tùy chỉnh hoạt động của toán tửinstanceof
, ví dụ:
// tùy biến Symbol.hasInstance sao cho
// tất cả các object mà canEat=true thì đều thuộc class Animal
class Animal {
static [Symbol.hasInstance](obj) {
if (obj.canEat) {
return true;
}
return false;
}
}
let obj = { canEat: true };
console.log(obj instanceof Animal); // true
// Bởi vì Animal[Symbol.hasInstance](obj) được gọi.
- 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 choobj instanceOf Class
là kiểm tra xemClass.prototype
có bằng một trong các prototype trong chuỗi prototype củaobj
hay không. Nói cách khác, thuật toán để kiểm tra như sau:
// Lần lượt kiểm tra các prototype trong chuỗi
obj.__proto__ === Class.prototype;
obj.__proto__.__proto__ === Class.prototype;
obj.__proto__.__proto__.__proto__ === Class.prototype;
//...
// Nếu bất cứ phép so sánh nào trả về true thì kết quả cuối cùng là true.
// Ngược lại thì kết quả là false.
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); // true
// Kiểm tra: rabbit.__proto__ === Animal.prototype (không đúng)
// Kiểm tra: rabbit.__proto__.__proto__ === Animal.prototype (đúng -> dừng lại)
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();
// thay đổi prototype
Rabbit.prototype = {};
// dẫn đến đối tượng rabbit không thuộc Rabbit nữa
console.log(rabbit instanceof Rabbit); // false
Đô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); // [object Object]
console.log(obj.toString()); // [object Object]
Đó 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õ:
// copy phương thức `toString` sang một biến khác
let objectToString = Object.prototype.toString;
// khai báo 1 biến
let arr = [];
// kiểm tra kiểu dữ liệu của biến
console.log(objectToString.call(arr)); // [object Array]
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)); // [object Number]
console.log(s.call(null)); // [object Null]
console.log(s.call(console.log)); // [object Function]
Symbol.toStringTag
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)); // [object 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]); // Window
console.log(XMLHttpRequest.prototype[Symbol.toStringTag]); // XMLHttpRequest
console.log({}.toString.call(window)); // [object Window]
console.log({}.toString.call(new XMLHttpRequest())); // [object 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"
★ Nếu bạn thấy bài viết này hay thì hãy theo dõi mình trên Facebook để nhận được thông báo khi có bài viết mới nhất nhé:
- Facebook Fanpage: Complete JavaScript
- Facebook Group: Hỏi đáp JavaScript VN
Bình luận