携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
类型转换几乎所有的编程语言都有,可是我们真的理解它吗?在JavaScript中,为什么alert()方法可以输出一个对象的字符串表示呢?为什么对象之间不能相加呢?我们似乎没有好好想过这个问题。
通过阅读ES2021英文原版手册,我发现手册对类型转换进行了详细的介绍。
文中通过结合已有的知识,配合应用场景中常见的例子,参考英文官方文档,介绍对JavaScript类型转换的理解。
一、类型转换原则
在进行类型转换时,我们需要牢牢抓住两个原则:
-
转换成什么类型
- 是字符串类型转换还是布尔值类型的转换
-
转换成什么值
- 比如
Boolean("aa") = true
- 比如
-
何时转换
- 类型转换分为自动类型转换和强制类型转换
- 当我们需要时,可以使用
Number()/String()/Boolean()等函数强制转换类型 - 当对不同类型的值进行比较时,会进行自动类型转换,它们会先被转化为数字(不包括严格相等检查)再进行比较。千万注意,是同类型的值比较时才会自动类型转换
例如:
"2" > "12" → true同类型如字符串的比较规则是逐一字符比较。首位字符
"2"大于"1"。
二、普通类型的转换
在ES2020中,JavaScript一共有八种数据类型,其中引用类型1种,基本类型7种。他们分别是:
- String
- Number
- BigInt
- Null
- Undefined
- Boolean
- Symbol
- Object
1.Boolean类型
转换规则:
- 直观上为“空”的值(如
0、空字符串、null、undefined和NaN)将变为false。 - 其他值变成
true。
举例:
console.log(Boolean(1) ); // true
console.log(Boolean(0) ); // false
console.log(Boolean("aaaa") ); // true
console.log(Boolean("") ); // false
注意:
console.log(Boolean("0") ); // true
console.log(Boolean(" ") ); // 里面是一个空格,也是 true(任何非空字符串都是 true)
2.String类型转换
转换规则:
- 值加""就是字符串类型的值
可以显示地调用String(value)或者加上一个空字符串""进行字符串转换
3.Number类型转换
转换规则:
| 值 | 变成…… |
|---|---|
undefined 或 NaN | NaN |
null | 0 |
true 和 false | 1 and 0 |
string | 去掉首尾空白字符(空格、换行符 \n、制表符 \t 等)后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN。 |
普通类型转换只要记住上述的规则即可。
三、引用类型的转换
同样是牢记上述的原则,转换成何种类型?值又怎么转换?
引用类型的转换,可视为对象 -----> 原始值类型的转换。
阅读手册,我们可以得到如下的规范:
ToPrimitive ( input [ , preferredType ] ) ------> 原始值转换规则
The abstract operation ToPrimitive takes argument input (an ECMAScript language value) and optional argument preferredType (string or number) and returns either a normal completion containing an ECMAScript language value or a throw completion. It converts its input argument to a non-Object type. If an object is capable of converting to more than one primitive type, it may use the optional hint preferredType to favour that type. It performs the following steps when called:
抽象操作(ToPrimitive)接受参数输入(ECMAScript七种类型)和可选参数preferredType(string或number类型),并返回包含ECMAScript语言值或抛出一个[[Type]]。它将输入参数转换为非object类型。如果一个对象能够转换为多个基本类型,它可以使用可选提示preferredType来选择该类型。调用时,它执行以下步骤:
- 1. If Type(
input) is Object, then- a. Let
exoticToPrimbe ? GetMethod(input, @@toPrimitive). - b. If
exoticToPrimis not undefined, then- i. If
preferredTypeis not present, lethintbe "default". - ii. Else if
preferredTypeis string, lethintbe "string". - iii. Else,
- 1. Assert:
preferredTypeis number. - 2. Let
hintbe "number".
- 1. Assert:
- iv. Let
resultbe ? Call(exoticToPrim,input, «hint»). - v. If Type(
result) is not Object, returnresult. - vi. Throw a TypeError exception.
- i. If
- c. If
preferredTypeis not present, letpreferredTypebe number. - d. Return ? OrdinaryToPrimitive(
input,preferredType).
- a. Let
- 2. Return
input.
When ToPrimitive is called without a hint, then it generally behaves as if the hint were number. However, objects may over-ride this behaviour by defining a @@toPrimitive method. Of the objects defined in this specification only Dates (see 21.4.4.45) and Symbol objects (see 20.4.3.5) over-ride the default ToPrimitive behaviour. Dates treat the absence of a hint as if the hint were string.
上述两段可以总结为如下的规则:
JavaScript根据hint(译为规范)决定转换的规则
- 是否是对象?如果是对象,则
- 提供规则 hint = string ,对象转换为字符串
- 提供规则 hint = number ,对象转换为数字
- 不提供规则 hint = default ,对象默认转换为number
- 否则返回值本身
基于上述规则,JavaScript在确定完hint的值后,尝试查找并调用三个对象方法:
- If hint is string, then
- a. Let methodNames be « "toString", "valueOf" ».
- Else,
- a. Let methodNames be « "valueOf", "toString" ».
- For each element name of methodNames, do
- a. Let method be ? Get(O, name).
- b. If IsCallable(method) is true, then
- i. Let result be ? Call(method, O).
- ii. If Type(result) is not Object, return result.
- Throw a TypeError exception.
- 调用
obj[Symbol.toPrimitive](hint)—— 带有 symbol 键Symbol.toPrimitive(系统 symbol)的方法,如果这个方法存在的话,- 否则,如果 hint 是
"string"—— 尝试调用obj.toString()或obj.valueOf(),无论哪个存在。先调用obj.toString()。- 否则,如果 hint 是
"number"或"default"—— 尝试调用obj.valueOf()或obj.toString(),无论哪个存在。 最终必须返回原始值,否则会抛出异常。
默认情况下,普通对象具有 toString 和 valueOf 方法:
toString方法返回一个字符串"[object Object]"。valueOf方法返回对象自身。
示例:
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000 (1)
alert(user + 500); // hint: default -> 1500 (2)
关于hint,我的理解其是根据上下文环境判断的,比如上述(1)(2)同一个符号,但是上下文意义不同,所以决定的hint也不同,进而决定转换的实现方式也不同。
总结:
转换类型有三种(hint):string、number、default;
obj.[Symbol.toPrimitive](hint)方法可以转任何类型,如果不存在,执行下一条;
如果hint为"string",使用toString()方法,找不到就使用valueOf;
如果hint不是"string",优先使用valueOf(),不存在调用toString;
toString方法啥都能干,实际使用中,我们最常用这个;、
所有这些方法都必须返回一个原始值才能工作(如果已定义)。
在实际使用中,通常只实现 obj.toString() 作为字符串转换的“全能”方法就足够了,该方法应该返回对象的“人类可读”表示,用于日志记录或调试,这点有点类似于Java类中需要重写的toString()方法。
思维导图整理如下:
参考