3月阅读周·你不知道的JavaScript | 详细了解强制类型转换,方能开发中合理运用它
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效,已经坚持阅读两个月。
《你不知道的JavaScript》分上中下三卷,内容相对较多。3月份,我计划先读前面两卷。
已读完书籍:《架构简洁之道》、《深入浅出的Node.js》。
当前阅读周书籍:《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》。
强制类型转换
详细了解强制类型转换的优缺点,帮助开发者在开发中合理地运用它。
值类型转换
将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况;隐式的情况称为强制类型转换(coercion)。在JavaScript中通常将它们统称为强制类型转换。
二者的区别显而易见:我们能够从代码中看出哪些地方是显式强制类型转换,而隐式强制类型转换则不那么明显,通常是某些操作产生的副作用。
看下面的代码:
var a = 42;
var b = a + ''; // 隐式强制类型转换
var c = String(a); // 显式强制类型转换
对变量b而言,强制类型转换是隐式的;由于+运算符的其中一个操作数是字符串,所以是字符串拼接操作,结果是数字42被强制类型转换为相应的字符串"42"。
而String(..)则是将a显式强制类型转换为字符串。
抽象值操作
字符串、数字和布尔值之间类型转换的基本规则,主要包括ToString、ToNumber和ToBoolean。
ToString
ToString负责处理非字符串到字符串的强制类型转换。
基本类型值的字符串化规则为:null转换为"null", undefined转换为"undefined",true转换为"true"。
数字的字符串化则遵循通用规则。
对普通对象来说,除非自行定义,否则toString()(Object.prototype.toString())返回内部属性[[Class]]的值,如"[object Object]"。
数组的默认toString()方法经过了重新定义,将所有单元字符串化以后再用", "连接起来:
var a = [1, 2, 3];
a.toString(); // "1,2,3"
1、JSON字符串化
工具函数JSON.stringify(..)在将JSON对象序列化为字符串时也用到了ToString。JSON.stringify(..)在对象中遇到undefined、function和symbol时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。
JSON.stringify(undefined); // undefined
JSON.stringify(function () {}); // undefined
JSON.stringify([1, undefined, function () {}, 4]); // "[1, null, null,4]"
JSON.stringify({ a: 2, b: function () {} }); // "{"a":2}"
JSON.stringify(..)并不是强制类型转换。它涉及ToString强制类型转换,具体表现在以下两点:
(1) 字符串、数字、布尔值和null的JSON.stringify(..)规则与ToString基本相同。
(2) 如果传递给JSON.stringify(..)的对象中定义了toJSON()方法,那么该方法会在字符串化前调用,以便将对象转换为安全的JSON值。
ToNumber
ToNumber会将true转换为1, false转换为0。undefined转换为NaN, null转换为0。
ToNumber对字符串的处理基本遵循数字常量的相关规则/语法。处理失败时返回NaN(处理数字常量失败时会产生语法错误)。
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
var a = {
valueOf: function () {
return '42';
},
};
var b = {
toString: function () {
return '42';
},
};
var c = [4, 2];
c.toString = function () {
return this.join(''); // "42"
};
Number(a); // 42
Number(b); // 42
Number(c); // 42
Number(''); // 0
Number([]); // 0
Number(['abc']); // NaN
ToBoolean
1、假值
JavaScript规范具体定义了一小撮可以被强制类型转换为false的值。
使用ToBoolean进行布尔强制类型转换可能出现的结果中,以下这些是假值:
- undefined
- null
- false
- +0、-0和NaN
- ""
假值的布尔强制类型转换结果为false。
2、假值对象(falsy object)
假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔值时结果为false。
3、真值(truthy value)
真值就是假值列表之外的值。
var a = []; // 空数组——是真值还是假值?
var b = {}; // 空对象——是真值还是假值?
var c = function () {}; // 空函数——是真值还是假值?
var d = Boolean(a && b && c);
d;
上面代码中,d的值是true。[]、{}和function(){}都不在假值列表中,因此它们都是真值。
掌握真/假值的重点在于理解布尔强制类型转换(显式和隐式)。
显式强制类型转换
字符串和数字之间的显式转换
字符串和数字之间的转换是通过String(..)和Number(..)这两个内建函数来实现的。
var a = 42;
var b = String(a);
var c = '3.14';
var d = Number(c);
b; // "42"
d; // 3.14
String(..)遵循前面讲过的ToString规则,将值转换为字符串基本类型。Number(..)遵循前面讲过的ToNumber规则,将值转换为数字基本类型。
1、日期显式转换为数字
一元运算符+的另一个常见用途是将日期(Date)对象强制类型转换为数字,返回结果为Unix时间戳,以毫秒为单位(从1970年1月1日00:00:00 UTC到当前时间):
var d = new Date();
console.log(+d); // 1710816557439
2、奇特的~运算符
~运算符首先将值强制类型转换为32位数字,然后执行字位操作“非”(对每一个字位进行反转)。
~x大致等同于-(x+1)。
~42; // -(42+1) ==> -43
显式解析数字字符串
解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字。
解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。
var a = '42';
var b = '42px';
Number(a); // 42
parseInt(a); // 42
Number(b); // NaN
parseInt(b); // 42
显式转换为布尔值
Boolean(..)(不带new)是显式的ToBoolean强制类型转换:
var a = '0';
var b = [];
var c = {};
var d = '';
var e = 0;
var f = null;
var g;
Boolean(a); // true
Boolean(b); // true
Boolean(c); // true
Boolean(d); // false
Boolean(e); // false
Boolean(f); // false
Boolean(g); // false
如果没有使用Boolean(..)和!!,就会自动隐式地进行ToBoolean转换。建议使用Boolean(..)和!!来进行显式转换以便让代码更清晰易读。
显式ToBoolean的另外一个用处,是在JSON序列化过程中将值强制类型转换为true或false:
var a = [
1,
function () {
/*..*/
},
2,
function () {
/*..*/
},
];
JSON.stringify(a); // "[1, null,2, null]"
JSON.stringify(a, function (key, val) {
if (typeof val == 'function') {
// 函数的ToBoolean强制类型转换
return !!val;
} else {
return val;
}
});
// "[1, true,2, true]"
隐式强制类型转换
隐式强制类型转换指的是那些隐蔽的强制类型转换,副作用也不是很明显。
字符串和数字之间的隐式强制类型转换
+运算符即能用于数字加法,也能用于字符串拼接。
var a = '42';
var b = '0';
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用ToPrimitive抽象操作,该抽象操作再调用[[DefaultValue]],以数字作为上下文。
可以将数字和空字符串 ""相+来将其转换为字符串:
var a = 42;
var b = a + '';
b; // "42"
对象的-操作与+类似:
var a = [3];
var b = [1];
a - b; // 2
布尔值到数字的隐式强制类型转换
在将某些复杂的布尔逻辑转换为数字加法的时候,隐式强制类型转换能派上大用场。
function onlyOne() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
// 跳过假值,和处理0一样,但是避免了NaN
if (arguments[i]) {
sum += arguments[i];
}
}
return sum == 1;
}
var a = true;
var b = false;
console.log(onlyOne(b, a)); // true
console.log(onlyOne(b, a, b, b, b)); // true
!! arguments[i]首先将参数转换为true或false,转换为布尔值以后,再通过Number(..)显式强制类型转换为0或1。
隐式强制类型转换为布尔值
相对布尔值,数字和字符串操作中的隐式强制类型转换还算比较明显。下面的情况会发生布尔值隐式强制类型转换。
(1) if (..)语句中的条件判断表达式。
(2) for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。
(3) while (..)和do..while(..)循环中的条件判断表达式。
(4) ? :中的条件判断表达式。
(5) 逻辑运算符||(逻辑或)和&&(逻辑与)左边的操作数(作为条件判断表达式)。
以上情况中,非布尔值会被隐式强制类型转换为布尔值。
总结
我们来总结一下本篇的主要内容:
- JavaScript的数据类型之间的转换,即强制类型转换:包括显式和隐式。
- 显式强制类型转换明确告诉我们哪里发生了类型转换,有助于提高代码可读性和可维护性。
- 隐式强制类型转换则没有那么明显,是其他操作的副作用。感觉上好像是显式强制类型转换的反面,实际上隐式强制类型转换也有助于提高代码的可读性。
- 解析和转换之间不是相互替代的关系。它们虽然类似,但各有各的用途。如果字符串右边的非数字字符不影响结果,就可以使用解析。而转换要求字符串中所有的字符都是数字。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)