对象 —— 原始值转换

110 阅读3分钟

这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

前言

  • 对象无法直接进行运算符的处理例如加减或直接显示(alert)

这里有个疑问,那为什么不行呢?

  • 因为obj1 + obj2 的结果不能是另一个对象(其他的运算也会这样),而解决这个问题的方法就是转换成原始值。

转换规则

  1. 无需进行布尔值转换,因为在布尔上下文均为true
  2. 数字转换主要是在对象相间和数学函数时发生 (date1 - date2)
  3. 在输出对象时会有字符串转换(alert(obj)

转换的三种变体(hint)

String

  • 对象到字符串的转换,当我们对期望一个字符串的对象执行操作时,如 “alert”:

    // 输出
    alert(obj);
    
    // 将对象作为属性键
    anotherObj[obj] = 123;
    

number

  • 对象到数字的转换,例如当我们进行数学运算时:

    // 显式转换
    let num = Number(obj);
    
    // 数学运算(除了二元加法)
    let n = +obj; // 一元加法
    let delta = date1 - date2;
    
    // 小于/大于的比较
    let greater = user1 > user2;
    

default

  • 当遇到类似 + 这种即可用于字符串(连接),也可用于数字(相加)。因此,这时就会依据“default”对齐进行转化;如果对象被用于与字符串、数字或 symbol 进行 == 比较,这时到底应该进行哪种转换也不是很明确,因此使用 "default" hint。
// 二元加法使用default hint 
let total = obj1 + obj2; 
// obj == number 使用default hint 
if (user == 1) { ... };

为了进行转换,JavaScript 尝试查找并调用三个对象方法:

  1. 调用 obj[Symbol.toPrimitive](hint) —— 带有 symbol 键 Symbol.toPrimitive(系统 symbol)的方法
  2. hint 是 "string""number" 或 "default" —— 尝试调用 obj.toString() 或 obj.valueOf(),无论哪个存在。

Symbol.toPrimitive

例如,这里 user 对象实现了它:

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
alert(user + 500); // hint: default -> 1500
  • 通过上面的代码可知,就是判断hint是什么类型的,根据不同类型获取不同的原始值进行运算/处理

toString/vauleOf

  • 有时会遇到没有Symbol.toPrimitive的情况,JS会尝试寻找toString 和 valueOf 方法

  • 字符串会优先调用toString方法,返回一个字符串 "[object Object]"。如果没有 Symbol.toPrimitive 和 valueOftoString 将处理所有原始转换。

  • 其他类型优先调用valueOf方法,返回对象自身。

下面看这段代码:

let user = {
    name: "John"
};

alert(user); // [object Object]
alert(user.valueOf() === user); // true

所以,如果我们尝试将一个对象当做字符串来使用,例如在 alert 中,那么在默认情况下我们会看到 [object Object]。而默认的 valueOf 它返回对象本身,因此被忽略。

let user = {
  name: "John",
  money: 1000,

  // 对于 hint="string"
  toString() {
    return `{name: "${this.name}"}`;
  },

  // 对于 hint="number" 或 "default"
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
  • 这段代码,我们可以看见执行的动作和前面使用 Symbol.toPrimitive 的那个例子相同。只不过是根据不同类型分别调用这两个方法而已

进一步转换

  • 有时我们将对象转换为原始值后,由于韩需要进一步计算这时需要原始值进一步转换

例如:

let obj = {
  // toString 在没有其他方法的情况下处理所有转换
  toString() {
    return "2";
  }
};

alert(obj * 2); // 4,对象被转换为原始值字符串 "2",之后它被乘法转换为数字 2。
  1. 乘法 obj * 2 首先将对象转换为原始值(字符串 “2”)。
  2. 之后 "2" * 2 变为 2 * 2(字符串被转换为数字)。

二元加法在同样的情况下会将其连接成字符串,因为它更愿意接受字符串:

let obj = {
  toString() {
    return "2";
  }
};

alert(obj + 2); // 22("2" + 2)被转换为原始值字符串 => 级联