Sean's Blog

An image showing avatar

Hi, I'm Sean

這裡記錄我學習網站開發的筆記
歡迎交流 (ゝ∀・)b

LinkedInGitHub

Understand JavaScript #25 解析 toString() 方法 ft. typeof, instanceof

本文主要內容為探討 JavaScript 中 toString() 方法的相關知識,以及關鍵字 typeof 和 instanceof 的使用。

使用物件的 toString() 檢測各種型別

JavaScript 中有個關鍵字 typeof,顧名思義,它能夠回傳型別。

然而如果要精準地判斷型別,我們應該使用物件的 toString() 而不是 typeof,為什麼呢?

使用 typeof 判斷型別

1var a = 3;
2console.log(typeof a); // number
3
4var b = 'Hello';
5console.log(typeof b); // string
6
7var c = { firstname: 'Damao' };
8console.log(typeof c); // object
9
10var d = [];
11console.log(typeof d); // object
12
13const z = function () {};
14console.log(typeof z); // function
15

我們可以看到關鍵的問題出在 Object 與 Array 身上,關鍵字 typeof 沒辦法判斷出陣列,因為在 JavaScript 中,除了基本型別以外的其他東西都是物件!

使用 toString() 判斷型別

所有的純值(基本型別)除了 null 跟 undefined 之外都有 toString() 方法。

這些 toString() 方法是從 Object.prototype 中繼承下來的,但是很多東西不會是一成不變的,繼承後 JavaScript 重寫了這個方法讓它更加實用。

舉例來說,兒子 Array 繼承自爸爸 Object,繼承時 JavaScript 在 Array.prototype 上重寫了 toString() 方法,所以使用 arr.toString() 時,實際調用的是 Array.prototype.toString()

1// 數字的陣列 [0, 1, 2] 可以直接轉成字串 "0,1,2"
2var arr = [0, 1, 2]; // "0,1,2"
3console.log(arr.toString());
4

再看看另一個兒子「數值」所改寫的 toString() 方法,它甚至可以把數值轉換成不同的進位制。

1var num = 10;
2console.log(num.toString(2)); // 10 進位轉為 2 進位 => 1010
3

至於物件當然也可以使用 toString(),但是結果會顯示成 "[object Object]" 這種樣子。

想要看到 key: value 還是得用 Loop,或是使用 JSON.stringify() 也可以看到完整 Object 的一行字串。

1var a = 3;
2console.log(a.toString()); // "3"
3
4var b = 'Hello';
5console.log(b.toString()); // "Hello"
6
7var c = { firstname: 'Damao' };
8console.log(c.toString()); // "[object Object]"
9console.log(JSON.stringify(c)); // {"firstname":"Damao"}
10

如果是陣列呢?陣列也是一種物件,所以基本上跟物件的結果一樣。

差別在於陣列的 toString() 方法會試著將陣列中的物件轉為字串,此時如果是「空陣列」就會等於陣列中沒有物件,結果就會回傳空字串。

1var d = [];
2console.log(typeof d); // object
3console.log(d.toString()); // "" (空字串)
4
5var d = [{}];
6console.log(d.toString()); // [object Object]
7console.log(JSON.stringify(d)); // "[{}]"
8

然而,到目前為止,我們可以發現陣列和物件使用 toString() 的結果都是 "[object Object]",所以問題還沒有解決,到底要怎麼檢驗區分出 Array 跟 Object 呢?

使用 Object.prototype.toString.call() 判斷型別

答案就是使用物件的原型的 toString() 方法,搭配使用 .call() 來控制 this 指向並且呼叫,就能精準地判斷各種型別囉!

例如:陣列會得到 [object Array],物件則會得到 [object Object],這樣子就能分辨物件與陣列哩。

toString() 與 Object.prototype.toString() 有何不同

Q:什麼是 Object.prototype.toString()

A:內建的函式建構子加上 .prototype 就是指它們的原型,在原型上有內建方法,像是物件的原型上有 toString() 方法。

  • Object、Array、Date、Function → 內建的函式建構子

  • Object.prototype → 預設的原生原型

    1console.log(Object.prototype); // 基本物件
    2console.log(Object.prototype.__proto__); // null
    3
  • Object.prototype.toString() → 內建方法

因為物件的 toString() 方法會返回 [object "type"],其中 type 是指這個物件的類別,所以我們可以發現這個 type 就能拿來區別物件與陣列。

我們讓每個東西都通過 Object.prototype.toString() 檢測,這個方法能判斷所有的型別喔!

1console.log(Object.prototype.toString.call(a)); // [object Number]
2console.log(Object.prototype.toString.call(b)); // [object String]
3console.log(Object.prototype.toString.call(c)); // [object Object]
4console.log(Object.prototype.toString.call(d)); // [object Array]
5console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
6console.log(Object.prototype.toString.call(null)); // [object Null]
7

結論:如果要精確地判斷型別,特別是區分物件與陣列,就要用物件的 toString() 方法。

另一個關鍵字 instanceof

如果物件 e 能在原型鏈上找到物件 Person 就會回傳 true,也就代表 ePerson 的 Instance。

1function Person(name) {
2  this.name = name;
3}
4var e = new Person('Damao');
5console.log(e); // Person {name: "Damao"}
6console.log(typeof e); // object
7console.log(e instanceof Person); // true
8

萬年 bug - typeof null

剛才講 typeof 的時候沒有提到 undefined 與 null。

首先 typeof undefined 得到 undefined 的結果還算合理,因為 undefined 代表沒有東西,所以型別尚未定義是可以接受的結果。

不過 typeof null 得到物件 (object) 這個結果就是 JavaScript 的 bug 了,但是因為這個問題存在很久了,過去建置的網站可能有用到,所以不能修正哩 🤔

1console.log(typeof undefined); // undefined
2console.log(typeof null); // object
3

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

  • 如何使用 typeof
  • 三種判斷型別的方法,以及哪一個能夠最精確地判斷型別
  • JavaScript 中 toString()Object.prototype.toString() 有何不同
  • 如何使用 instanceof
  • JavaScript 萬年 bug - typeof null

References