1、JS类型转换共有三种情况:
- 转换为布尔值
- 转换为数字
- 转换为字符串
2、类型转换表格:
原始值 | 转换目标 | 结果 |
number | 布尔值 | 除了0、-0、NaN都为true |
string | 布尔值 |
除了空字符串都为true |
undefined、null | 布尔值 | false |
引用类型 | 布尔值 | true |
number | 字符串 | 5 -----> '5' |
Boolean | 字符串 | true -----> 'true' |
数组 | 字符串 | [1, 2] ----> '1, 2' |
对象 | 字符串 | {id: 1} ----> '[object Object]' |
string | 数字 | '1' ----> 1 ; 'a' ----> NaN |
数组 | 数字 | 空数组为0,存在一个元素且为数字转数字,其他情况NaN |
null | 数字 | 0 |
除了数组的引用类型 | 数字 | NaN |
Symbol | 数字 | 抛错 |
- 转Boolean:在条件判断时,除undefined、null、false、''、NaN、0、-0,其他都转为true,包括所有对象。
3、类型转换:
- valueOf():如果转换为基础类型,就返回这个对象逻辑上对应的原始类型的值。如String包装对象的valueOf(),应该返回这个对象所包装的字符串;
- x.toString():返回这个对象的字符串表示。用一个字符串来描述这个对象的内容
基本类型的valueOf()会返回自身的原始类型,而Array、Function、Object都返回自身,Date返回时间戳,Error和Math没有valueOf()方法
// object var obj = {a: 1}; obj.valueOf(); // {a: 1} obj.toString(); // "[object Object]" // array var arr = [1, 2]; arr.valueOf(); // [1, 2] arr.toString(); // "1, 2" // function var fun = function() {}; fun.valueOf(); // ƒ () {} fun.toString(); // "function() {}" // date var date = new Date(); date.valueOf(); // 1572592931967 date.toString(); // "Fri Nov 01 2019 15:22:01 GMT+0800 (中国标准时间)" // string var str = 'abc'; str.valueOf(); // "abc" str.toString(); // "abc" // number var num = 222; num.valueOf(); // 222 num.toString(); // "222" // boolean var bool = true; bool.valueOf(); // true bool.toString(); // "true" // null & undefined 无法调用,报错 // regExp var reg = /cat/g; reg.valueOf(); // /cat/g reg.toString(); // "/cat/g" // window var win = window; win.valueOf(); // window win.toString(); // "[object Window]" // error var err = new Error('abc'); err.valueOf(); // Error: abc at <anonymous>:1:11 err.toString(); // "Error: abc"
4、检测类型的四个内部方法:
- ToPrimitive( input [, PreferredType ] )
- ToBoolean( argument )
- ToString( argument )
- ToNumber( argument )
这4个方法是ECMAScript定义的4个抽象的操作,它们在JS内部使用,进行类型转换。JS的使用者不能直接调用这些函数,但是了解这些函数有利于我们了解JS类型转换的原理。
(1)ToPrimitive( input [, PreferredType ] )
将input转换成一个原始类型的值。PreferredType参数要么不传入,要么是Number或String。如果PreferredType参数是Number,ToPrimitive这样执行:
- 如果input本身就是原始类型,直接返回input。
- 调用input.valueOf(),如果结果是原始类型,则返回这个结果。
- 调用input.toString(),如果结果是原始类型,则返回这个结果。
- 抛出TypeError异常。
以下是PreferredType不为Number时的执行顺序:
-
- 如果PreferredType参数是String,则交换上面这个过程的第2步和第3步的顺序,其他执行过程相同。
- 如果PreferredType参数没有传入
- 如果input是内置的Date类型,PreferredType视为String
- 否则PreferredType视为Number - 先调用ValueOf再调用toString
Date 转换的时候 PreferredType 视为String 所有先调用toString。 console.log(111 + new Date()); // "111Thu Nov 07 2019 20:29:33 GMT+0800 (中国标准时间)"
(2)ToBoolean:
Argument Type | Result |
Undefined | Return false |
Null | Return false |
Boolean | Return argument |
Number | 仅当argument为+0,-0,NaN时,return false;否则一律return true |
String | 仅当argument为空字符串(长度为0)时,return false;否则一律return true |
Symbol | Return true |
Object | Return true |
这些规定都来自ECMA的标准,在条件判断时,除了undefined、null、false、NaN、''、+0、-0,其他所有值都转为true,包括所有对象。
(3)ToNumber:
Argument Type | Result |
Undefined | Return NaN |
Null | Return +0 |
Boolean | 如果argument为true,return 1,为false则return +0 |
Number | 直接return argument |
String | 将字符串的内容转化为数字(比如"23" -> 23),如果转化失败则return NaN(比如"23a" -> NaN) |
Symbol | 抛出TypeError异常 |
Object | 先primValue = ToPrimitive(argument, Number),再对primValue使用ToNumber(primValue) |
由此可见ToNumber的转化并不总是成功,有时会转化成NaN,有时则直接抛出异常。
所以Number([1, 2]) -> 先由ToPrimitive(argument, Number)转换[1, 2]
-> valueOf返回本身,再调用toString返回'1, 2' -> 再ToNumber(primValue)转换为NaN
(4)ToString:
Argument Type | Result |
Undefined | Return 'undefined' |
Null | Return 'null' |
Boolean | 如果argument为true,return 'true',为false则return 'false' |
Number | 用字符串来表示这个数字 |
String | 直接return argument |
Symbol | 抛出TypeError异常 |
Object | 先primValue = ToPrimitive(argument, hint String),再对primValue使用ToString(primValue) |
5、隐式类型转换(自动类型转换):
(1)当JS期望得到某种类型的值,而实际的值是其他类型,就会发生隐式类型转换。系统会自动调用内部的ToBoolean( argument )、ToNumber( argument )、ToString( argument )转换方法。
eg1:
if(!undefined && !null && !0 && !NaN && !'' ) { console.log('true'); } // true
eg1:因为在if的括号中,js期望得到boolean的值,所以对括号中每一个值都是用Toolean( argument ),将它们转换成boolean。
eg2:
3 * { valueOf: function() { return 5 } }; // 15
eg2:在乘号的两段,js期望得到Number类型的值,所以对右边的对象使用ToNumber( argument ),得到结果5,再与乘号左边的3相乘。
eg3:
3 * { valueOf: function() { return {} }, toString: function() { return {} } } // Uncaught TypeError: Cannot convert object to primitive value
eg3:调用ToNumber( argument )的过程中,调用了ToPrimitive(input, Number),因为在toPrimitive中valueOf和toString都没有返回原始类型,所以抛出异常。
(2)加法:
符号“+”既可以表示“算数加法”,也可以表示“字符串拼接”。因此:只要‘+’两端的任意一个操作数是字符串,就表示拼接。否则表示算数加法。
12 + 3 // 15 12 + '3' // '123'
计算过程:
- 令lval = 符号左边的值,rval = 符号右边的值;
- 令lprim = ToPrimitive(lval), rprim = ToPrimitive(rval);
- 如果lprim和rprim中有任意一个为String类型,将ToString(lprim)和ToString(rprim)的结果做字符串拼接;
否则,将ToNumber(lprim)和ToNumber(rprim)的结果做加法。
[] + [] // '' // 提示:ToPrimitive([])返回空字符串 [] + {} // "[object Object]" // 提示:ToPrimitive({})返回"[object Object]" {} +[] // 0 // 结果不符合我们的预期:"[object Object]" // 提示:在Chrome中,符号左边的{}被解释成了一个语句块,而不是一个对象 // 注意在别的执行引擎上可能会将{}解释成对象 // 这一行等价于 '+ []' // '+ anuValue' <==>Number(anyValue) ({}) + [] // "[object Object]" // 加上括号以后,{}被解释成了一个对象
另外对于加法还需要注意 'a' + + 'b'
'a' + + 'b' // "aNaN" // 先执行+'b',等于NaN,所以结果为"aNaN" // +'1' 类似的形式快速获取Number类型,相当于调用ToNumber方法
(3)其他运算符:其他运算符中,只要其中一方是数字,那么另一方就会被转为数字
// 减法运算 4 - ''; // 4 4 - '3'; // 1 4 - 'b'; // NaN 4 - true; // 3 4 - []; // 4 4 - [1, 2]; //NaN 4 - {}; //NaN 4 - {id: 1}; // NaN // 乘法运算 4 * ''; // 0 4 * '3'; // 12 4 * 'b'; // NaN 4 * true; // 4 4 * []; // 0 4 * [1, 2]; //NaN 4 * {}; //NaN 4 * {id: 1}; // NaN // 除法运算 4 / ''; // Infinity 4 / '3'; // 1.333333333 4 / 'b'; // NaN 4 / true; // 4 4 / []; // Infinity 4 / [1, 2]; //NaN 4 / {}; //NaN 4 / {id: 1}; // NaN
TEST:
5 * '5'; // 25 Null / 5; // 0 5 * [1, 2]; // NaN [] + [ ]; // '' 5 >= NaN; // false 5 * 'a'; // NaN 5 / NaN; // NaN [] + {}; // "[object Object]" 0 / 0; // Infinity 5 * []; // 0 5 < [1, 2]; // false 5 / 'a'; // NaN 5 >= null; // true 5 / [1]; //5 'a' + + 'b'; // "aNaN" 5 / undefined; // NaN 5 * false; // 0 5 + [1, 2, 3]; // "51, 2, 3" 5 >= true; // true {} + []; // 0 5 / []; // Infinity 5 * true; // 5 ({}) + []; // "[object Object]" 5 * null; // 0 5 >= 'true'; // false 5 > []; // true 123 + {toString: function() { return 'def'; }} // "123def" 5 / null; // Infinity "16" > "5"; // false 0 > [1]; // false 123 + {toString: function() { return 'def'; }, valueOf: function() { return 3; }} // 126
(4)比较运算符:
1.如果是对象,就通过 ToPrimitive 转换对象;
2.如果是字符串,就通过 unicode 字符索引来比较
let a = { valueOf() { return 0; }, toString() { return '-1'; } } a > -1; // true
3. == 双等:
1)x、y都为 Null 或 undefined,return true;一方为 null 或 undefined、NaN,return false;
2)如果x和y为String、Number或Boolean并且类型不一致,都转为Number再进行比较;
3)如果存在Object,都转为原始值,再比较
TEST:
"0" == null; // false "0" == undefined; // false "0" == false; // true "0" == NaN; // false "0" == 0; // true "0" == ''; // false false == null; // false false == undefined; // false false == NaN; // false false == 0; // true false == ''; // true false == []; // true false == {}; // false "" == null; // false "" == undefined; // false "" == NaN; // false "" == 0; // true "" == ''; // true "" == []; // true "" == {}; // false 0 == null; // false 0 == undefined; // false 0 == NaN; // false 0 == []; // true 0 == {}; // false [] == ![]; // true
6、显式类型转换(强制类型转换):
(1) 显式调用Boolean(value)、Number(value)、String(value)完成的类型转换,叫显式类型转换
(2)new Boolean(value)、new Number(value)、new String(value)传入各自对应的原始类型的值,可以实现“装箱” -- 将原始类型封装成一个对象。其实这三个函数不仅仅可以当作构造函数,可以直接当作普通函数使用,将任何类型的参数转换成原始类型的值:
Boolean('sdjdh'); // true Number('123'); // 123 String({ a: 24 }); //"[object Object]"
其实这三个函数用于类型转换的时候,调用的就是JS内部的 ToBoolean( argument )、ToNumber( argument )、ToString( argument ) 方法!
Number()比parseInt parseFloat严格。parseInt parseFloat会只截取数字转换。
(3) 这里解释一下 String({ a: 24 }); // "[object Object]" 的过程:
· 执行String({a: 24})
· 执行js内部函数ToString({a: 24})
· 执行 primValue = ToPrimitive({a: 24}, hint String)
· {a: 24}不是原始类型,进入下一步
· 在ToPrimitive内调用({a: 24}).toString(),返回原始值"[object Object]",因此直接返回这个字符串,ToPrimitive后面的步骤不用继续进行了
· primValue被复制为ToPrimitive的返回值"[object Object]"
· 执行JS内部函数ToString("[object Object]"),返回"[object Object]"
· 返回"[object Object]"
· 返回"[object Object]"
· 返回 "[object Object]"
Tips: 为了防止出现意料之外的结果,最好在不确定的地方使用显示类型转换。
let obj2 = { valueOf() { return 2 }, toString() { return [] } } String(obj2); // '2' 2 + obj2; // 4
可以重写 Symbol.toPrimitive,该方法在转原始类型时调用优先级最高。
let a = { valueOf() { return 0 }, toString() { return '1'; }, [Symbol.toPrimitive]() { return 2; } } 1 + a; // 3 a = { valueOf() { return 3 }, toString() { return '1'; } } 1 + a; // 4
(4)parseInt:只会转换字符串。如果传入的不是字符串,会先转换成字符串在进行parse。
let obj = { valueOf: function() { return '2px' }, toString: function() { return [] } } parseInt(obj); // 2 --->如果不是字符串会先根据ToString转换成字符串,再去转换 parseInt(1/0, 19); // 18 --->1/0转换成Infinity。而有效数字范围是0-9 a-i 所以第一位为I代表18.n超出19的范围了,所以只返回18 parseInt(0.0000008); // 8 ---> 8e+7 6个0转换成字符串会转换成指数 parseInt(0.000008); // 0 ---> 0.000008 parseInt(false, 16); // 250 ---> 'false'=>'fa'在16进制下有效 parseInt(function() {...}, 16); // 15 ---> 'f'在16进制下有效
如果不填写第二个参数,则会根据传入的值来判断是什么进制,填写了代表转换成多少进制。
parseInt('0x10'); // 16 parseInt('103', 2); // 2
(5)~运算符表示-(x + 1):
~2; // -3 ~-1; // 0
根据这个特性,可以对-1进行特殊判断。比如indexof时,if(~a.indexof('bbb'))表示如果是-1就会返回false。
let a = 'aa'; if(~a.indexOf('bbb')) { console.log(11); } else {console.log(1222);} // 1222
(6)| 运算符:
3.2 | 0; // 3 -6.7 | 0; // -6