转换类型的那些事儿
最近一直在看《你不知道的 JS》,轻总结下,转换类型。
强制转换分为隐式和显式转化,虽然名声不好,但还是有必要知道转换的规则是啥。
显式的强制转换,通过减少困惑,增强了代码的可读性和可维护性。
隐式的强制转换,有其的“隐藏的”副作用,但有些也是为了增强代码的可读性。
TL;DR
- ToString:基本类型和函数就是原地加上引号。但对象、数组转化就麻烦点。
- JSON.stringify:不识别
undefined
和函数,对于对象可以跳过部分值。可以有第二个和第三个参数。 - ToNumber:一句话难概括。。。
- ToBoolean:只有特定的值是可以转化为
false
,其他都是true
- parseInt:是从字符串里解析数字,遇到非数字就停止。要始终传字符串类型,且加上进制参数
- 隐式转化:各种条件表达式、加减乘除、><、==。
><
尽量转化为数字之后在使用。尽量用===
替换==
ToString()
一般用xx.toString(xx)或者String(xx)
显示转化。
基本类型有自然的字符串化形式
String(null); // 'null' String(undefined); // 'undefined' String(true); // 'true' String(1); // '1' String(1000000000000000000000.1); // "1e+21", 稍微注意的是,如果数很大或者很小,会变成指数的
- 普通对象,会调用默认的
toString()
,会返回内部的[[ Class ]]
- 函数,会返回函数的字符串形式
- 数组,会将数组每个都字符串化,然后用 **","**拼接每个字符串
String({}); // "[object Object]"( String(function () {}); // "function(){}" String([1, 2, {}]); // "1,2,[object Object]"
JSON.stringify(xx)
string
、number
、boolean
、和 null
值在 JSON
字符串化时,和xx.toString()
的值基本是相同的。
JSON.stringify(null); // 'null' JSON.stringify(true); // 'true' JSON.stringify(1); // '1', JSON.stringify(1000000000000000000000.1); // "1e+21",稍微注意的是,如果数很大或者很小,会变成指数的
但是,其他类型的都不一样了!!!
- 遇到
undefined
、function
、和symbol
时将会自动地忽略它们,返回undefined
- 遇到
array
的时候,如果某项是上面的值,会替换成null
- 遇到对象的时候,如果对象有**
toJSON
**的方法,会将其返回值字符串化。没有的话,直接转化,会跳过值是undefined
、function
、和symbol
。
JSON.stringify(undefined); // undefined JSON.stringify(function () {}); // undefined JSON.stringify([1, 2, undefined, function () {}]); // "[1,2,null,null]" JSON.stringify({ a: 1, b: 2, c: undefined, hello() {} }); // "{\"a\":1,\"b\":2}" JSON.stringify({ a: 1, b: 2, toJSON() { return { a: this.a }; }, }); // "{\"a\":1}"
JSON.stringify 可以不止一个参数
第二个参数和第三个参数都是可选的。
第二个参数的作用类似toJSON
,可以指定特定的key
字符串化,可以是数组,也可以是函数。 第三个参数的作用,就是每级缩进,个人觉得这个,作用不大,知道就行。
var a = { b: 1, c: 2 }; JSON.stringify(a, ['b']); // "{\"b\":1}" JSON.stringify(a, function (k, v) { if (k === 'b') return v; }); // "{\"b\":1}" JSON.stringify(a, ['c'], '--'); // "{\n--\"c\": 2\n}"
ToNumber
一般用Number(xx)
显示转化。
undefined
的话,变成NaN
null
的话,变成0
Boolean
的话true
变成1
,false
变成0
- 字符串的话,只含整数或者小数都可以变成正常数字,其他都是
NaN
Number(undefined); // NaN Number(null); // 0 Number(true); // 1 Number(false); // 0 Number('1.2'); // 1.2 Number('1.2d'); // NaN
对象以及数组,会先转换成基本类型值的等价物,然后根据上面的规则继续转化。
转换成等价物说白了,就是先调用xx.valueOf()
没有这个方法的话,就调用xx.toString()
,如果这两方法返回基本类型值的话,则Number(返回值)
,没返回基本值的话就报错
var a = { valueOf() { return '1'; }, toString() { return '2'; }, }; Number(a); // 1 var b = { c: 1 }; Number(b); // NaN var c = { c: 1, toString() { return {}; }, }; Number(c); // 报错
ToBoolean
一般用Boolean(xx)
显示转化。
将其他类型的值转化为 Boolean 类型的值,其实只要记住哪些转化为false
即可,其他的都是true
。
undefined
null
false
+0
,-0
,NaN
""
特别注意:
- 空对象、空函数、空数组转换成 Boolean 的话都是
true
- 用 new 创建基本类型的时候,此时转化成 Boolean 的话也是
true
Boolean({} && [] && function () {}); // true Boolean(new String('') && new Number(0) && new Boolean(false)); // true
其他的显式转换
上面的String(x)/Number(x)/Boolean(x)
都是显式转化。
还有一些其他公认的显式转换:
// number => string 1 + '' === String(1); // string => number +'1' === Number('1'); // date => number 获取当前时间戳 等同于 Date.now() 或 new Date().getTime() +new Date() === Number(new Date()); // 任意类型 => Boolean !!1;
~x 等同于 -(x+1)
有时候,还会看到~
。 在 JS 里,常和indexOf
结合使用。
-1
也是另一种哨兵值,C 语言里-1
表示失败。 而 JS 里的indexOf
返回值为-1 的话,也表示没找到。
const isInclude = 'abc'.indexOf('d') !== -1; if (isInclude) { // ... }
这种写法没毛病,但如果用~
更简洁,这时候用在 if 里,相当于转换成 Boolean
const isInclude = ~('abc').indexOf('d') if (isInclude).indexOf('d')) { // ... }
解析数字字符串:和强制转换不一样
解析数字字符串:从一个字符串中解析出一个数字是 容忍 非数字字符的 —— 从左到右,如果遇到非数字字符就停止解析 。 强制转换是 不容忍 并且会失败而得出值 NaN。
解析不应当被视为强制转换的替代品
var a = '42'; var b = '42px'; Number(a); // 42 parseInt(a); // 42 Number(b); // NaN parseInt(b); // 42
parseInt 是工作在string值上的,所以永远不要传入非 string,不然会强制转换成 string。
parseInt 还有第二个可选参数,表示将字符串翻译成几进制(2-35),默认是 10 进制,总是在第二个参数值上传递进制,注意,最终返回的结果是 10 进制。
parseInt('0x16', 10); // 0 parseInt('0x16', 16); // 22 parseInt('15', 10); // 15 // 15作为16进制的翻译,转化为10进制就是21 parseInt('15', 16); // 21
参数是非字符串的话,会有各种奇怪的现象,所以永远不要传入非 string
parseInt(1 / 0, 19); // 18 ("I" from "Infinity") parseInt(0.000008); // 0 ("0" from "0.000008") parseInt(0.0000008); // 8 ("8" from "8e-7") parseInt(false, 16); // 250 ("fa" from "false") parseInt(parseInt, 16); // 15 ("f" from "function..") parseInt('0x10'); // 16 parseInt('103', 2); // 2
其他的隐式转换
- 、* 、/ 运算符会将两边隐式变成 number 类型
这个还好,也容易理解。
'1' - '2' === -1;
+ 运算符
使用加号运算符要小心,加到字符串类型的时候,另一边会强制转化成字符串类型。
2 + 1 + '1' === '31';
> 和 <
这种也会发生类型转换,为了消除歧义,最好转化为数字类型之后在使用。
var a = [42]; var b = '043'; a < b; // false -- 字符串比较! Number(a) < Number(b); // true -- 数字比较! '1' - '2';
* -> Boolean
哪个种类的表达式操作(隐含地)要求/强制一个 boolean 转换呢?
- 在一个
if (..)
语句中的测试表达式。 - 在一个
for ( .. ; .. ; .. )
头部的测试表达式(第二个子句)。 - 在
while (..)
和do..while(..)
循环中的测试表达式。 - 在
? :
三元表达式中的测试表达式(第一个子句)。 ||
(“逻辑或”)和&&
(“逻辑与”)操作符左手边的操作数(它用作测试表达式 —— 见下面的讨论!)
特别注意:一个&&或||操作符产生的值不见得是 Boolean 类型。这个产生的值将总是两个操作数表达式其中之一的值!
var a = 42; var b = 'abc'; var c = null; a || b; // 42 a && b; // "abc" c || b; // "abc" c && b; // null // 经常可能会写这样的代码 var a = 42; var b = null; var c = 'foo'; // 其实这里相当于 if("foo") 因为是if表达式,所以隐式转化为 if(true) if (a && (b || c)) { console.log('yep'); }
== 和 ===
==
两边的转换规则,可能超乎你的想象,所以,最好直接使用===
。