JavaScript—数据类型(二)
上一篇主要介绍了JavaScript中的数据类型种类及检验方法,本篇将理解如何正确和准确地转换两个不同类型的值
前言
如果你有一个number
值42
,但你想像一个string
那样对待它,比如从下标1
中将"2"
作为一个字符抽取出来,那么显然你需要首先将值从 number
(强制)转换成一个 string
。
这看起来十分简单。
但是这样的强制转换可能以许多不同的方式发生。其中有些方式是明确的,很容易推理的,和可靠的。但是如果你不小心,强制转换就可能以非常奇怪的,令人吃惊的方式发生,例如下面这段代码
// `1.77`乘以`1000`,7次
var a = 1.77 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
// 7次乘以3位 => 21位
a.toString(); // "1.77e21" 并不是我们想要的1770000000000000000000
思考题: 上述的数字要拿到每一位有什么方法可以实现?
显式类型转换
将一个值从一个类型明确地转换到另一个类型通常称为类型转换,通常使用Number()
、String()
、Boolean()
等方法进行强制转换。
原始值到原始值的转换
原始值到原始值的转换相对简单,所有原始值转化为字符串的情形也已明确定义。转化为数字的情况比较微妙,那些以数字表示的字符串可以直接转为数字,也允许开头或者结尾有空格。
但是包含数字字面量
,开头或者结尾处的任意非空格字符(注:开头处的+
或-
允许),或者任意位置的非数字字符,都不会当成数字直接量的一部分,进而使字符串转化为数字结果成为NaN。
有一些数字转化结果看起来很奇怪,true转化为1,false转化为0,空字符串''转化为0。
表1-原始值转化值 | 转化为 | ||
---|---|---|---|
数字 | 字符串 | 布尔值 | |
null | 0 | 'null' | false |
undefined | NaN | 'undefined' | false |
true false | 1 0 | 'true' 'false' | |
''(空字符串) ' 3.14'(非空,数字) 'me'(非空,非数字) | 0 3.14 NaN | false true true | |
0 -0 NaN Infinity -Infinity 1 | '0' '0' 'NaN' 'Infinity' 'Infinity' '1' | false false false true true true |
上表展示了常规的原始值到原始值转化结果。
引用型对象转为原始值
对象到原始值的转换过程主要用到两个转化方法 toString()
、valueOf()
1、对象到字符串
JavaScript中对象到字符串的转换经过了如下这些步骤:
-
如果对象具有
toString()
方法,则调用这个方法。如果它返回一个原始值,JavaScript将这个值转换为字符串(如果本身不是字符串),并返回这个字符串结果。 -
如果对象没有
toString()
方法,或者这个方法不返回一个原始值,那么JavaScript会调用valueOf()
(如果存在),如果返回值是原始值,JavaScript将这个值转换为字符串(如果本身不是字符串),并返回这个字符串结果。 -
否则,JavaScript无法从toString()或者valueOf()获得原始值,因此抛出一个类型错误异常。
2、对象到数字
对象到数字的转换过程,JavaScript做了同样的事情,只是它会首先尝试使用valueOf()方法:
-
如果对象具有
valueOf()
,后者返回一个原始值,则JavaScript将这个原始值转换为数字并返回。 -
否则,如果对象具有
toString()
方法,后者返回一个字符串原始值,则JavaScript将其转换为数字并返回。 -
否则,JavaScript抛出一个类型错误异常。
对象转换为数字的细节解释了为什么空数组会被转换为数字0以及为什么具有单个数字元素的数组同样会转化为一个数字。数组继承了默认的valueOf(),这个方法返回一个对象而不是一个原始值,因此数组到数字的转换调用了toString()。空数组转换为空字符串,空字符转换为0,同样的单个数字元素的数组会转换为一个数字直接量字符,然后再转换为数字。
隐式类型转换
隐含的 强制转换是指这样的类型转换:它们是隐藏的,由于其他的动作隐含地发生的不明显的副作用。换句话说,任何(对你)不明显的类型转换都是 隐含的转换。
明确的 强制转换的过程很明白,但是这可能 太过 明显 —— 隐含的 强制转换拥有相反的目的:使代码更难理解。但是事情都有双面性,隐含的 强制转换可以——减少搞乱我们代码的繁冗,模板代码,和/或不必要的实现细节。例如:
// 你会选择哪种方式完成 Date ---> string
+new Date(); // 第一种
new Date().getTime() // 第二种
ToNumber
这里列举一些常见的隐含的转化为number类型的方式
// String (数字直接量) --> Number
let strNum = '3.14';
+strNum // 结果3.14
strNum - 0 // 结果3.14
// Array(单个数字元素) --> Number
let arr1 = [2],arr2 = [3];
+arr1 // 2
arr1 - 0 // 2
arr1 + arr2 // 5;
// 其他
true + false // 1
-true // -1
+null // 0
上述的转换过程用到了运算符 +号 或者 -号,还有一元操作符+
或者-
.
一元加( +
)运算符在其操作数之前并计算其操作数,但会尝试将其转换为数字
(如果它还不是的话)。虽然一元减(-
)也可以转换非数字,但一元加是将某些东西转换为数字的最快和首选方法,因为它不对数字执行任何其他操作。
另外其中 +号 操作的过程分析步骤:
-
如果有一方是字符串,另一方则会被
转换为字符串
,并且它们连接起来。 -
如果双方都是
BigInt
,则执行 BigInt 加法。如果一方是 BigInt 而另一方不是,会抛出TypeError
。 -
否则,双方都会被
转换为数字
执行数字加法。
-号 操作的过程分析步骤:
-
如果两个操作数都是数值,按照常规的减法计算
-
如果双方都是
BigInt
,则执行 BigInt 减法。如果一方是 BigInt 而另一方不是,会抛出TypeError
。 -
如果有一个操作数不是数值,则调用上面显式类型转换为number后,执行减法运算。
ToString
隐含的转为string类型也多由上述的 +号转化。
"" + 3.14 // "3.14"
"" + true // "1"
"" + [3] // "3"
"" + {} // "[object Object]"
"" + null // "null"
"" + undefined // "undefined"
上面的例子中如果一方是对象类型,则运用了上面讲到的对象到字符串的转化过程。
ToBoolean
现在,让我们将注意力转向目标为boolean
值的 隐含 强制转换上,这是目前最常见,并且还是目前潜在的最麻烦的一种。
记住,隐含的 强制转换是当你以强制一个值被转换的方式使用这个值时才启动的。对于数字和string
操作,很容易就能看出这种强制转换是如何发生的。
但是,哪个种类的表达式操作(隐含地)要求/强制一个boolean
转换呢?
- 在一个
if (..)
语句中的测试表达式。 - 前置操作符
!
或者!!
。 - 在一个
for ( .. ; .. ; .. )
头部的测试表达式(第二个子句)。 - 在
while (..)
和do..while(..)
循环中的测试表达式。 - 在
? :
三元表达式中的测试表达式(第一个子句)。 ||
(“逻辑或”)和&&
(“逻辑与”)操作符左手边的操作数。
让我们展示一下:
var a = 42;
var b = "abc";
var c;
var d = null;
if (a) { // true
console.log( "yep" ); // yep
}
!d // true
!!d // false
for(let i = 0; i < b.length; i++) {
console.log(b.char(i)) // 依次输出 a b c
}
while (c) { // false
console.log( "nope, never runs" );
}
c = d ? a : b;
c; // "abc"
// 先判断 && 左侧的 a 为42, 为true,
// 再判断 (a && d)的结果为false, 继续执行 || 右侧 c 值为'abc'
// 因此(a && d) || c 的值为'abc', 相当于执行 if('abc'), 总结果为true
if ((a && d) || c) {
console.log( "yep" ); // yep
}
在这些上下文环境中使用的,任何还不是boolean
的值,都会被 隐含地 强制转换为一个boolean
。
另外值得注意的是:
一个&&或||操作符产生的值不见得是Boolean类型。这个产生的值将总是两个操作数表达式其中之一的值。
例如:
var a = 42;
var b = "abc";
var c = null;
a || b; // 42
a && b; // "abc"
c || b; // "abc"
c && b; // null
总结
JavaScript中的变量类型的不确定性,常常使人在开发过程中,踩到各种各样的坑,就像邪恶的魔法一样,但是我认为我们应当直面不理解的东西并设法更全面地 搞懂它。而不是因为大家都避开,或是你曾经被一些东西坑到就逃避强制转换。
最后 如果你觉得这篇文章对你有益,烦请点赞以及分享给更多需要的人!
转载自:https://juejin.cn/post/7257441472444727351