类型转换
JavaScript是弱类型语言,这意味着它不像Java,C++一样的强类型语言有预先确定的类型。你可以使用与预期类型不同类型的值,并且Javascript将为你转换它为正确的类型,这种被动的类型转换被称为隐式类型转换或强制类型转换。通常在JS当中只存在其他类型向String,Number,Boolean的转换。
Number()转换规则
- 数值:直接返回。
- 布尔值:
true转换为1,false转换为0。 - null:返回
0。 - undefined:返回
NaN。 - 字符串:应用以下规则:
- 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。
- 如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)。
- 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值。
- 如果是空字符串(不包含字符),则返回0。
- 如果字符串包含除上述情况之外的其他字符,则返回NaN。
- Symbol:会抛出
TypeError错误。 - 对象:调用
valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。
NOTE
一元加操作符与Number()函数遵循相同的转换规则,属于强制数字转换,可用于快捷转换字符串类型的数字。
String()转换规则
- 字符串:直接返回原字符串。
- 数值:直接转换为相应的字符串形式。例如,
123转换为"123",0转换为"0"。 - 布尔值:
true转换为"true",false转换为"false"。 - null:转换为
"null"字符串。 - undefined:转换为
"undefined"字符串。 - Symbol:直接转换为字符串。例如,
Symbol(description)转换为"Symbol(description)"。 - 对象:调用
toString()方法并返回结果。如果toString()方法返回对象而非原始类型,再调用valueOf()方法,并将其结果转换为字符串。- 如果
toString()方法存在并返回原始类型(通常是字符串),直接使用此结果。 - 如果
valueOf()返回原始类型,则使用其字符串形式。 - 如果两个方法都没有返回原始类型,将抛出
TypeError错误。
- 如果
Boolean()转换规则
- 数值:
0和NaN转换为false。- 任何非零数值(包括正数和负数)转换为
true。
- 字符串:
- 空字符串
""转换为false。 - 非空字符串(包括包含空格的字符串)转换为
true。
- 空字符串
- null:转换为
false。 - undefined:转换为
false。 - Symbol:任何Symbol值转换为
true。 - 对象:
- 任何对象(包括空对象、数组、函数等)转换为
true。 - 即使是空数组
[]或空对象{},也转换为true。
- 任何对象(包括空对象、数组、函数等)转换为
NOTE
在条件判断中(如if语句中),JavaScript会自动使用Boolean()的转换规则,将表达式转换为布尔值以决定代码的执行路径。
if({}){
console.log(true)
}
// trueNOTE
逻辑非操作符也可以用于把任意值转换为布尔值。同时使用两个叹号(!!),相当于调用了转型函数Boolean()。
Object
JavaScript中的对象转换为原始类型主要依赖于
[Symbol.toPrimitive]、valueOf()和toString()方法。
当JavaScript需要将一个对象转换为原始类型时,它会按照以下步骤进行:
首先检查对象是否有[Symbol.toPrimitive](ES6之后可手动定义)方法:
如果对象定义了这个方法,JavaScript会调用它,传递一个提示参数(hint)来指定期望的转换类型。注意,[Symbol.toPrimitive]只被允许返回一个原始类型值。
let obj = {
[Symbol.toPrimitive](hint) {
if (hint === "number") {
return 10;
}
if (hint === "string") {
return "String value";
}
return true;
}
};
console.log(Number(obj)); // 输出: 10
console.log(String(obj)); // 输出: "String value"
console.log(obj + 1); // 输出: "true1" (默认转换为布尔值 true)NOTE
hint参数提示了期望的原始类型,它可以是"string"、"number"或"default"
如果没有定义[Symbol.toPrimitive]:
强制基本类型转换和强制数字类型转换
hint为"default"或"number"
JavaScript首先调用valueOf()方法。如果valueOf()返回原始类型(如数字、字符串、布尔值),则使用该值。如果返回的是对象,JavaScript会继续调用toString()方法并使用它的返回值。
强制字符串转换
hint为"string"
JavaScript首先调用toString()方法。如果toString()返回原始类型,则使用该值。如果返回的是对象,JavaScript会调用valueOf()方法并使用它的返回值。
如果这两个方法都返回对象而不是原始值,那么JavaScript会抛出一个TypeError错误,表示无法进行有效的转换。
总结
Boolean:所有对象(包括空对象、数组等)都转换为true。
Number:优先调用valueOf(),然后调用toString(),无法转换则返回NaN。
String:优先调用toString(),然后调用valueOf(),如果都无法返回原始值,抛出错误。
加法操作符
操作数均为数值
- 如果有任一操作数是
NaN,则返回NaN; - 如果是
Infinity加Infinity,则返回Infinity; - 如果是
-Infinity加-Infinity,则返回-Infinity; - 如果是
Infinity加-Infinity,则返回NaN; - 如果是
+0加+0,则返回+0; - 如果是
-0加+0,则返回+0; - 如果是
-0加-0,则返回-0。
至少一个操作数为字符串
- 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
- 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
任一操作数是对象、布尔值或null undefined
- 对象:JavaScript首先调用
valueOf()方法。如果返回原始值(如数字或字符串),则直接使用;如果不是,则调用toString()方法获取字符串表示,然后拼接。 - 布尔值:会被自动转换为字符串,再应用前面的字符串拼接规则。
undefined和null:分别调用String()函数,将它们转换为"undefined"和"null",再拼接。
关系操作符
一般比较
- 如果操作数都是数值,则执行数值比较。
- 如果操作数都是字符串,则比较字符串中第一个字符的编码,逐字符进行比较。
- 如果有任一操作数是数值,则将另一个操作数尝试转换为数值,执行数值比较。
- 如果有任一操作数是对象,则调用其
valueOf()方法,取得结果后再根据前面的规则执行比较。如果valueOf()结果不是原始值,则调用toString()方法,再根据字符串的规则执行比较。 - 如果有任一操作数是布尔值,则将其转换为数值再执行比较,
true转换为1,false转换为0。
特殊情况
null被转换为0,参与数值比较。undefined被转换为NaN,任何与NaN的比较结果都为false。
相等操作符
Javascript中有三种不同的相等性判断:
==、===和Object.is()。 同值相等Object.is()与零值相等并不常用,不在此赘述。
严格相等
- 非相同类型的比较直接返回
false。 - 相同类型相同值的比较一般返回
true,但有以下特殊情况:NaN与任何值(包括自身)的比较结果都为false。0与-0被认为是相等的,因此0 === -0返回true。- 只有当两个对象引用同一内存地址时,比较结果才为
true。
NOTE
在日常中使用严格相等几乎总是正确的选择。
宽松相等
- 相同类型的数据比较与严格相等相同。
- 非相同类型的数据比较时会发生隐式类型转换。
null和undefined在宽松相等判断中被认为是相等的,但它们与其他类型(如数值、布尔值、字符串等)不相等。- 当一方为数值类型时,会尝试将另一方按照
Number()规则进行转换后比较。 - 当一方为布尔值时,会将布尔值转化为数值,然后根据第2条规则进行比较。
- 当一方为字符串时,会将另一方转化为字符串进行比较。
Symbol与Symbol.for()
// Symbol()
let sym1 = Symbol('foo');
let sym2 = Symbol('foo');
console.log(sym1 == sym2); // false, 即使描述相同,Symbol 是唯一的
console.log(sym1 === sym2); // false, 同样严格相等也为 false
let sym3 = sym1;
console.log(sym1 == sym3); // true, 因为 sym1 和 sym3 引用同一个 Symbol 实例
// Symbol.for()
let globalSym1 = Symbol.for('foo');
let globalSym2 = Symbol.for('foo');
console.log(globalSym1 == globalSym2); // true, 因为它们通过相同的字符串在全局注册表中创建NOTE
在大多数情况下,不建议使用宽松相等。使用严格相等进行比较的结果更容易预测,并且由于缺少类型强制转换可以更快地执行。
