对象 - 原始类型转换 - Object 基础

  1. 概述
  2. 转换变体
  3. 三个对象方法
  4. Symbol.toPrimitive
  5. toString&valueOf
  6. 多重转换
  7. 总结

本文需要先了解 Symbol

概述

  1. 所有对象在布尔上下文中均为 true。所以,对象不存在布尔值转换问题。只有字符串和数值转换。
  2. 数值转换:对象相减 - 或者应用数学函数时。例如两个 Date 对象相减。
  3. 字符串转换,发生在期望需要字符串的上下文时,例如 alert 函数。

转换变体

在转换时,hint 用来指代具体哪种原始值的上下文。
下面是三个类型转换的变体,hint 值分别为:

  • "string"。对象到字符串的转换,当我们对期望一个字符串的对象执行操作时. 例如 alert 方法
  • "number"。对象到数字的转换,例如进行数学运算(** 二元加法除外 **)时。
  • "default"。只在少数情况下发生。当不能仅仅由运算符确定期望的类型时。例如,二原加运算符在有字符串参与运算时,可以连接字符串,在数学运算时,可以执行加法。此时 hint 就是 default。还有一种情况是,对象使用 == 与字符串,数字,或者 symbol 比较时。也是用 default 的 hint。

大于,小于虽然也算不确定情况(既可以比较字符串也可以比较数字),但是由于历史原因,对象参与这两个运算时的 hint 是 “number”。

三个对象方法

上面说的 hint 还要看对象有没有对应的方法:

  1. 若存在 实例对象[Symbol.toPrimitive](hint) 方法,则根据上面的描述决定传入的 hint 是 "string", "number" 还是 "default"
  2. 不存在上面的 1 的方法,且 hint 是 "string",则尝试 实例对象. toString()实例对象. valueOf()
  3. 不存在上面的 1 的方法,且 hint 是 number 或者 default,尝试 实例对象. valueOf()实例对象. toString()

Symbol.toPrimitive

此内建 symbol 用来给 ** 转换方法 ** 命名:

let obj = {age:999,name:'路由器'};
obj + 10; =>'[object Object]10'
obj[Symbol.toPrimitive] = function(hint) {
    console.log(hint);
    return hint === 'string' ? this.name : this.age;
}
obj + 10; => hint:number
obj + "10"; => hint:default
+obj; => hint:default
obj > 9; => hint:number
obj <= 1000; => hint:number

可以看出,obj[Symbol.toPrimitive] 根据转换的上下文的不同,obj 可以是字符串,也可以是数值。而且一般做法是将 default 和 number 视作同一个情况。

Symbol.toPrimitive 方法必须返回原始值(number, string,boolean,null, undefined),否则在进行类型转换时会报错!!!

toString&valueOf

还没有 symbol 的时候就有了这两个方法,用来实现转换。
就像上面说的,没有 Symbol.toPrimitive 时,JavaScript 尝试使用这两个方法。

顺序是:

  • 对于 "string"hint,toString 优先于 valueOf
  • 其他情况,valueOf 优先于 toString

默认情况下,普通对象具有 toStringvalueOf 方法:

  • toString 方法默认返回一个字符串 “[object Object]”
  • valueOf 方法默认返回对象本身

测试执行顺序:

let obj = {
    toString() {
        return "2";
    }
};
obj * 4 => 8

这是 number 上下文,首先寻找 valueOf 找不到,toString 找到了,就回去执行 toString,得到字符串,然后尝试 string 到 number 转换。

在期望需要字符串的上下文时:

let user = {name: "John"};
alert(user); // [object Object]
alert(user.valueOf() === user); // true

所以,如果我们尝试将一个对象当做字符串来使用,例如在 alert 中,那么在默认情况下我们会
看到 [object Object]

自己实现这两个方法时,需要注意:

必须返回原始值,否则对象在需要进行类型转换时,你的非原始类型的返回值变得毫无意义。

现在,实现一个同前面的 obj[Symbol.toPrimitive] 一样功能的方法:

let obj2 = {
    age: 666,
    desp: '描述',
}

obj2.toString = function () {
    return this.desp;
}

obj2.valueOf = function () {
    return this.age;
}

多重转换

  1. 对象被转换为原始值(通过前面我们描述的规则)。
  2. 如果生成的原始值的类型不正确,则继续进行转换。

总结

通常对于内建对象, “default” hint 的处理方式与 “number” 相同,因此在实践中,最后两个 hint 常常合并在一起。
转换算法:

  1. 调用 obj[Symbol.toPrimitive](hint) 如果这个方法存在,
  2. 否则,如果 hint 是 “string”
    1. 先尝试 obj.toString(),如果 toString 的返回值为原始类型,则得到此值就此结束,如果返回值为其他类型,则返回对象本身。
    2. 没有 obj.toString() 则去调用 valueOf, 如果 valueOf 的返回值为原始类型,则得到此值就此结束,如果返回值为其他类型,则返回对象本身
  3. 否则,如果 hint 是 number,则先尝试 valueOf,再尝试 toString。

在实践中,为了便于进行日志记录或调试,对于所有能够返回一种 “可读性好” 的对象的表达形式的
转换,只实现以 obj.toString() 作为全能转换的方法就够了


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
我的空间