likes
comments
collection
share

JavaScript—数据类型(二)

作者站长头像
站长
· 阅读数 17

JavaScript—数据类型(二)

上一篇主要介绍了JavaScript中的数据类型种类及检验方法,本篇将理解如何正确和准确地转换两个不同类型的值

前言

如果你有一个number42,但你想像一个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转换呢?

  1. 在一个if (..)语句中的测试表达式。
  2. 前置操作符 ! 或者 !!
  3. 在一个for ( .. ; .. ; .. )头部的测试表达式(第二个子句)。
  4. while (..)do..while(..)循环中的测试表达式。
  5. ? :三元表达式中的测试表达式(第一个子句)。
  6. ||(“逻辑或”)和&&(“逻辑与”)操作符左手边的操作数。

让我们展示一下:

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中的变量类型的不确定性,常常使人在开发过程中,踩到各种各样的坑,就像邪恶的魔法一样,但是我认为我们应当直面不理解的东西并设法更全面地 搞懂它。而不是因为大家都避开,或是你曾经被一些东西坑到就逃避强制转换。

最后 如果你觉得这篇文章对你有益,烦请点赞以及分享给更多需要的人!