javaScript : 資料型別整理、測型方法、轉型技巧

 


如果把程式比成一群演員,「資料型別」就是每個角色的性格與台詞。你不先弄清楚,舞台上就會混亂:字串裝成數字、陣列被當物件、NaN 竟然不等於自己、null 與 undefined 一直互相背鍋。

這篇想做你的排練指導:先介紹 JS 的所有型別與各自的「戲路」,再教你用對方法辨識(什麼時候用 typeof、怎麼判斷陣列、日期、正規表達式),接著帶你處理實務中的硬梆梆問題:表單轉型、API 邊界清洗、金額避免浮點誤差、深拷貝與不可變更新、Map/Set 的出場時機。

文末還整理出常見雷點清單,像「== 帶來的隱式轉型」、「parseInt 要記得 radix」、「structuredClone 的適用時機」等。希望你讀完後,能更懂每個值的個性,讓它們在你的程式裡各就各位、各司其職。希望本篇文章能幫助到需要的您。


目錄

{tocify} $title={目錄} 


JavaScript 的資料型別有哪些?

原始型別(Primitive Types)

原始型別是不可變值(immutable)、以值傳遞(pass-by-value),比較快、占記憶體小。


number:含整數與浮點數、特別值 NaN、Infinity、-Infinity

bigint:任意精度整數(以 n 結尾:123n)

string:字串,UTF-16 編碼

boolean:true / false

undefined:未賦值的狀態

null:刻意「空」的狀態(設計歷史造成 typeof null === "object")

symbol:唯一且不可變的識別符(常拿來當物件的非衝突鍵)


ES 規範上還有 undefined 與 null 皆屬於原始型別;symbol、bigint 為較新的原始型別。


物件型別(Object Types)

物件是可變值(mutable)、以參照傳遞(pass-by-reference)。一切複合結構(陣列、日期、正規表達式、Map/Set、函式)本質都屬物件。


Object:萬物之源(平面物件、原型鍊)

Array:有序集合,長度可變

Function:可被呼叫的物件(typeof fn === "function")

Date、RegExp、Map、Set、WeakMap、WeakSet、ArrayBuffer、TypedArray…等


小結:判斷「是不是物件」只能得到一個大方向,要精準判斷(陣列?日期?)需更細的檢查方法。


為何要區分「原始型別」與「物件型別」

1.    賦值/傳遞語意不同

        原始型別:拷貝「值」。

        物件型別:拷貝「參照」。兩個變數指向同一個實體,改 A 也會影響 B。

2.    不可變 vs 可變

        原始型別不可變:str[0] = 'X'不會改變原字串。

        物件可變:obj.a = 1 直接改到原物件。

3.    比較方式不同

        原始型別比的是值(===)。

        物件比的是參照(同個記憶體位址才會 ===)。

4.    效能與記憶體管理

        原始型別通常更輕量;物件需要 GC(垃圾回收)處理生命週期。


常用型別判斷方法(由粗到細)

A.     typeof:快速判斷原始型別與函式

typeof 42             // "number"
typeof 42n            // "bigint"
typeof "hi"           // "string"
typeof true           // "boolean"
typeof undefined      // "undefined"
typeof Symbol("id")   // "symbol"
typeof function () {} // "function"
typeof {}             // "object"
typeof []             // "object"  ← 陣列也是物件
typeof null          // "object"   ← 歷史包袱,雷點之一


適合: 快速判斷原始型別、或確認「是不是函式」。

不適合: 分辨物件子型別(Array/Date/RegExp…)。


B.     Array.isArray(value):辨識陣列的首選

Array.isArray([])          // true
Array.isArray({ length: 1 }) // false


C.     instanceof:判斷「是否為某建構函式實例」

[] instanceof Array      // true
new Date() instanceof Date // true
(() => {}) instanceof Function // true


限制: iframes / 多重 realm 可能失準;以及對原始型別無效(42 instanceof Number 為 false,除非是 new Number(42) 這種「包裝物件」—不建議使用)。


D.     Object.prototype.toString.call(value):最精準的通用解

const tag = Object.prototype.toString.call;
tag([])            // "[object Array]"
tag(new Date())    // "[object Date]"
tag(/re/)          // "[object RegExp]"
tag(null)          // "[object Null]"
tag(undefined)     // "[object Undefined]"
tag(42)            // "[object Number]"
tag(42n)           // "[object BigInt]"


結論: 真要寫「型別工具箱」,這個方法最穩。


E.     快速對照表(常見值 vs 檢查方法)

陣列:Array.isArray(v) ✅

函式:typeof v === "function" ✅

null:v === null ✅

日期:Object.prototype.toString.call(v) === "[object Date]" ✅

NaN:Number.isNaN(v)(而不是 isNaN)✅


注意:

1.    以上的  v 只是「變數(variable)」或「值(value)」的縮寫的意思。

2.    你可以把它換成任何有意義的名字:value、input、data、maybeDate…都行。

3.    表格裡每一條,其實都在示範「拿某個待檢查的值丟進判斷式」,看看它屬不屬於某種型別。


各型別重點、實務示例與易踩坑位

1.    Number(含 NaN / Infinity)

重點

JS 只有一種數字型別(IEEE 754 雙精度浮點),包含整數與小數。

NaN 代表「不是數字的數字結果」,NaN !== NaN。

Number.isNaN 與全域 isNaN 行為不同(後者會先嘗試轉型)。

示例

0.1 + 0.2 === 0.3      // false 浮點誤差
Number.isNaN("foo")    // false(不會強轉)
isNaN("foo")           // true(因為先轉成數字 -> NaN)
Number.isFinite(42)    // true


避雷

金融/金額請用整數(分、厘)+ BigInt 或 decimal library 來避免浮點誤差。

判斷 NaN 請用 Number.isNaN;需要安全有限值判斷用 Number.isFinite。


2.    BigInt

重點

解決大整數精度問題:9007199254740993n 這種超過 Number.MAX_SAFE_INTEGER 的整數。

不可與 Number 直接混算;需要顯式轉型。

示例

10n + 20n     // 30n
Number(10n) + 5 // 15
// 10n + 5     // TypeError: Cannot mix BigInt and other types


避雷

JSON 目前不支援 BigInt,序列化需自訂。


3.    String

重點

不可變。任何「修改」其實都是回傳新字串。

常見需求:安全轉數字、模板字串。

示例

const s = "123";
Number(s)     // 123
parseInt(s, 10) // 123
`${user.firstName} ${user.lastName}`


避雷

parseInt("08") 請務必帶 radix:parseInt("08", 10)。

大量串接建議用陣列 + join 或模板字串,易讀又省錯。


4.    Boolean

重點真值/假值(truthy/falsy)規則要熟:

false, 0, -0, 0n, "", null, undefined, NaN 為 falsy,其餘為 truthy(包含 "0", "false", [], {})。

避雷

想判斷「有沒有內容」時,請區分 "" 與 "0"、[] 與 {} 的語意;別用過度寬鬆的判斷。


5.    Undefined vs Null

重點

undefined:變數宣告了但沒給值、或物件上沒有該屬性。

null:開發者主動賦予的「空」。

示例

let x;
x               // undefined
const obj = { a: null };
obj.b           // undefined
obj.a === null  // true


避雷

讀深層屬性請用 可選鏈 ?. + 空值合併 ??:

const city = user?.address?.city ?? "未填寫";


6.    Symbol

重點

每個 Symbol() 都是唯一的,可當作物件鍵,避免鍵名衝突。

const k = Symbol("id");
const o = { [k]: 123 };
o[k] // 123


避雷

JSON.stringify 會忽略 symbol 鍵;需自訂序列化。


7.    Object / Array / Function / Date / RegExp / Map / Set

重點與示例

// 物件:鍵值對
const user = { id: 1, name: "A" };

// 陣列:有序集合
const arr = [10, 20];
Array.isArray(arr) // true

// 函式:一級公民
function add(a, b) { return a + b; }
const mul = (a, b) => a * b;
typeof add === "function" // true

// 日期
const d = new Date("2025-10-12T00:00:00Z");
Object.prototype.toString.call(d) // "[object Date]"

// 正規表達式
const re = /ab+c/i;
re.test("ABBC") // true

// Map / Set:鍵可非字串、保留插入順序
const m = new Map([["k1", 1]]);
m.get("k1") // 1
const s = new Set([1,2,2]);
s.size // 2


避雷

陣列 vs 物件用途別混:索引型資料用 Array,字典/表用 Object 或 Map。

需要「鍵不是字串」或需要可預期的迭代順序,選 Map。需要唯一集合選 Set。

Date 解析字串容易踩時區與瀏覽器差異;建議統一用 ISO 8601(UTC)或導入日期函式庫。

深拷貝別用 JSON.parse(JSON.stringify(obj)) 濫用,會丟失 Date/Map/Set/undefined/symbol 等;用結構化拷貝 structuredClone(現代瀏覽器/Node 支援)或專門工具。


型別確認與轉換:實務操作流程(步驟+範例)

流程 1:先判斷是否「可呼叫」或「可迭代」

1.    typeof v === "function" → 函式

2.    v != null && typeof v[Symbol.iterator] === "function" → 可迭代(陣列、字串、Map、Set…)


流程 2:原始 vs 物件的第一層判斷

v === null → null

Array.isArray(v) → 陣列

typeof v in ("string","number","bigint","boolean","undefined","symbol","function") → 原始/函式

其餘 → 物件分支,使用 Object.prototype.toString.call(v) 精確判斷


流程 3:安全數字處理

function toNumberSafe(x) {
  const n = Number(x);
  return Number.isFinite(n) ? n : null;
}


解析整數:Number.parseInt(str, 10)

解析浮點:Number.parseFloat(str)

檢查安全整數:Number.isSafeInteger(n)


流程 4:字串化與序列化

通用顯示:String(v)(會呼叫 toString 或 valueOf)

JSON 傳輸:JSON.stringify(obj) / JSON.parse(json)

        自訂序列化:JSON.stringify(obj, replacer, space)


流程 5:深拷貝與不可變更新

// 現代環境
const clone = structuredClone(obj);

// 不可變更新(淺)
const updated = { ...obj, name: "New" };
const arr2 = [...arr, 99];


比較與等值:==、===、Object.is 的正確用法

一律優先 ===(嚴格相等,不做隱式轉型)

== 容易引發隱式轉型陷阱:

0 == ""    // true
0 == "0"   // true
null == undefined // true


Object.is 用於處理 NaN 與 -0/+0 的特殊情況:

Object.is(NaN, NaN) // true
Object.is(0, -0)    // false


實戰場景:常用型別模式

1.    表單輸入 → 安全數字

function parseAmount(input) {
  const n = Number(input.trim());
  if (!Number.isFinite(n) || n < 0) return null;
  return Math.round(n * 100); // 轉成「分」避免小數誤差
}


2.    API 回傳可能缺欄位 → 可選鏈與空值合併

const city = payload?.user?.address?.city ?? "N/A";


3.    快速分類不同型別的處理器

function handle(value) {
  switch (Object.prototype.toString.call(value)) {
    case "[object String]":   return value.trim();
    case "[object Number]":   return Number.isFinite(value) ? value : 0;
    case "[object Array]":    return value.filter(Boolean);
    case "[object Object]":   return { ...value };
    default:                  return value;
  }
}


4.    用 Map 管理快取(鍵可不是字串)

const cache = new Map();
function memo(keyObj, compute) {
  if (cache.has(keyObj)) return cache.get(keyObj);
  const v = compute();
  cache.set(keyObj, v);
  return v;
}


5.    安全判斷「陣列非空」

const isNonEmptyArray = (v) => Array.isArray(v) && v.length > 0;


常見錯誤與雷點

1.    typeof null === "object"

現象:以為 null 是物件。

做法:判斷 null 請用 v === null。


2.    陣列判斷錯誤(用 typeof 得到 "object")

做法:用 Array.isArray(v)。


3.    isNaN 與 Number.isNaN 混用

isNaN("foo") === true(先轉型)

Number.isNaN("foo") === false(不轉型)

做法:判斷 NaN 一律用 Number.isNaN。


4.    浮點數誤差(0.1 + 0.2 !== 0.3)

做法:金額用整數+縮放、或使用 BigInt/十進位套件。


5.    物件比較用 === 以為比內容

({a:1}) === ({a:1}) // false

做法:比內容請序列化或寫深比較函式。


6.    JSON 深拷貝萬能論

會丟失 Date/Map/Set/undefined/symbol/函式。

做法:用 structuredClone 或專門工具。


7.    parseInt 不給 radix

parseInt("08") 在舊環境可能被視為八進位。

做法:parseInt(str, 10)。


8.    BigInt 與 Number 直接混算

會拋錯。

做法:顯式轉換後再計算。


9.    == 帶來隱式轉型坑

做法:預設使用 === 與 !==。


10.    日期字串與時區混亂

做法:統一 ISO 8601 / UTC,或使用可靠日期工具,前端顯示再做本地化。


11.    把函式當一般物件存;或忘了判斷可呼叫性

做法:typeof v === "function" 再呼叫;或用 TypeScript 加上型別註記。


12.    把空陣列/空物件當 falsy

[] 與 {} 在 JS 是 truthy。

做法:判斷長度或鍵數:Array.isArray(v) && v.length > 0、Object.keys(o).length > 0。


從零開始:型別檢查與防呆的「專案預設」做法

1.    建立一個 types.js 小工具庫

// types.js
const tag = (v) => Object.prototype.toString.call(v);

export const isNil     = (v) => v == null; // null or undefined
export const isStr     = (v) => typeof v === "string";
export const isNum     = (v) => typeof v === "number" && Number.isFinite(v);
export const isBigInt  = (v) => typeof v === "bigint";
export const isBool    = (v) => typeof v === "boolean";
export const isSym     = (v) => typeof v === "symbol";
export const isFn      = (v) => typeof v === "function";
export const isArr     = Array.isArray;
export const isDate    = (v) => tag(v) === "[object Date]";
export const isRegExp  = (v) => tag(v) === "[object RegExp]";
export const isObj     = (v) => tag(v) === "[object Object]";
export const isMap     = (v) => tag(v) === "[object Map]";
export const isSet     = (v) => tag(v) === "[object Set]";

// 安全轉型
export const toNumSafe = (x) => {
  const n = Number(x);
  return Number.isFinite(n) ? n : null;
};


2.    表單/接口邊界全面收斂型別

輸入一律字串化 → 再依需求轉型。

API 解析層做一次型別清洗(validation + transform),內部程式不再承擔模糊型別。

3.    ESlint/TypeScript 加強保障(可選)

規則:禁止 ==、禁止 new Number/String/Boolean 包裝物件、禁止未處理的 any。

TypeScript:在編譯期把大部分錯誤擋掉,runtime 再加少量守門人。


問題集

Q1:我要判斷「空字串或只包含空白」怎麼寫?

const isBlank = (s) => typeof s === "string" && s.trim().length === 0;


Q2:怎麼判斷一個值「可被迭代」?

const isIterable = (v) => v != null && typeof v[Symbol.iterator] === "function";


Q3:如何判斷「純物件(Plain Object)」?

const isPlainObject = (v) =>
  Object.prototype.toString.call(v) === "[object Object]" &&
  (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);


Q4:為什麼 typeof new Number(1) === "object"?

因為它是包裝物件。請避免用 new Number/String/Boolean,會帶來混淆與效能負擔。


Q5:如何安全地合併物件?

const merged = { ...a, ...b }; // 淺合併
// 深合併請用專門工具(如 lodash.merge)或自行遞迴


範例集:從壞到好(常見坑位改寫)

範例 A:比較數字(壞)

if (price == "0") { /* ... */ } // 隱式轉型,容易誤判


應改(好)

if (Number(price) === 0) { /* ... */ }




範例 B:判斷陣列(壞)

if (typeof list === "array") { /* ... */ } // 永遠不會是 "array"


應改(好

if (Array.isArray(list)) { /* ... */ }



範例 C:處理 NaN(壞)

if (value !== NaN) { /* ... */ } // 永遠為 true


應改(好)

if (!Number.isNaN(value)) { /* ... */ }



範例 D:深拷貝(壞)

const deep = JSON.parse(JSON.stringify(obj)); // 丟失 Date/Map/Set...


應改(好)

const deep = structuredClone(obj);




範例 E:金額相加(壞)

const sum = 0.1 + 0.2; // 0.30000000000000004

應改(好)

const sumCents = Math.round(0.1 * 100) + Math.round(0.2 * 100);
const sum = sumCents / 100; // 0.3


延伸閱讀推薦:



張貼留言 (0)
較新的 較舊