彻底理解JavaScript中的类型转换(下)

简介: 彻底理解JavaScript中的类型转换(下)

彻底理解JavaScript中的类型转换(上)https://developer.aliyun.com/article/1411369


(3)数字转换

将数据类型转换为数字称为数字转换,可以使用Number()parseInt()parseFloat()等方法将数据类型显式转换为数字。当一个值不能被强制转换为一个数字时,就会返回 NaN

① 字符串 => 数字:

当把字符串转换为数字时,JavaScript 引擎首先会修剪前导和后置空格、\n\t 字符,如果修剪后的字符串不代表有效数字,则返回 NaN。 如果字符串为空,则返回 0。

Number('123');            // 123
Number("-12.34")          // -12.34
Number("12s");            // NaN
Number("\n")              // 0
parseInt(' 203px');       // 203 
parseInt('10.000')        // 10   
parseInt('10.20')         // 10 
parseFloat('203.212px');  // 203.212
parseFloat('10.20')       // 10.2
parseFloat('10.81')       // 10.81

可以看到,parseInt 函数会从字符串中读取一个数字并删除它后面所有字符,但是如果数字前面有字符(空格除外),那么它将输出 NaN

② 布尔值 => 数字:

当使用 Number() 将布尔值转化为数字时,true 会转化为 1,false 会转化为 0。

Number(true);  // 1Number(false); // 0

③ null  => 数字:

当使用 Number()null 转化为数字时,会返回 0:

Number(null); // 0null + 5; // 5

④ undefined / 数组 / 对象 / NaN => 数字:

当使用 Number()undefined、数组、对象、NaN 转化为数字时,会返回 NaN

Number(undefined);  // NaN
Number([1, 2, 3])   // NaN
Number({})          // NaN
Number(NaN)         // NaN

⑤ 数组元素

可以使用map遍历数组元,并使用需要的类型来进行类型转换:


["1", "9", "-9", "0.003", "yes"].map(Number);// 输出结果:[1, 9, -9, 0.003, NaN]

⑥ 特殊规则

在表达式中,当我们将 == 运算符应用于 nullundefined 时,不会发生数字转换。 此外,null 只等于 nullundefined,不能等于其他任何值:

null == null;           // true 
null == 0;              // false
null == undefined;      // true
undefined == undefined  // true

根据运算符优先级,+ 运算符具有从左到右的关联性,因此如果有一个表达式 2 + 3 + '4' + 'number' ,则操作按以下方式完成:

javascript

复制代码

2 + 3 + '4' + 'number'
==> 5 + '4' + 'number'
// 数字 5 被隐式转换为字符串,然后连接起来
==> '54' + 'number'
==> '54number'

NaN 不等于任何其他类型,甚至它本身:

javascript

复制代码

NaN == NaN  // false

⑦ 总结

上面的例子中,可以清楚地看到一些意想不到的结果:将 null 转换为数字时返回了 0,而将 undefined 转换为数字返回了 NaN。两个操作都应该返回 NaN,因为这两种值类型显然都不是有效的数字,将空字符串转换为数字时也返回了 0。

下面是 ECMAScript 规范中将数据类型转换为字符串的规则,清楚的解释了上面的异常现象:

12.webp.jpg

另外,在 ECMAScript 规范中,还提到一点:

13.webp.jpg

意思就是:为空或仅包含空格的 StringNumericLiteral 将转换为 +0。这也就解释了为什么将空字符串转换为数字时也返回了 0。

ECMAScript 规范:262.ecma-international.org/5.1/#sec-9.…


3. Symbol 类型转换


Symbol  只能进行显式转换,不能进行隐式转换。也就是说,Symbol不能被强制转换为字符串或数字,这样它们就不会被意外地用作本来应该表现为 Symbol 的属性。

const mySymbol = Symbol.for("mySymbol");
const str = String(mySymbol);
console.log(str);  // 'Symbol(mySymbol)'

当使用 console.log() 来打印 symbol 时,它之所以有效,是因为 console.log()symbol 上调用了 String() 方法以创建可用的结果。

如果尝试直接使用字符串连接 symbol,它将抛出TypeError

const mySymbol = Symbol.for("mySymbol");
const sum = mySymbol + "";
console.log(sum);   // Uncaught TypeError: Cannot convert a Symbol value to a string

mySymbol 连接到字符串需要首先将 mySymbol 转换为字符串,并且在检测到强制转换时会抛出错误,从而阻止以这种方式使用它。

同样,我们不能将 symbol 强制转换为数字,所有数学运算符在与符号一起使用时都会引发错误:

const mySymbol = Symbol.for("mySymbol");
const factor = mySymbol / 2;
console.log(factor);   // Uncaught TypeError: Cannot convert a Symbol value to a number


4. 对象类型转换


介绍完了基本数组类型的转化,下面来看看对象类型的转化。例如,当执行 obj_1 + obj_2 或者 obj_1 - obj_2时,都会先将对象转换为原始类型,然后将其转换为最终类型。当然,这里的转化仍然只有三种类型:数字、字符串和布尔值。

对象通过内部的 ToPrimitive 方法将其转换为原始类型,该算法允许我们根据使用对象的上下文来选择应如何转换对象。从概念上讲,ToPrimitive 算法可以分为两部分:Hints 和 Object-to-primitive 转换方法。

22.webp.jpg

(1)Hints

Hints 是 ToPrimitive 算法用于确定对象在特定上下文中应转换为什么的信号。有三种情况:

  • string:在操作需要字符串的上下文中,如果可以转换为字符串,例如 alert() 或内置 String() 函数:
alert(obj);
String(obj)
// 使用对象作为属性key值
anotherObj[obj] = 1000;
  • number:如果可以进行这种转换,则在操作需要数字的上下文中:
// 显示转换
let num = Number(obj);
// 数学(二进制加号除外)
let x = +obj; // 一元加
let difference = Date1 - Date2; // 日期对象
// 对象大小比较
let less = Obj1 < obj2;
  • default:在极少数情况下发生,不确定需要什么类型。例如,二元 + 运算符既适用于字符串(连接它们)也适用于数字(添加它们)。在这种情况下,对象可以转换为字符串或数字。 或者当使用宽松相等 == 运算符将对象与字符串、数字或 symbol 进行比较时。
// 二元加
let sum = obj1 + obj2;
// obj == string/number/symbol
if (obj == 10 ) { ... };

所有内置对象(日期除外)都将default认为是number,Date 日期对象将default认为是string

(2)Methods

在 ToPrimitive 算法根据 Hints 确定对象应转换为的原始值类型之后。 然后使用 Object-to-primitive 转换方法将对象转换为原始值。有三种情况:

  • toString/valueOftoString()valueOf() 被 JavaScript 中的所有对象继承。 它们仅用于对象到原始值的转换。 ToPrimitive 算法首先会尝试 toString() 方法。 如果定义了方法,它返回一个原始值,那么 JavaScript 使用原始值(即使它不是字符串)。 如果toString() 返回一个对象或不存在,那么 JavaScript 会尝试使用 valueOf() 方法,如果该方法存在并返回一个原始值,JavaScript 将使用该值。 否则,转换失败并提示 TypeError
  • toString -> valueOf:用于 Hints 为string 的情况。
  • valueOf -> toString:其他情况。
let Person = {
  name: "Mary",
  age: 22,
  // hint 是 "string"
  toString() {
    return `{name: "${this.name}"}`;
  },
  // hint 是 "number" 或 "default"
  valueOf() {
    return this.age;
  }
};
alert(Person);      // toString -> {name: "Mary"}
alert(+Person);     // valueOf -> 22
alert(Person + 10); // valueOf -> 32

在上面的代码中,Person 变成了一个对象字符串或数字,具体取决于转换上下文。 toString() 方法用于 Hints = "string" 的转换,valueOf() 用于其他情况(Hints 为“number”或“default”)。

你可能希望在一个地方处理所有转换。 在这种情况下,只能像这样实现 toString() 方法:

let Person = {
  name: "Mary",
  toString() {
    return this.name;
  }
};
alert(Person); // toString -> Mary
alert(Person + 1000); // toString -> Mary1000

Symbol.toPrimitive:与 toString()valueOf() 方法不同,Symbol.toPrimitive 允许覆盖 JavaScript 中的默认对象到原始值的转换(其中 toString()valueOf 方法由 ToPrimitive 算法使用)并定义我们希望如何将对象转换为原始类型的值。 为此,需要使用此 Symbol 名称定义一个方法,如下所示:

obj[Symbol.toPrimitive] = function(hint) {  // 返回原始类型值  // hint 等于 "string", "number", "default" 中的一个}

例如,这里的 Person 对象使用 Symbol.toPrimitive 执行与上面相同的操作:

j

let Person = {
  name: "Mary",
  age: 22,
  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.age;
  }
};
alert(Person);       // hint: string -> {name: "Mary"}
alert(+Person);      // hint: number -> 22
alert(Person + 10);  // hint: default -> 32

可以看到,单个方法 Person[Symbol.toPrimitive] 处理了所有转换情况。需要注意,在没有 Symbol.toPrimitivevalueOf() 的情况下,toString() 将处理所有原始类型转换。

下面是将对象转化为布尔值、字符串、数字时的执行过程:(1)对象到布尔值的转换Javascript 中的所有对象都转换为 true,包括包装对象 new Boolean(false) 和空数组。 对象到布尔值的转换不需要对象到原始类型算法。

(2)对象到字符串的转换当需要将对象转换为字符串时,Javascript 首先使用 ToPrimitive 算法(Hints = “string”)将其转换为原始类型,然后将派生的原始类型转换为字符串。例如,如果将对象传递给 String() 这样的内置函数,或者在模板字符串中插入对象时。

(3)对象到数字的转换当需要将对象转换为数字时,Javascript 首先使用  ToPrimitive  算法(Hints = “number”)将其转换为原始类型,然后将派生的原始类型转换为数字。 期望数字参数的内置 Javascript 函数和方法以这种方式将对象参数转换为数字,例如 Math()。

(3)特殊情况

当某些 Javascript 运算符的操作数是对象时,也会发生类型转换:

  • + 运算符: 此运算符可以用于执行数字加法和字符串连接。如果其中任何一个操作数是对象,则使用  ToPrimitive 算法(Hints = “default”)将它们转换为原始值。一旦将它们转换为原始值,就会检查它们的类型。如果任一参数是字符串,则将另一个参数转换为字符串并连接字符串。否则,它将两个参数都转换为数字并将它们相加。
  • == 和 !== 运算符: 这些运算符以宽松方式执行相等和不相等测试。如果一个操作数是一个对象而另一个是一个原始值,这些运算符使用  ToPrimitive  算法(Hints = “default”)将对象转换为原始值,然后比较两个原始值。
  • <,<=,> 和 >= 关系运算符: 关系运算符用于比较两个值之间的关系,可用于比较数字和字符串。如果任一操作数是对象,则使用 ToPrimitive 算法将其转换为原始值(Hints = “number”)。但是,与对象到数字的转换不同,返回的原始值不会转换为数字(因为它们被比较并且不被使用)。

参考:


相关文章
|
1月前
|
JavaScript 前端开发
JavaScript基础语法(类型转换)
JavaScript基础语法(类型转换)
33 0
|
7月前
|
JavaScript 前端开发
js中的类型转换
js中的类型转换
37 0
|
1月前
|
JavaScript 前端开发
JavaScript变量、数据类型、运算符及类型转换
JavaScript变量、数据类型、运算符及类型转换
47 0
|
10月前
|
JavaScript 前端开发 安全
深度剖析之由浅入深揭秘JavaScript类型转换(最全总结篇)(三)
深度剖析之由浅入深揭秘JavaScript类型转换(最全总结篇)(三)
|
1月前
|
JavaScript 前端开发 Python
javascript中的强制类型转换和自动类型转换
javascript中的强制类型转换和自动类型转换
|
1月前
|
JavaScript 前端开发 编译器
彻底理解JavaScript中的类型转换(上)
彻底理解JavaScript中的类型转换
120 0
|
1月前
|
JavaScript 前端开发
javascript中的类型转换
javascript中的类型转换
|
1月前
|
JavaScript 前端开发
【Javascript保姆级教程】显示类型转换和隐式类型转换
【Javascript保姆级教程】显示类型转换和隐式类型转换
101 0
|
1月前
|
JavaScript
JS 类型转换
JS 类型转换
28 0
|
7月前
|
存储 JavaScript 前端开发
Javascript数据类型和类型转换
Javascript数据类型和类型转换
34 0