基础篇:类型转换

类型转换(Type Conversion)是指将数据由一种类型变换为另一种类型。在 JavaScript 中,在一些运算类型与预期不符的情况下,会发生隐式转换,也可以用一些写法强制要求进行显式转换。

JavaScript 中有 7 种简单数据类型(也称为基本数据类型):数字(Number)、字符串(String)、布尔值(Boolean)、 Undefined、Null,还有 ES6 新增的 Symbole,以及处于第 4 阶段的 BigInt。此外,还有 1 种复杂数据类型(也称引用类型)—对象(Object)。

强制类型转换

强制类型转换主要是使用 Number()、String()、Boolean() 三个函数,手动将各种类型的值,分别转换成数字、字符串、布尔值。

Number()

Number 对象是经过封装的处理数值的对象,主要有两个作用:一是作为构造函数,创建数字对象;二是用来将任意类型的值转换为数值。

原始类型值
  • 数值:返回原值。如果是极小和极大的数字,则会返回指数形式。
  • 字符串:可以被解析,则转换为相应的数值;不可以被解析为数值,返回 NaN;空字符串转为 0。
  • 布尔值:true 转成 1;false 转成 0。
  • undefined:转成为 NaN。
  • null:转成为 0。
  • Symbol:不能转换为数字,会报错。
  • BigInt:转换为数值,末尾 n 会丢失。如果是极小和极大的数字,会丢失精度。
Number(324) // 324

Number('324')    // 324
Number('324abc') // NaN
Number('')       // 0

Number(true)  // 1
Number(false) // 0

Number(undefined) // NaN
Number(null)      // 0

Number(Symbol('a')) // Uncaught TypeError: Cannot convert a Symbol value to a number

Number() 函数将字符串转为数值,要比 parseInt() 函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为 NaN。

另外,parseInt() 和 Number() 函数都会自动过滤一个字符串前导和后缀的空格。

对象

简单的规则是,Number() 函数的参数是对象时,将返回 NaN,除非是空数组或者只有一个数值的数组。

Number({})         // NaN
Number({a: 1})     // NaN
Number([1, 2, 3])  // NaN
Number([])         // 0
Number([5])        // 5
Number(new Date()) // 1653321770387

具体规则:

  • 调用对象自身的 valueOf() 方法。如果返回原始类型的值,则使用 Number() 函数转换该值。
  • 如果 valueOf() 方法返回的还是对象,则改为调用对象自身的 toString() 方法;如果 toString() 方法返回原始类型的值,则使用 Number() 函数转换该值。
  • 如果 toString() 方法返回的是对象,则报错。
const obj = {}
obj.valueOf()     // {}
obj.toString()    // '[object Object]'
Number('[object Object]') // NaN

另外,一个对象的 valueOf() 和 toString() 方法,是可以自定义的。

const obj = {
    valueOf: function () { return {}; },
    toString: function () { return {}; }
};
obj.valueOf()  // {}
obj.toString() // {}
Number(obj)    // Uncaught TypeError: Cannot convert object to primitive value

String()

String 是一个用于字符串或一个字符序列的构造函数,另外,还可以作普通函数使用,可以将任意类型的值转化成字符串。

原始类型值
  • 数值:转换为相应的字符串。
  • 字符串:返回原值。
  • 布尔值:true 转换为字符串 "true";false 转换为字符串 "false"。
  • undefined:转换为字符串 "undefined"。
  • null:转换为字符串 "null"。
  • Symbol:可以显式强制转换为字符串,然而隐式强制类型转换会产生错误。
  • BigInt:转换为数值,末尾 n 会丢失。
String(123)         // '123'
String('abc')       // 'abc'
String(true)        // 'true'
String(undefined)   // 'undefined'
String(null)        // 'null'
String(Symbol('a')) // 'Symbol(a)'
'a' + Symbol('a')   // Uncaught TypeError: Cannot convert a Symbol value to a string
String(BigInt(1))   // '1'
对象

String 函数的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。

String({a: 1})     // '[object Object]'
String({})         // '[object Object]'
String([1, 2, 3])  // '1,2,3'
String([])         // ''
String(new Date()) // 'Tue May 24 2022 00:03:00 GMT+0800 (中国标准时间)'

具体转换规则:

  • 先调用对象自身的 toString() 方法。如果返回原始类型的值,则使用 String() 函数转换该值。

  • 如果 toString() 方法返回的是对象,再调用原对象的 valueOf() 方法。如果 valueOf() 方法返回原始类型的值,则使用 String() 函数转换该值。

  • 如果 valueOf() 方法返回的是对象,则报错。

Boolean()

Boolean 对象是一个布尔值的对象包装器,也可以用作普通函数,将任意类型的值转为布尔值。

如果是布尔值,则原值返回,仅以下五个值的转换为 false,其他的值全部为 true:

  • undefined
  • null
  • 0(包含 -0、+0、BigInt(0))
  • NaN
  • ''
Boolean(undefined) // false
Boolean(null)      // false
Boolean(0)         // false
Boolean(NaN)       // false
Boolean('')        // false

注意: 所有对象(包括空对象、Symbol 值)的转换都是 true,甚至连 false 对应的布尔对象 new Boolean(false) 也是。

Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
Boolean(Symbol('a'))        // true

注意: 有一个特殊的对象,它转换成的布尔值是 false。

document.all // HTMLAllCollection(9) [html, head, meta, meta, title, body, div, script, script, viewport: meta]
Boolean(document.all) // false

隐式类型转换

隐式类型转换是以强制类型转换为基础的。

以下三种情况时,JavaScript 会自动转换数据类型:

  • 不同类型的数据互相运算。

  • 对非布尔值类型的数据求布尔值。

  • 对非数值类型的值使用一元运算符(即 +、-)。

自动转换的规则:预期什么类型的值,就调用该类型的转换函数。如果该位置既可以是字符串,也可能是数值,那么默认转为数值。

比如,'' + a,调用 String(a) 转换字符串;+ a,调用 Number(a) 转换为数值。

由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用 Boolean()、Number() 和 String() 函数进行强制转换。

自动转换为布尔值

以下情况,JavaScript 会自动调用 Boolean() 函数:

  • if 语句的条件部分
  • for (;;) 语句中的条件判断表达式(第二个)
  • while () 和 do...while() 循环中的条件判断表达式
  • 三元表达式(?:)
  • 逻辑否运算符(&&、||、!)

自动转换为字符串

以下情况,JavaScript 会自动调用 String() 函数:

字符串的加号运算符(+):当两个操作数,有一个是字符串,另一个为非字符串,则后者转为字符串。

'1' + 1               // '11'
'1' + true            // '1true'
'1' + false           // '1false'
'1' + {}              // '1[object Object]'
'1' + []              // '1'
'1' + function () { } // '1function () { }'
'1' + undefined       // '1undefined'
'1' + null            // '1null'

自动转换为数值

以下情况,JavaScript 会自动调用 Number() 函数:

除了加号运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。

'5' - '2'     // 3
'5' * '2'     // 10
true - 1      // 0
false - 1     // -1
'1' - 1       // 0
'5' * []      // 0
false / '5'   // 0
'abc' - 1     // NaN
null + 1      // 1
undefined + 1 // NaN

对象转基本类型

对象转基本类型有三种变体,发生在各种情况下,这些情况被称为 hint(暗示、示意)。ECMAScript 规范定义:当一个对象被用在需要原始值的上下文中时,例如,在比较 alert 或数学运算中,对象会被转换为原始值。

Hint

  • string: 对象到字符串的转换。当对期望一个字符串的对象执行操作,如,alert 函数中的参数。
  • number:对象到数字的转换。如,进行数学运算时。
  • default:在少数情况下发生,当运算符 “不确定” 期望值的类型时,如二元加法 +、不严格相等 ==、比较运算符 <、>。

为了实现转换,JavaScript 会遵循以下规则顺序:

  • 如果 Symbol.toPrimitive 方法存在,则它会被用于所有 hint,无需更多其他方法。
  • 否则,如果 hint 是 "string" —— 顺序尝试对象的 toString() 和 valueOf(),无论哪个存在。
  • 否则,如果 hint 是 "number""default" —— 顺序尝试对象的 valueOf() 和 toString(),无论哪个存在。

Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

const user = {
    name: "John",
    money: 1000,

    [Symbol.toPrimitive](hint) {
        console.log(`hint: ${hint}`);
        return hint == "string" ? `{name: "${this.name}"}` : this.money;
    }
};
alert(user); // hint: string
(+user);      // hint: number
(user + 500); // hint: default

唯一强制性的事情是:这些方法必须返回一个原始值,而不是对象。

toString()和valueof()

toString() 方法返回一个表示该对象的字符串,valueOf() 方法返回指定对象的原始值。

如果没有 Symbol.toPrimitive 方法,那么 JavaScript 将尝试寻找 toString() 和 valueOf() 方法:

  • 对于 “string” hint:如果 toString() 存在,优先调用 toString();否则调用 valueOf()。
  • 对于其他 hint:如果 valueOf() 存在,优先调用 valueOf();否则调用则 toStrin()。

这些方法必须返回一个原始值。如果 toString() 或 valueOf() 返回了一个对象,那么返回值会被忽略(和没有方法时相同)。

默认情况下,普通对象具有 toString() 和 valueOf() 方法:

  • toString() 方法返回一个字符串 "[object Object]"。
  • valueOf() 方法返回对象自身。

一般地,toString() 作为 “全能” 的方法来处理转换。

ES6 的四种相等性判断

相等(==)

相等运算符(==)检查其两个操作数是否相等,并返回布尔值。

相等运算符(==)会尝试强制类型转换并且比较不同类型的操作数,转换规则如下:

  • 如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回 true。

  • 如果一个操作数是 null,另一个操作数是 undefined,则返回 true。

  • 一个操作数是 undefined 或者 null,如果另一个操作数是 Number、String、Boolean ,则返回 false;如果另一个操作数是对象,则返回 !Boolean(object)(特殊对象 document.all,查看下方示例)。

  • 如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:

    • 当数字与字符串进行比较时,会尝试将字符串转换为数字值。
    • 如果操作数之一是 Boolean,则 true 转换为1、false 转换为 0。
    • 如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的 valueOf() 和 toString() 方法将对象转换为原始值。
  • 如果操作数具有相同的类型,则将它们进行如下比较:

    • String: 仅当两个操作数具有相同顺序的相同字符时才返回 true。
    • Number:仅当两个操作数具有相同的值时才返回 true。+0 和 -0 视为相同的值,NaN 和任何值(包括 NaN 本身)返回 false。
    • Boolean:仅当操作数为两个 true 或两个 false 时才返回 true。

以下表格为不同类型一一对应的转换规则:

备注: 表格纵向为被比较值 A,横向为被比较值 B;ToNum(A) 表示将 A 转换为数字;ToPrim(A) 表示调用 A.toString() 和 A.valueOf() 方法,将 A 转换为原始值;IsFalsy(A) 表示 A 是否为假值。

undefined null Number String Boolean Object
undefined true true false false false IsFalsy(B)
null true true false false false IsFalsy(B)
Number false false A===B A === ToNum(B) A === ToNum(B) A== ToPrim(B)
String false false ToNum(A) === B A===B ToNum(A) === ToNum(B) ToPrim(B) == A
Boolean false false ToNum(A) === B ToNum(A) === ToNum(B) A===B ToNum(A) == ToPrim(B)
Object IsFalsy(A) IsFalsy(A) ToPrim(A) == B ToPrim(A) == B ToPrim(A) == ToNumB A===B

一些特殊的比较:

document.all
// HTMLAllCollection(10) [html, head, meta, meta, title, style, body, div, a, script, viewport: meta]
console.log(document.all == undefined) // true
console.log(document.all == null)      // true

全等运算符(===)

全等运算符(===)会检查它的两个操作数是否相等,并且返回一个布尔值结果。与相等运算符不同,全等运算符总是认为不同类型的操作数是不同的,也就是说,不会隐藏转换类型。

全等运算符(===)比较规则:

  • 如果操作数的类型不同,则返回 false。
  • 如果两个操作数都是对象,只有当它们指向同一个对象时才返回 true。
  • 如果两个操作数都为 null,或者两个操作数都为 undefined,返回 true。
  • 如果两个操作数有任意一个为 NaN,返回 false。
  • 否则,比较两个操作数的值:
    • 数字类型必须拥有相同的数值。+0 和 -0 会被认为是相同的值。
    • 字符串类型必须拥有相同顺序的相同字符。
    • 布尔运算符必须同时为 true 或同时为 false。

同值相等

确定两个值是否在任何情况下功能上是相同的,可以通过 Object.is() 方法来判断。

与严格相等(===)只有两处不同:

  • +0 不等于 -0。
  • NaN 等于自身。
Object.is(+0, -0)   // false
Object.is(NaN, NaN) // true

零值相等

与同值相等类似,不过会认为 +0 与 -0 相等。

+0 === -0 // true

零值相等使用场景:%TypedArray% 和 ArrayBuffer 构造函数、以及 Map、Set 操作, 并将用于 ES2016/ES7 中的 String.prototype.includes 方法。

const map = new Map();
const obj = { a: 'a' }
map.set(+0, 1)
map.get(+0) // 1
map.get(-0) // 1

一些例子

![]     // false
+[]     // 0
+![]    // 0
[] + [] // '
{} + {} // '[object Object][object Object]'
{} + [] // 0
{a:0} + 1 // 1
[] + {}   // '[object Object]'
[] + ![]  // 'false'
{} + []   // 0
![] + []  // 'false'
'' + {}   // '[object Object]'
{} + ''   // 0
[]['map'] + [] // 'function map() { }'
[]['a'] + []   // 'undefined'
[][[]] + []    // 'undefined'
+!![] + []     // '1'
+!![]    // 1
1 - {}   // NaN
1 - []   // 1
true - 1 // 0
{} - 1   // -1
[]==![]  // true

+((+![])+(+!![])+[]+(+![])) // 10
(![]+[])[+[]]   // 'f'。'false'[0]
(![]+[])[+!![]] // 'a'。'false'[1]

相关问题

(a==1 && a==2 && a==3) 能不能为 true?

事实上是可以的。

因为在 == 比较的情况下,会进行类型的隐式转换。如果参数是对象,先调用 valueOf() 方法,再调用 toString() 方法。因此,只要改变原生的 valueOf() 方法或者 toString() 方法就可以达到效果。

const a = {
    num: 0,
    valueOf: function () {
        return this.num += 1
    }
};
console.log(a == 1 && a == 2 && a == 3) // true
let num = 0;
Function.prototype.toString = function () {
    return ++num;
}
function a() { }
console.log(a == 1 && a == 2 && a == 3) // true
const a = {
    [Symbol.toPrimitive]: (function (i) {
        return function () { return ++i }
    })(0)
};
console.log(a == 1 && a == 2 && a == 3) // true

完善Cash打印三个101

要求只能在 class 里面增加代码:

class Cash {}
const a = new Cash(1)
const b = new Cash(100)
console.log(`${a.add(b)},${Cash.add(a,b)},${new Cash(a+b)}`) // '101,101,101'

首先,三个输出结果是以隐式转换的形式出现的,这是关键之处。 a 和 b 都是 new 出来的对象,由 new Cash(a+b) 可以看出构造函数传入的也是两个 Cash 的实例对象。那么 new 出来的结果肯定不是简简单单的一个对象,不然就是被转换成字符串 '[object Object]',但是又不得不以对象类型出现,那就只能魔改隐式转换用到的toString() 和 valueOf() 方法。

class Cash {
    constructor(a) {
        this.m = a
        this.valueOf = function () { return a }
    }
    add(n) { return this.m + n }
    toString() { return this.m }
    static add(v1, v2) { return v1 + v2 }
}
const a = new Cash(1)
const b = new Cash(100)
console.log(`${a.add(b)},${Cash.add(a, b)},${new Cash(a + b)}`) // '101,101,101'

参考资料

阮一峰 - 数据类型的转换

玩转js类型转换

对象 — 原始值转换

MDN - JavaScript 中的相等性判断

MDN - 相等(==)

© lizhao all right reserved,powered by Gitbook文件修订时间: 2022-07-20 22:41:56

results matching ""

    No results matching ""