Skip to Content

面试导航 - 程序员面试题库大全 | 前端后端面试真题 | 面试

Javascript类型转换

Type Conversion

Type conversion typically occurs during the compilation phase in statically-typed languages, whereas in dynamically-typed languages, type coercion happens at runtime. This is why type conversion is ubiquitous in daily development and interviews—we often need to manually perform coercion or rely on the language’s implicit conversion mechanisms.

In JavaScript, type conversion is generally referred to as type coercion. It can be further categorized into implicit coercion and explicit coercion, with a clear distinction between the two. Explicit coercion involves developers intentionally invoking conversion functions, while implicit coercion is less obvious and often arises as a side effect of certain operations.

As shown in the following code:

var a = 77; var b = a + ''; // Implicit coercion var c = String(a); // Explicit type conversion

toString

toString() is a method that every object in JavaScript has, used to convert the object into a string. Different types of data will return different results when calling toString().

Basic Usage

Plain Objects: By default, it returns "[object Object]".

const obj = {}; console.log(obj.toString()); // "[object Object]"

Numbers: Returns the string representation of the number.

const num = 123; console.log(num.toString()); // "123"

Booleans: Returns “true” or “false”.

const bool = true; console.log(bool.toString()); // "true"

Arrays: Converts the array elements into a comma-separated string.

const arr = [1, 2, 3]; console.log(arr.toString()); // "1,2,3"

Functions: Returns the source code string of the function.

const func = () => 'hello'; console.log(func.toString()); // () => "hello"

You can override the toString() method of an object to define your own string representation:

const person = { name: 'Moment', toString: function () { return this.name; }, }; console.log(person.toString()); // "Moment"

When performing string concatenation, JavaScript will automatically call toString():

const obj = { name: 'Moment' }; console.log('Hello, ' + obj); // "Hello, [object Object]"

The toString() method helps convert objects into strings. Different types of data and objects have different conversion methods, and you can customize an object’s string representation by overriding toString().

valueOf()

The valueOf() method is used to convert an object into a primitive value, and it is typically prioritized when performing numeric operations. The toString() method, on the other hand, is used to convert an object into a string and is prioritized during string operations. If toString() returns a string type, valueOf() is generally not invoked because the string conversion is already accomplished by toString().

As shown in the code below:

const obj = { valueOf: () => 42, toString: () => 'Hello', }; // In numeric operations, valueOf() is called first console.log(10 + obj); // Output: 52, because obj.valueOf() is called, returning 42 // During string concatenation, valueOf() is prioritized and its result is converted to a string console.log('Message: ' + obj); // Output: Message: 42, because obj.valueOf() is called, returning 42 // When explicitly converting to a string, toString() is invoked console.log(String(obj)); // Output: Hello, calls obj.toString()

Through this example, we can observe the priority order of valueOf() and toString() during different types of type conversions.

Symbol.toPrimitive

Symbol.toPrimitive is a built-in Symbol property in JavaScript that defines how an object is converted to a primitive value. When an object needs to undergo type coercion, the method specified by this property is prioritized and invoked first.

This method accepts a hint parameter indicating the expected primitive type for conversion. The possible values for hint are:

  1. "number": Convert to a numeric type.
  2. "string": Convert to a string type.
  3. "default": A generic type, typically determined automatically by JavaScript.

This method supports all type coercion algorithms (such as numeric operations, string concatenation, etc.). In these scenarios, JavaScript prioritizes calling Symbol.toPrimitive to ensure the object is correctly converted to the expected primitive value.

The example below demonstrates how the Symbol.toPrimitive property modifies the primitive value derived from an object:

const object = { [Symbol.toPrimitive](type) { if (type === 'number') return 77; if (type === 'string') return 'string priority here'; // When handling the "default" type, return the desired value directly if (type === 'default') return 'default'; }, valueOf() { return 22; }, toString() { return 33; }, }; console.log(String(object)); // "string priority here", type parameter is "string" console.log(Number(object)); // 77, type parameter is "number" console.log(object + ''); // "default", type parameter is "default"

JSON Stringification

The utility function JSON.stringify(...) calls the toString() method when serializing a JSON object into a string. The behavior of JSON.stringify and toString() is largely similar, except that JSON.stringify always returns a string formatted according to JSON standards, while toString() returns a string representation of the object, typically a concise description.

console.log(JSON.stringify(77)); // '77' console.log(JSON.stringify('77')); // '"77"' console.log(JSON.stringify(null)); // 'null' console.log(JSON.stringify(undefined)); // undefined

When encountering undefined, function(), or Symbol values in objects, JSON.stringify(...) automatically ignores them. For arrays, these values are replaced with null, for example:

console.log(JSON.stringify(undefined)); // undefined console.log(JSON.stringify(function () {})); // undefined console.log(JSON.stringify(class C {})); // undefined console.log(JSON.stringify([1, undefined, function () {}, 4])); // [1,null,null,4] console.log(JSON.stringify({ a: 2, b() {} })); // "{"a":2}" console.log(JSON.stringify({ x: undefined, y: Object, z: Symbol('') })); // '{}'

JSON.stringify(...) throws an error when the object contains circular references. A circular reference occurs when a property of an object directly or indirectly references the object itself. Additionally, JSON.stringify() may throw errors in the following cases:

  1. Unserializable values: Properties with values of type undefined, function, or Symbol are ignored. If the object contains a BigInt value, JSON.stringify() throws a TypeError because BigInt cannot be serialized into JSON.

  2. Non-enumerable properties: JSON.stringify() only serializes enumerable properties and ignores non-enumerable ones.

  3. Invalid values returned by toJSON(): If the toJSON() method of an object returns a value containing unserializable data (e.g., undefined or BigInt), serialization will fail.

Example code:

const obj1 = { name: 'John', func: function () { return 'Hello'; }, // Functions are ignored }; const obj2 = { name: 'Jane', value: Symbol('unique'), // Symbols are ignored }; const obj3 = { name: 'Alice', bigIntValue: BigInt(123), // BigInt causes a TypeError }; // No error, but func is ignored console.log(JSON.stringify(obj1)); // Output: {"name":"John"} // No error, but Symbol is ignored console.log(JSON.stringify(obj2)); // Output: {"name":"Jane"} try { console.log(JSON.stringify(obj3)); // Throws TypeError } catch (error) { console.error('Error: BigInt cannot be processed by JSON.stringify()', error); }

If an object defines a toJSON() method, JSON.stringify() prioritizes calling this method and uses its return value for serialization. This allows you to ensure the returned value is safe and JSON-compliant, avoiding errors caused by invalid JSON values.

Example code:

const obj = { name: 'John', age: 30, birthdate: new Date(), // Date objects are not directly serializable toJSON: function () { return { name: this.name, age: this.age, birthdate: this.birthdate.toISOString(), // Convert Date to ISO string }; }, }; const jsonString = JSON.stringify(obj); console.log(jsonString);

The output is shown below:

Replacer

The second parameter of JSON.stringify(), replacer, controls which properties of an object should be serialized. It can be either an array or a function, each serving distinct purposes.

Replacer as an Array

If the replacer is an array of strings, only the properties listed in the array will be included in the JSON result. Other properties are ignored.

Example code:

var a = { foo: 77, bar: 'moment', baz: [1, 2, 3], }; console.log(JSON.stringify(a, ['bar', 'baz'])); // Output: '{"bar":"moment","baz":[1,2,3]}' // Only "bar" and "baz" are included; "foo" is ignored

This specifies which properties to serialize by explicitly listing them.

Replacer as a Function

If the replacer is a function, it is called for each property to decide whether to serialize it. The function accepts two parameters:

  1. key: The name of the property being processed.
  2. value: The value of the property.

If the function returns undefined, the property is ignored. Otherwise, the returned value is used for serialization.

Example code:

var a = { foo: 77, bar: 'moment', baz: [1, 2, 3], }; console.log( JSON.stringify(a, function (key, value) { if (key === 'foo') return undefined; // Ignore "foo" return value; // Serialize other properties }), ); // Output: '{"bar":"moment","baz":[1,2,3]}' // "foo" is ignored; "bar" and "baz" are serialized

This allows dynamic control over property serialization using custom logic.

ToNumber

Sometimes we need to treat non-numeric values as numbers, such as in numeric operations. For this purpose, the ES5 specification defines the abstract operation ToNumber, with the basic syntax ToNumber(argument). When invoked, it performs the following steps:

  1. If argument is of the number type, return argument directly.
  2. If argument is a Symbol or BigInt, throw a TypeError.
  3. If argument is undefined, return NaN.
  4. If argument is null or false, return +0.
  5. If argument is true, return 1.
  6. If argument is a string type, invoke the StringToNumber(argument) method. If the string contains non-numeric characters (e.g., "1,2"), the Number function converts it to NaN.

If the input is an object (including arrays), it is first converted to a primitive value. If the resulting value is not a number, it is further coerced to a number following Rule 6.

To convert an object to a primitive value, the abstract operation ToPrimitive invokes the internal method [[DefaultValue]](hint):

  • If the hint parameter is "number":
    • Check if the object has a valueOf() method.
      • If valueOf() exists, call it and return the result.
      • If the result is a primitive value, use it for type coercion.
      • If valueOf() does not exist or does not return a primitive value, call toString().
  • The toString() method follows the same rules as valueOf(). If it returns a primitive value, type coercion proceeds.

If neither valueOf() nor toString() returns a primitive value, a TypeError is thrown.

Starting in ES5, objects created via Object.create(null) have a [[Prototype]] of null and thus lack valueOf() and toString() methods, making them impossible to coerce into primitive values.

var a = { valueOf: function () { return '66'; }, toString() { return '77'; }, }; var b = { toString() { return '77'; }, }; var c = [2, 3]; c.toString = function () { return this.join(''); }; console.log(Number(a)); // 66 (valueOf() is prioritized) console.log(Number(b)); // 77 (toString() is called) console.log(Number(c)); // 23 (custom toString()) console.log(Number('')); // 0 (empty string → 0) console.log(Number([])); // 0 ([] → "" → 0) console.log(Number([1, 2])); // NaN ("1,2" is not a valid number) console.log(Number(['a'])); // NaN ("a" is not a number) console.log(Number('12ab')); // NaN (invalid numeric string)

In the example above:

  • Numeric coercion prioritizes valueOf(), followed by toString().
  • For array c, the toString() method is overridden to return "23", which is then coerced to 23.
  • Arrays are converted by calling toString():
    • [] becomes "" (coerced to 0).
    • [2,3] becomes "2,3" (invalid number → NaN).
    • Non-numeric strings (e.g., "12ab") also result in NaN.

ToBoolean

In JavaScript, the keywords true and false represent the boolean values for truth and falsity. The ECMAScript (ES) specification defines the abstract operation ToBoolean, which takes an argument argument and follows these rules:

  1. If argument is a Completion Record and is an abrupt completion, return argument directly. Otherwise, return ToBoolean(argument.[[value]]).
  2. If argument is undefined, return false.
  3. If argument is null, return false.
  4. If argument is a Boolean, return argument directly.
  5. If argument is a Number and is +0, -0, or NaN, return false; otherwise, return true.
  6. If argument is a String and is empty (length 0), return false; otherwise, return true.
  7. If argument is a Symbol, return true.
  8. If argument is an Object (including [], {}, function(){}), return true (but see Rule 9).
  9. If argument is an Object with the [[IsHTMLDDA]] internal slot (e.g., document.all), return false.
console.log(Boolean(undefined)); // false console.log(Boolean(null)); // false console.log(Boolean(true)); // true console.log(Boolean(+0)); // false console.log(Boolean(-0)); // false console.log(Boolean(NaN)); // false console.log(Boolean(777)); // true console.log(Boolean('')); // false console.log(Boolean('1')); // true console.log(Boolean('""')); // true (non-empty string) console.log(Boolean(Symbol())); // true console.log(Boolean(function () {})); // true console.log(Boolean({})); // true console.log(Boolean([])); // true console.log(Boolean(document.all)); // false (exception case)

Completion Record

A Completion Record is a special type of Record used to describe the outcome of executing a specific step in a process. For example, when executing var a = 2; in the console, the statement completes successfully but returns undefined. Ordinary statements produce a normal Completion Record (indicating execution should proceed to the next step), while expression statements return a Completion Record with a [[value]] field representing the computed result. For var declarations, the Completion Record’s [[value]] is empty.

In the ES specification, every step or statement execution explicitly or implicitly returns a Completion Record with three fields:

  1. [[type]]: Indicates the type of Completion Record. Possible values:

    • normal: Execution completed normally.
    • return: Triggered by a return statement.
    • throw: Triggered by a thrown exception.
    • break/continue: Control flow jumps (like goto).
  2. [[value]]:

    • For normal, return, or throw types: Holds the resulting value or exception.
    • For break/continue: empty.
  3. [[target]]:

    • For break/continue: Specifies the target label for control flow.

A Completion Record with [[type]] as normal is called a normal completion; other types are abrupt completions.

Explicit Type Coercion

Explicit type coercion refers to intentional and obvious type conversions. Many type conversions fall into this category.

Explicit Conversion Between Strings and Numbers

Conversions between strings and numbers are achieved using the built-in functions String(...) and Number(...), which do not use the new keyword and thus do not create wrapper objects. For example:

var a = 77; console.log(String(a)); // "77" var b = '3.14'; console.log(Number(b)); // 3.14

In addition to String(...) and Number(...), other methods can perform explicit conversions between strings and numbers:

var a = 77; console.log(a.toString()); // "77" var b = '3.14'; console.log(+b); // 3.14

While a.toString() is explicit, it involves implicit conversion. Since primitives like 77 do not have a toString() method, JavaScript automatically creates a wrapper object (e.g., new String(a)) and calls toString() on it. This explicit conversion includes an implicit step. The code a.toString() effectively executes:

var a = 77; var aa = new String(a); // Implicit wrapper object creation console.log(aa.toString()); // "77"

In the example +b, the unary + operator (with a single operand) explicitly converts b to a number, rather than performing addition or string concatenation.

Other examples of unary operators:

var a = '3.14'; var b = 3.14 + +a; // The second `+` converts "3.14" to 3.14 console.log(b); // 6.28 console.log(1 + -+(+(+-+1))); // 2 (demonstrating nested unary operators)

Let’s examine more cases:

console.log(+[]); // 0 ([] → "" → 0) console.log(+['1']); // 1 (["1"] → "1" → 1) console.log(+['1', '2', '3']); // NaN ("1,2,3" is invalid) console.log(+{}); // NaN ({} → "[object Object]" → NaN)

For +[], JavaScript first attempts valueOf(), but since arrays lack a meaningful valueOf(), it calls toString(), which returns "". Converting "" to a number yields 0. Similarly, ["1"] becomes "1", which converts to 1, while ["1", "2"] becomes "1,2", resulting in NaN. Objects like {} return "[object Object]", which converts to NaN.

Explicit Parsing of Numeric Strings

Parsing a numeric string (e.g., parseInt) and converting it to a number (e.g., Number) both produce numbers, but they behave differently:

var a = '77'; var b = '77px'; console.log(Number(a)); // 77 console.log(parseInt(a)); // 77 console.log(Number(b)); // NaN (non-numeric characters) console.log(parseInt(b)); // 77 (parses until non-numeric character)

parseInt parses left-to-right and stops at the first invalid character, while Number requires the entire string to be numeric.

A tricky example:

console.log(parseInt(1 / 0, 19)); // 18

Explanation: 1 / 0 evaluates to Infinity. parseInt("Infinity", 19) parses the first character "I", which represents 18 in base 19 (digits 0-9 and letters a-i). The second character "n" is invalid in base 19, so parsing stops at "I", returning 18.

Other examples of parseInt quirks:

console.log(parseInt(0.000008)); // 0 ("0.000008" parsed as 0) console.log(parseInt(0.0000008)); // 8 ("8e-7" parsed as 8) console.log(parseInt(false, 16)); // 250 ("fa" from "false") console.log(parseInt(parseInt, 16)); // 15 ("f" from "function") console.log(parseInt('0x10')); // 16 (hexadecimal notation) console.log(parseInt('103', 2)); // 2 (binary, "10" = 2, "3" invalid)

When parseInt receives an object, it implicitly calls the object’s toString() method and parses the result:

var obj = { a: 1, toString() { return this.a; }, // Used by parseInt valueOf() { return 2; }, toJSON() { return 3; }, }; console.log(parseInt(obj)); // 1 (from toString())

Explicit Conversion to Boolean

The Boolean(...) function performs explicit ToBoolean coercion. While explicit, it is rarely used directly. Instead, the double negation !! is a common pattern to coerce a value to a boolean:

var a = '0'; var b = []; var c = {}; var d = ''; var e = 0; var f = null; var g; console.log(!!a); // true (non-empty string) console.log(!!b); // true (object) console.log(!!c); // true (object) console.log(!!d); // false (empty string) console.log(!!e); // false (0) console.log(!!f); // false (null) console.log(!!g); // false (undefined)

The first ! converts the value to a boolean and inverts it, while the second ! inverts it back to the original boolean equivalent.

Implicit Type Coercion

Implicit type coercion refers to hidden type conversions with non-obvious side effects. In other words, any coercion that is not explicitly clear to the developer can be considered implicit. While explicit coercion aims to improve code clarity, implicit coercion often makes code harder to understand.

Implicit Coercion Between Strings and Numbers

The + operator is overloaded to handle both numeric addition and string concatenation. How does JavaScript determine which operation to perform? For example:

var a = '77'; var b = '0'; var c = 77; var d = 0; console.log(a + b); // "770" (string concatenation) console.log(c + d); // 77 (numeric addition)

Why do we get different results? A common assumption is that + performs string concatenation if either operand is a string. However, the reality is more nuanced. Consider:

var a = [1, 2]; var b = [3, 4]; console.log(a + b); // "1,23,4" (both arrays coerced to strings)

Neither a nor b are strings, but they are implicitly coerced to strings and concatenated. Why?

Based on the ES5 specification, the rules for + are as follows:

  1. Evaluate the left operand (AdditiveExpression) and assign it to lref.
  2. Get the value of lref using GetValue(lref), assigned to lval.
  3. Check if lval is an abrupt completion (e.g., error). If so, return it.
  4. Repeat steps 1–3 for the right operand (rval).
  5. Convert lval to a primitive (lprim) using ToPrimitive(lval).
  6. Convert rval to a primitive (rprim) using ToPrimitive(rval).
  7. If either lprim or rprim is a string, coerce both to strings and concatenate.
  8. Otherwise, convert both to numbers (lnum and rnum) and perform numeric addition.

Additional rules for numeric addition:

  • NaN + anyNaN
  • Infinity + InfinityInfinity
  • -Infinity + InfinityNaN
  • Infinity + finiteInfinity
  • -0 + 00
  • -0 + -0-0
  • 0 + non-zero → non-zero value

Examples:

console.log(null + 1); // 1 (Number(null) → 0) console.log(undefined + 1); // NaN (Number(undefined) → NaN) console.log(NaN + 1); // NaN console.log(-Infinity + Infinity); // NaN console.log(Infinity + Infinity); // Infinity console.log(-0 + 0); // 0

In the array example, since arrays lack valueOf(), toString() is called, converting [1,2] to "1,2" and [3,4] to "3,4", resulting in "1,23,4".

Object Coercion in + Operations

Objects are coerced to primitives using ToPrimitive, which prioritizes valueOf() over toString():

var obj = { valueOf() { return 7; }, toString() { return '7'; }, }; var bar = { toString() { return '7'; }, }; console.log(obj + obj); // 14 (valueOf() used) console.log([1] + obj); // "17" ([1] → "1", obj → 7 via valueOf()) console.log(obj + bar); // "77" (obj → 7, bar → "7")

Edge Cases with {} and []

console.log([] + {}); // "[object Object]" ([] → "", {} → "[object Object]") console.log({} + []); // 0 ({} parsed as empty block; +[] → 0)

The second case ({} + []) behaves differently because {} is interpreted as an empty code block in some contexts, leaving +[] (which coerces to 0). Wrapping in parentheses forces object interpretation:

console.log({} + []); // "[object Object]"

The - Operator

Unlike +, the - operator always coerces operands to numbers:

console.log([1, 3] - 2); // NaN ("1,3" → NaN) console.log(null - 1); // -1 (null → 0) console.log(undefined - 1); // NaN (undefined → NaN) console.log(77 - '7'); // 70 ("7" → 7) console.log([3] - [1]); // 2 ([3] → 3, [1] → 1) console.log(new Date() - 1); // Timestamp - 1 (Date → number via valueOf())

Key Takeaways

  • + prioritizes string concatenation if either operand is a string after ToPrimitive.
  • Objects without valueOf() fall back to toString().
  • - always performs numeric coercion, ignoring string/object semantics.
  • Edge cases like {} + [] depend on parsing context (code block vs. expression).

References

Summary

JavaScript’s type conversion mechanisms include implicit and explicit conversions.

  • Implicit conversion is automatically performed by JavaScript, often triggered by operators. For example, the + operator may perform string concatenation or numeric addition based on operand types.
  • Explicit conversion involves manual coercion using built-in functions like String(), Number(), etc.
  • The toString() and valueOf() methods are key for object-to-primitive conversion, while Symbol.toPrimitive allows customizing conversion logic.

Overall, JavaScript’s type coercion is flexible yet complex, significantly impacting code behavior and outcomes.

Last updated on:
Copyright © 2025Moment版权所有粤ICP备2025376666