JavaScript Study Notes: Type Conversion
1. Fundamentals and Conversion Principles
Changing a value from one type to another in JavaScript is generally called type conversion. Before looking at specific conversions, we need to understand how JavaScript views and handles them underneath.
1. Data Types
Primitive types: Undefined, Null, Boolean, Number, String, and Symbol and BigInt, added in ES6 and later.
Reference type: Object.
// 可以使用 typeof 查看基本类型(注意 null 的历史遗留 bug)
console.log(typeof undefined); // "undefined"
console.log(typeof true); // "boolean"
console.log(typeof 42); // "number"
console.log(typeof "text"); // "string"
console.log(typeof null); // "object" (历史遗留问题)
console.log(typeof {}); // "object"2. The Underlying Conversion Logic
During conversion, JavaScript internally calls several specification operations that aren't directly exposed to developers:
ToNumber(value): convert a value to a number.
ToString(value): convert a value to a string.
ToPrimitive(input, PreferredType): convert an object to a primitive; this one is especially important.
input: the value being processed.
PreferredType: the desired type, Number or String. If omitted, objects default to Number, except Date, which defaults to String.
Steps when Number is preferred:
If the input is already primitive, return it directly.
Call valueOf(); if it returns a primitive, return that value.
Call toString(); if it returns a primitive, return that value.
If neither returns a primitive, throw a TypeError.
(If String is preferred, reverse steps 2 and 3: call toString() first, then valueOf().)
(This is implemented inside the engine, without directly callable JavaScript code. Understanding the steps is enough.)
3. Two Actual Conversion Methods on Objects
Alongside the specification operations, objects expose two real methods for conversion to primitives:
toString(): return a string representing the object.
Ordinary objects call Object.prototype.toString, returning "[object Object]".
Arrays join their elements with commas.
Functions return their source text; Date objects return a human-readable date and time.
console.log({a: 1}.toString()); // "[object Object]"
console.log([1, 2, 3].toString()); // "1,2,3"
console.log([].toString()); // ""
console.log((function(){ let a = 1; }).toString()); // "function(){ let a = 1; }"
console.log(new Date(2023, 0, 1).toString()); // "Sun Jan 01 2023 00:00:00 GMT+0800..."valueOf(): return the object's primitive value.
By default, it returns the object itself; the same applies to arrays, functions, and regular expressions.
Date objects return a timestamp: milliseconds since 1970.
var obj = {a: 1};
console.log(obj.valueOf() === obj); // true (返回对象本身)
var arr = [1, 2];
console.log(arr.valueOf() === arr); // true (返回数组本身)
var date = new Date(2023, 0, 1);
console.log(date.valueOf()); // 1672502400000 (返回时间戳数字)
let numObj = new Number(42);
console.log(typeof numObj); // "object" (它是个复杂对象)
// 当它参与运算时,会调用 valueOf() 吐出真正的基本类型 42
console.log(numObj + 8); // 502. Conversions for Each Data Type
1. Primitives to Booleans
Rule: only six values convert to false in JavaScript; everything else converts to true.
// 转为 false 的情况
console.log(Boolean()); // false (不传参默认 false)
console.log(Boolean(false)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean("")); // false
// 其他全部为 true
console.log(Boolean("0")); // true (非空字符串)
console.log(Boolean(" ")); // true (包含空格的字符串)
console.log(Boolean([])); // true (对象)
console.log(Boolean({})); // true (对象)2. Primitives to Numbers
Number() is strict. A value containing nonnumeric characters will often return NaN directly.
console.log(Number(undefined)); // NaN
console.log(Number(null)); // 0
console.log(Number(true)); // 1
console.log(Number(false)); // 0
// 字符串转数字
console.log(Number("")); // 0 (空串)
console.log(Number(" ")); // 0 (纯空格)
console.log(Number("123")); // 123
console.log(Number("0x11")); // 17 (识别十六进制)
console.log(Number("100a")); // NaN (包含非数字字符)
console.log(Number("1 1")); // NaN(Because Number() is so strict, developers often use parseInt() and parseFloat().)
console.log(parseInt("3 abc")); // 3 (尽力解析前面的数字)
console.log(parseFloat("3.14 a")); // 3.14
console.log(parseInt("a 3")); // NaN (第一个非空格字符就是非数字)3. Primitives to Strings
This is straightforward: essentially, put quotes around the value.
console.log(String(undefined)); // "undefined"
console.log(String(null)); // "null"
console.log(String(true)); // "true"
console.log(String(0)); // "0"
console.log(String(-0)); // "0"
console.log(String(NaN)); // "NaN"
console.log(String(Infinity)); // "Infinity"4. Primitives to Objects
Calling new String(), new Number(), or new Boolean() wraps a primitive in an object.
var num = 1;
var numObj = new Number(num);
console.log(typeof numObj); // "object"
console.log(numObj instanceof Number); // true
// 注意:null 和 undefined 无法包装为对象
// new Object(null) 或 new Object(undefined) 会返回一个空对象 {}
// 但如果在期望对象的属性访问中使用它们,会报错:
// console.log(null.toString()); // TypeError: Cannot read properties of null5. Objects to Booleans
Rule: every object converts to true, including arrays, functions, and even wrapper objects such as new Boolean(false).
console.log(Boolean({})); // true
console.log(Boolean([])); // true
console.log(Boolean(function(){})); // true
// 包装对象永远是 true
var falseObj = new Boolean(false);
console.log(Boolean(falseObj)); // true 6. Objects to Strings
Internally, ToPrimitive(obj, String) first tries toString() for a primitive, then valueOf() if necessary. It finally converts that primitive to a string and returns it.
console.log(String({a: 1})); // "[object Object]"
// 过程:{a: 1}.toString() 直接返回 "[object Object]" (是原始值,结束)
console.log(String([1, 2, 3])); // "1,2,3"
// 过程:[1, 2, 3].toString() 返回 "1,2,3" (是原始值,结束)7. Objects to Numbers
Internally, ToPrimitive(obj, Number) first tries valueOf() for a primitive, then toString() if necessary. It finally converts the primitive to a number and returns it.
console.log(Number({})); // NaN
// 过程:
// 1. valueOf() 返回 {} (非原始值)
// 2. toString() 返回 "[object Object]" (原始值)
// 3. Number("[object Object]") -> NaN
console.log(Number([])); // 0
// 过程:
// 1. valueOf() 返回 [] (非原始值)
// 2. toString() 返回 "" (原始值)
// 3. Number("") -> 0
console.log(Number([1, 2, 3])); // NaN
// 过程:
// 1. valueOf() 返回 [1, 2, 3] (非原始值)
// 2. toString() 返回 "1,2,3" (原始值)
// 3. Number("1,2,3") -> NaN3. Special Cases and Implicit Conversion
1. JSON.stringify
Converting JavaScript values to JSON strings follows its own rules:
// 1. 基本等同于 String(),但 undefined 是例外
console.log(JSON.stringify(42)); // "42"
console.log(JSON.stringify(undefined)); // undefined (并非字符串 "undefined")
// 2. 包装对象会被自动拆包为对应的原始值
console.log(JSON.stringify([new Number(1), new String("false")])); // "[1,"false"]"
// 3. 忽略规则:undefined、函数、Symbol
// 在对象属性中被忽略:
console.log(JSON.stringify({x: undefined, y: function(){}, z: Symbol("")})); // "{}"
// 在数组中转成 null:
console.log(JSON.stringify([undefined, function(){}, Symbol("")])); // "[null,null,null]"
// 4. 自定义 toJSON 方法
var obj = {
foo: 'foo',
toJSON: function () { return 'bar'; }
};
console.log(JSON.stringify(obj)); // '"bar"'2. The Unary + Operator
Effect: equivalent to explicitly calling Number(), triggering the underlying ToNumber operation.
console.log(+'1'); // 1
console.log(+[]); // 0 (ToPrimitive 转为 "",再 ToNumber("") -> 0)
console.log(+['1']); // 1 (ToPrimitive 转为 "1",再 ToNumber("1") -> 1)
console.log(+['1', '2']); // NaN (ToPrimitive 转为 "1,2",ToNumber("1,2") -> NaN)
console.log(+{}); // NaN (ToPrimitive 转为 "[object Object]" -> NaN)3. The Binary + Operator
The core rule: after obtaining both primitive values, concatenate if either is a string; otherwise, perform addition.
// 既无字符串,转数字相加
console.log(null + 1); // 1 (0 + 1)
console.log(true + true); // 2 (1 + 1)
// 包含字符串,执行拼接
console.log([] + []); // "" ("" + "")
console.log([] + {}); // "[object Object]" ("" + "[object Object]")
console.log(1 + "1"); // "11"Pitfall: the Difference Between{} + []and({} + [])
// 在浏览器控制台直接输入(作为语句执行):
> {} + []
0
// 原因:{} 被视为独立的代码块(不参与运算),实际执行的是一元操作符 +[] -> 0
// 加上圆括号(作为表达式执行):
> ({} + [])
"[object Object]"
// 原因:标准二元运算 "[object Object]" + ""4. Equality with ==
When the JavaScript engine evaluates x == y, it runs the abstract equality comparison algorithm. Conversion and comparison follow the order and conditions below. After a conversion, the engine starts these rules again from the beginning with the new values, until it reaches a boolean result.
Underlying rules, in execution priority order:
Same type: use strict equality. If Type(x) equals Type(y), return the result of x === y directly.
Null and Undefined: comparing them returns true. Comparing null or undefined with any other type using == returns false, without implicit numeric or string conversion.
Number and String: convert the string to a number before comparing.
Boolean involved: if either operand is a boolean, convert it to a number first: true becomes 1 and false becomes 0.
Object and primitive (String/Number/Symbol): convert the object to a primitive using the internal operation.
Object and object:
Same memory address: return true.
Different memory addresses: return false, even if every property and value is identical.
Default fallback: if none of these conditions applies, return false.
// 示例 1:空字符串与 0
console.log("" == 0); // true
/* 推导:
1. 触发规则 3(String vs Number),执行 ToNumber(""),结果为 0。
2. 比较转变为:0 == 0。
3. 触发规则 1(类型相同),0 === 0 返回 true。
*/
// 示例 2:布尔值与字符串
console.log("0" == false); // true
/* 推导:
1. 触发规则 4(包含 Boolean),执行 ToNumber(false),结果为 0。
2. 比较转变为:"0" == 0。
3. 触发规则 3(String vs Number),执行 ToNumber("0"),结果为 0。
4. 比较转变为:0 == 0,返回 true。
*/
// 示例 3:数组与布尔值
console.log([] == false); // true
/* 推导:
1. 触发规则 4(包含 Boolean),执行 ToNumber(false),结果为 0。
2. 比较转变为:[] == 0。
3. 触发规则 5(Object vs 原始值),执行 ToPrimitive([])。
- 调用 [].valueOf() 返回 []。
- 调用 [].toString() 返回 ""。
4. 比较转变为:"" == 0。
5. 触发规则 3(String vs Number),执行 ToNumber(""),结果为 0。
6. 比较转变为:0 == 0,返回 true。
*/
// 示例 4:数组之间的反直觉比较
console.log([] == ![]); // true
/* 推导:
1. 优先执行一元操作符 !,![] 结果为 false。
2. 比较转变为:[] == false。
3. 接下来完全重复示例 3 的推导步骤,最终返回 true。
*/
// 示例 5:包含 Null 的比较
console.log(null == 0); // false
console.log(null == ""); // false
/* 推导:
1. 根据规则 2,null 只与 undefined 宽松相等。
2. 不触发任何 ToNumber 转换,直接走到底层规则 6(默认兜底),返回 false。
*/5. Strict Equality with ===
=== follows the strict equality comparison algorithm. Its defining feature is that it never performs type conversion.
Underlying rules:
Different types: reject immediately. If Type(x) differs from Type(y), return false.
Special Number rules: NaN is not equal to itself; positive and negative zero are strictly equal.
Primitives: compare values. Return true if the character sequences (String), boolean states (Boolean), or unique identities (Symbol) match exactly; otherwise return false.
Reference types (Object): compare memory addresses. If both x and y are objects, including arrays and functions, check whether they reference the same object instance in heap memory.
Return true for the same memory address.
Return false for different memory addresses, even with identical contents.
5. A Collection of Classic Pitfalls
Following the rules above, all these expressions evaluate to true:
console.log([] == ![]); // true
/* 推导过程:
1. ! 优先级高,![] 是 false。变成了 [] == false
2. 包含布尔值 false,转数字 0。变成了 [] == 0
3. 对象与基本类型,[] 执行 ToPrimitive 变成 ""。变成了 "" == 0
4. 字符串与数字,"" 转数字 0。变成了 0 == 0 -> true
*/
console.log(false == "0"); // true (false转0, "0"转0 -> 0 == 0)
console.log(false == []); // true (false转0, []转""转0 -> 0 == 0)
console.log("" == 0); // true (""转0 -> 0 == 0)
console.log([] == 0); // true ([]转""转0 -> 0 == 0)