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

本文需要先了解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() 作为全能转换的方法就够了