本文深入解析 JavaScript 中的隐式类型转换机制,涵盖 ToString、ToNumber、ToBoolean 及 ToPrimitive 等核心抽象操作。详细阐述对象在强制转换中 valueOf 与 toString 的调用优先级,并重点剖析 == 宽松相等运算符在字符串、数字、布尔及 null/undefined 比较中的具体转换规则与陷阱。
前言
深入掌握类型转换机制及其内在规律,有助于编写出更加简洁且安全的代码。
在编码过程中,我们往往有意或无意地编写包含类型转换逻辑的代码。这类代码的返回值有时令人困惑,例如以下工作中常见的表达式:
var a = 1;
var b = '2';
var c = a + b; // '12';
var d = 43;
d == '43'; // true
d === '43'; // false上述示例较为常见,接下来让我们观察几个更具代表性的案例:
// 例子1
const obj = {
toString() {
return : 'xxx';
}
}
const obj2 = {};
obj.toString(); // "xxx" 当然,我们还是可以使用Object.prototype.toString.call(obj)的,因为这是调用了Object对象,但是如果改写了Object的toString方法,那情况就不一样了。
obj2.toString(); // "[object Object]"
// 例子2
const a = [1,2,3];
const b = [4,5];
a + b; // "1,2,34,5"
// 例子3
false == []; // true
false == ""; // true
false == null; // false
false == undefined; // false
[] == ![]; // true面对上述输出结果,你是否也感到困惑?现在让我们深入探索,剖析其中涉及的类型转换过程。
在探讨强制类型转换之前,我们需要思考几个核心问题:不同数据类型的值会经历何种转换?在哪些场景下会发生转换?具体的转换机制是什么?
类型转换的发生通常源于操作值执行了【抽象操作】(ES5规范第9节定义了一系列“仅供内部使用的操作”)。此处简要介绍ToString、ToNumber、ToBoolean,同时还有一个ToPrimitive。
ToString
ToString用于对非字符串类型进行强制类型转换。
- 针对基本数据类型,ToString执行字符串化操作:null转换为"null",undefined转换为"undefined",true转换为"true"。对于数字类型的ToString,需注意极大值和极小值会以指数形式表示。
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
a.toString(); // "1.07e21";- 针对引用数据类型的ToString,通常调用对象内部的toString()方法进行转换;若对象缺乏该方法,则抛出错误。(注意:自ES5起,使用Objest.create(null)创建的对象,因其
[[Prototype]]为null,故不具备toString()和valueOf()方法)。
var a = [1,2,3,4];
a.toString(); // "1,2,3,4"
var b = {
a: 42,
foo: 'foo'
}
b.toString(); // "[object Object]"ToNumber
- 针对基本数据类型,ToNumber的行为等同于调用Number()函数转换为数字。
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
Number(""); // 0
Number("123"); // 123
Number("123aaa"); // NaN- 针对引用数据类型,首先判断对象是否包含valueOf()方法;若存在,则对返回值进行类型转换;若不存在,则判断是否包含toSrting()方法,并对其返回值进行类型转换。若两者皆无,则抛出TypeError错误。
var a = { valueOf() { return "42"} };
var b = { toString() { return "42"} };
var c = [1,2];
c.toString = function() { return this.join(""); }
Number(a); // 42
Number(b); // 42
Number(c); // 12
Number([]); // 0
Number([1,2]); // NaNToBoolean
关于抽象操作ToBoolean,ES5规范9.2节列举了部分假值,即对这些值进行布尔强制类型转换时将转为false。除这些值外,所有值进行强制类型转换时均得到true。
undefined
null
"" // 空字符串
+0, -0, NaN
falseToPrimitive
抽象操作ToPrimitive,实际上在ToSting、ToNumber、ToBoolean获取对象原始值时,正是通过ToPrimitive来获取的。获取原始值后,再执行toString()、Number()、Boolean()等类型转换。
总结而言,对值进行类型转换时,若为基本数据类型,则直接对该值执行类型转换;若为引用类型,则先通过ToPrimitive获取原始值,再基于该原始值执行类型转换。
此前所述的【先检查对象是否包含valueOf(),若无则再检查toString()】的过程即为ToPrimitive。
分析完类型转换的具体操作后,接下来我们查看在哪些操作下会触发类型转换。
运算符 +
若操作数中包含字符串,则执行字符串拼接;若操作数为对象,则对对象执行ToPrimitive抽象操作以获取原始值,通常该操作会获取到字符串。最后执行 + 运算。
var a = [1,2];
var b = [3,4];
a + b; // "1,23,4"逻辑判断相关
- if(..)
- while(..)和do..while(..)
- 三元表达式 ?:
- for(..; ..; ..){}
- 逻辑或|| 和 逻辑与&& 左边的操作数
上述语句中包含的判断表达式,均会将非布尔值通过抽象操作ToBoolean转换为boolean类型。
== 宽松对等规则
1. 字符串和数字之间的相等比较
- 若Type(x)为数字,Type(y)为字符串,则返回 x === ToNumber(y)的结果。
- 若Type(x)为字符串,Type(y)为数字,则返回 ToNumber(x) === y的结果。
var x = 42;
var y = '42';
x === y; // false
x == y; // true 这里转换为: 42 == ToNumber('42') => true2. 其他类型和布尔类型的相等比较
- 若Type(x)为布尔类型,则返回 ToNumber(x) === y 的结果。
- 若Type(y)为布尔类型,则返回 x === ToNumber(y)的结果。
var x = true;
var y = '42'
x === y; // false
x == y; // false
// false 这里转换为:toNumber(true) == toNumber('42') => 1 == 42 => false3. null 和 undefined 之间的相等比较
- 若x为null,y为undefined,则结果为true。
- 若x为undefined,y为true,则结果为true。
注意:在 == 中null和undefined相等(也与自身相等),除此之外其他值都不与他们相等。因此可利用此特性判断值是否等于null或undefined。
var x = null;
var y;
x === y; // false
x == y; //true4. 对象和非对象之间的相等比较
- 若Type(x)为字符串或数字,Type(y)为对象,则返回 x == ToPrimitive(y)的结果。
- 若Type(y)为字符串或数字,Type(x)为对象,则返回 ToPrimitive(x) == y的结果。
var a = 42
var b = [42]
a == b; // true
// [42]调用ToPrimitive,返回"42",变成 "42" == 42,然后又变成 42 == 42,最后两者相等。
var a = "abc";
var b = Object(a); // new String( a )一样
a === b; // false
a == b; // true此处存在部分值差异
var a = null;
var b = Object(a); // 和Object()一样
a == b; // false
var c = undefined;
var d = Object(c); // 和Object()一样
c == d; // false
var e = NaN;
var f = Object(e); // 和new Number(e) 一样
e == f; // NaN == NaN => false由于js中不存在undefined和null对应的封装对象,因此对他们进行Object封装与调用Object()相同,返回常规对象。
而NaN是因为其本身就不等于自身。
总结
总体而言:在宽松对比中,对比的值遵循如下转换优先级,对象 > 非对象(非对象中:字符串,布尔都会ToNumber转为数字,进行比较)。同时需注意NaN(唯一不等于自身的值)、undefined和null这几个值的转换。
当然,宽松对等中仅在不同值对比时进行类型转换。若为相同类型比较,则不进行类型转换。"0" == "" 返回false。因为这是字符串"0"和""的对比,不会执行抽象操作ToNumber。
理解了对象的类型转换均涉及ToPrimitive操作,那么需注意,任何情况下都不要重写对象的valueOf()和toString()方法。
至此,关于JavaScript隐式类型转换例子总结的文章介绍完毕,更多相关JS隐式转换内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!