将一种类型转换为另一种类型,便是类型转换,其中包括显示转换和隐式转换。
显示转换当然是用户自发调用API进行转换,隐式转换常常在我们意想不到的时候进行,当然,看完本篇博客,你就可以拥有洞察隐式转换的能力了。
在 JavaScript 中,当我们进行比较操作或者加减乘除四则运算操作时,常常会触发 JavaScript 的隐式类型转换机制;而这部分也往往是令人迷惑的地方。譬如浏览器中的 console.log 操作常常会将任何值都转化为字符串然后展示,而数学运算则会首先将值转化为数值类型(除了 Date 类型对象)然后进行操作。
比如:
console.log(1 + '1') //'11'
console.log(1 + []); //1
第一个将1转换成‘1’,然后和后边的字符串进行拼接,第二个的[]被转化成了0,那你可能会问,为什么第一个不转化’1’为1,然后1+1=2呢?当然主要是甲鱼的屁股——规定,官方是这么规定的,那我们必须遵守,当然你有能力可以自己创建自己的规范,然后自己造一个符合自己规范的语言,浏览器。。。话不多说,我们接下来会详细介绍背后的转化机制。
数据类型
我们知道JS的数据类型分为两类,一类是基本类型,一类是引用类型
JS有六大基本类型Number String Null Undefined Null Symbol
引用类型有:Object Array Function
因为下文表述有原始值和原始类型,我们约定 原始类型和基本类型等意
原始值转布尔
在 JavaScript 中,只有 6 种值可以被转换成 false,其他都会被转换成 true。
console.log(Boolean()); //false
console.log(Boolean(false)); //false
console.log(Boolean(+0)); //false
console.log(Boolean(-0)); //false
console.log(Boolean("")); //false
console.log(Boolean(NaN)); //false
console.log(Boolean(null)); //false
console.log(Boolean(undefined)); //false
原始值转数值
类型 | 转化后的结果 |
---|---|
Number | 对应的数值 |
String | 较复杂,后边讨论 |
Boolean | true为1 ,false为0 |
Undefine | NaN |
Null | +0 |
字符串转数字
我们先看几个例子
console.log(Number("123")); //123
console.log(Number("123aaa")); //NaN
console.log(Number('aaa123')); //NaN
console.log(Number('1 23')); //NaN
console.log(Number('1a23')); //NaN
console.log(Number('123 ')); //123
console.log(Number(' 123')); //123
console.log(Number("000123")); //123
console.log(Number("0xff")); //255
console.log(Number(".123")); //0.123
console.log(Number("")) // 0
console.log(Number(" ")) // 0
我们可以发现几个规律
- 字符串中只有数字的话,那么就返回对应的数字值
- 只要字符串中,含有字母,以及数字之间有空格,就返回NaN
- 数字前后有空格,且没有字母包含在内,则返回对应数值
- 如果是按16进制形式书写,会自动转化成十进制
- 可以转化整数部分为0的小数。
基于这种严格判断,我们一般还是用parseInt或parseFloat进行更加灵活的转换。
parseInt 和 parseFloat 都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回 NaN:
console.log(parseInt("aaaa123")); //NaN
console.log(parseInt("123aaa")); //123
console.log(parseInt(" 123")); //123
原始值转字符串
类型 | 转化后的值 |
---|---|
Boolean | 为 true 和 false对应单词的字符串 |
Undefined | “undefined" |
Null | ‘null’ |
String | 返回对应的字符串 |
Number | 也比较复杂,看例子 |
console.log(String(+0));//0
console.log(String(-0));//0
console.log(String(Infinity));//Infinity
console.log(String(-Infinity));//-Infinity
原始值转对象
这个很简单,每种类型都有对应的实例化方法。
console.log(typeof new Boolean(0)); //object
console.log(typeof false); //boolean
console.log(typeof new Number(1)); //object
console.log(typeof 1); //number
console.log(typeof new String('test')); //object
console.log(typeof 'test'); //number
null 和 undefined 属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误 (TypeError) 异常,而不会执行正常的转换。
对象转布尔
对象到布尔值的转换非常简单:所有对象(包括数组和函数)都转换为 true。
对象转字符串和数字
对象转字符串和数字,都是通过调用带转换对象的方法来进行转换的,有两个方法,toString 和 valueOf
我们知道Object.prototype,toString
这个方法非常强大,他会返回[object 类名]
字符串,经常用于我们的类型判断。
console.log(Object.prototype.toString({ a: 1 })); //'[object Object]'
{a: 1}).toString() // "[object Object]"
我们可以看做,在调用toString方法的时候,其实调用的是Object.prototype.toString()方法
当然js中也定义了很多版本的toString方法
- 数组的toString就是把数组里的每一个元素转化成字符串
- 函数的toString就是把整个函数体转化为字符串
- 日期的toString就是返回一个可读日期和时间字符串
- 正则的toString 就是返回对应正则表达式的字符串
console.log(['a,b'].toString()); //'a','b'
console.log([].toString()); //''
console.log((function() {}).toString()); //function(){}
console.log((new RegExp(/\d/)).toString()); //'/\d/'
console.log((new Date()).toString());//Fri Apr 24 2020 10:34:18 GMT+0800 (GMT+08:00)
默认的 valueOf 方法返回这个对象本身,数组、函数、正则简单的继承了这个默认方法,也会返回对象本身。日期是一个例外,它会返回它的一个内容表示: 1970 年 1 月 1 日以来的毫秒数。
接下来重头戏来了,我们了解了toString和valueOf
之后,接下来了解对象转字符串和数字的内部机制是什么。
首先官方给的定义是,
类型 | 结果 |
---|---|
Object | 1. primValue = toPrimitive(input,number) 2. toNumber(primValue) |
类型 | 结果 |
---|---|
Object | 1. primValue = toPrimitive(input,string) 2. toString(primValue) |
所谓的 ToPrimitive 方法,其实就是输入一个值,然后返回一个一定是基本类型的值。
我们总结一下,当我们用 String 方法转化一个值的时候,如果是基本类型,就参照 “原始值转字符” 这一节的对应表,如果不是基本类型,我们会将调用一个 ToPrimitive 方法,将其转为基本类型,然后再参照“原始值转字符” 这一节的对应表进行转换。
转数字同理。
ToPrimitive
语法表示是
ToPrimitive(input[, PreferredType])
- input代表输入的内容,下一个参数是要转化成的类型
- 如果没有指定类型,那么除了input是日期是认为是字符串,其他都默认为数字类型
- 如果传入的 input 是
Undefined、Null、Boolean、Number、String
类型,直接返回该值。
ToPromitive(input,number)步骤如下:
4. input为基本类型,直接返回
5. 否则调用valueOf方法,如果返回一个原始值,则将其返回
6. 否则调用toString方法,如果返回一个原始值,则将其返回
7. 否则js抛出一个类型错误
ToPromitive(input,string)步骤如下:
- input为基本类型,直接返回
- 否则调用toString方法,如果返回一个原始值,则将其返回
- 否则调用valueOf方法,如果返回一个原始值,则将其返回
- 否则js抛出一个类型错误
依然是看几个例子
console.log(Number({})) // NaN
console.log(Number({a : 1})) // NaN
console.log(Number([])) // 0
console.log(Number([0])) // 0
console.log(Number([1, 2, 3])) // NaN
console.log(Number(function(){var a = 1;})) // NaN
console.log(Number(/\d+/g)) // NaN
console.log(Number(new Date(2010, 0, 1))) // 1262275200000
console.log(Number(new Error('a'))) // NaN
以第一个Number({})为例,{}不是基本类型,然后调用valueOf,返回{}本身,不是原始值,然后我们进行toString,返回‘[object Object]’,是一个字符串,然后按原始值转数字来转化,结果自然是NaN
我们再来分析Number([])
,同样不是原始值,然后调用valueOf,返回[],然后调用toString,返回”“,依据原始值转数字,结果为0
其他例子读者可一次尝试。
JSON.stringify
这个方法就是将js值,转化为JSON串,其实也是调用了toString()方法,但是还是有一些区别。
- 处理原始值和toString的返回值相同
console.log(JSON.stringify(null)) // null
console.log(JSON.stringify(undefined)) // undefined,注意这个undefined不是字符串的undefined
console.log(JSON.stringify(undefined) == 'undefined');
console.log(JSON.stringify(true)) // true
console.log(JSON.stringify(42)) // 42
console.log(JSON.stringify("42")) // "42"
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]"
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
JSON.stringify({x: undefined, y: Object, z: Symbol("")});
// "{}"
JSON.stringify([undefined, Object, Symbol("")]);
// "[null,null,null]"
- 如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如:
var obj = {
foo: 'foo',
toJSON: function () {
return 'bar';
}
};
JSON.stringify(obj); // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'
当然,到这里,内部的转换机制我们基本了解了,但是关于类型转换还没完,我们先消化一下,然后转到这里
轻松玩转JS的类型转换(下)