• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

4.5 对象到原始类型转换

JS 教程 tiantian 24次浏览 0个评论 扫描二维码

如果应用加法运算符:obj1 + obj2,算法运算符:obj1 - obj2 和 打印操作符 alert(obj) 到对象上,会是什么结果呢?

实际上,对象中存在几个特殊的方法用来进行转换操作。

在前面的类型转换 章节中,我们已经学习过了数值,字符串和布尔类型等这些原始类型之间的转换。但是,我们遗留了一个关于对象转换的部分。现在,我们已经学习和掌握了方法和symbol类型的知识,我们可以来补充该部分的内容了。

对于对象而言,不存在转换为布尔类型的情况,因为所有的对象转换为布尔类型都是为true。所以,只存在转换为数值和字符串类型的转换。

当我们在对象上应用减法操作,或者数学函数时,对象才会被转换成数值。举个例子,Date对象(将在日期与时间章节详细讲解)可以进行减法操作,相减操作date1 - date2的结果是那两个日期的差值。

对于字符串转换——它常常发生于我们输出一个对象时,比如alert(obj),或者相似的场景中。

ToPrimitive

当将一个对象用于需要一个原始类型的上下文时,举个例子,在一个alert函数或者数学操作符中,该对象就会使用ToPrimitive算法(规范)来转换为一个原始类型值。

那个算法允许我们使用一个特殊的对象方法来自定义这个转换过程。

依据上下文的不同,那个转换具有一个所谓的“hint”(提示)。

一共存在三个变种:

“string”

当某个操作期待一个字符串时,就执行对象到字符串的转换,比如alert:

// output
alert(obj);

// using object as a property key
anotherObj[obj] = 123;

“number”

当某个操作期待一个数值时,就执行对象到数值的转换,比如数学:

// explicit conversion
let num = Number(obj);

// maths (except binary plus)
let n = +obj; // unary plus
let delta = date1 - date2;

// less/greater comparison
let greater = user1 > user2;

“default”

发生的概率很小,只在操作符无法确定所需类型的情况下发生。

举个例子,两元操作符加号+可在操作数为字符串(连接它们)和操作数为数值(累加它们)的情况下都能正确执行,所以对象转换为字符串或者数值类型都是合法的。或者,当对象使用相等比较符==来比较字符串,数值或者symbol时。

// binary plus
let total = car1 + car2;

// obj == string/number/symbol
if (user == 1) { ... };

那个大于/小于比较操作符<>也可以在操作数为字符串或者数值的情况下正常工作。但是,它会使用“数值”提示,而不是“默认”提示。这是由历史原因造成的。

在实践中,所有的内建对象,除了一个另外(Date对象,我们将很快学习它),实现的“默认”转换算法与“数值”转换算法相同。所以,也许我们也应该遵守这个不成文的约定。

请注意——一共只存在三个提示。这样可以简化程序逻辑。不存在所谓的“布尔”提示(所有的对象转换成布尔类型都是为true)或者其他类型提示。而且,如果我们将“默认”提示与“数值”提示具有相同的效果,就像绝大多数内建的对象那样,那么就可以认为只存在两种类型的转换。

**为了执行转换,JavaScript 会尝试查找和调用三个对象方法:

  1. 如果存在方法obj[Symbol.toPrimitive](hint),就调用该方法,
  2. 否则,如果提示是”字符串”,尝试调用 obj.toString() 和 obj.valueOf() 中的一个,
  3. 否则,如果提示是”数值”或者”默认”,尝试调用 obj.valueOf() 和 obj.toString() 中的一个。

Symbol.toPrimitive

现在,我们从第一个方法开始。存在一个内建的 symbol,名为Symbol.toPrimitive,应该被用来命名那个转换方法,比如:

obj[Symbol.toPrimitive] = function(hint) {
  // return a primitive value
  // hint = one of "string", "number", "default"
}

比如,此处的user对象就实现了该方法:

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

  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money;
  }
};

// conversions demo:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500

正如我们所看见的那样,更具转换的不同,user变成了一个可以自我描述的字符串或者一定数量的金钱。那个单一的方法user[Symbol.toPrimitive]处理了所有的转换情况。

toString/valueOf

方法toStringvalueOf在JavaScript 的早期就存在。它们不是 symbol类型,symbole 类型在JavaScript 的早期也是不存在的,但是它们也不是普通的字符串类型的方法。它们提供了一种古老的转换类型的方式。

如果不存在Symbol.toPrimitive方法,那么 JavaScript 会尝试查找这些方法,并以一定的顺序应用它们:

  • toString -> valueOf 对于“字符串”提示。
  • valueOf -> toString 其他情况。

举个例子,此处的user利用toStringvalueOf的组合使用了上述示例代码相同的功能:

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

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

  // for hint="number" or "default"
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500

通常,我们希望一个单一的“全功能”函数可以处理所有类型的转换。在这种情况下,我们可以只实现toString方法,比如:

let user = {
  name: "John",

  toString() {
    return this.name;
  }
};

alert(user); // toString -> John
alert(user + 500); // toString -> John500

在对象缺失Symbol.toPrimitivevalueOf方法的情况下,toString方法会处理所有的原始类型转换。

ToPrimitive and ToString/ToNumber

关于那些原始类型转换方法的一个很重要信息就是:它们没有必要一定返回提示所表明的那种原始数据类型。

JavaScript 规范中并没有固定toString()方法的返回值一定是一个字符串,或者Symbol.toPrimitive方法在提示为数值的情况下一定返回一个数值。

唯一的约束是:它们的返回值必须是一个原始类型

某个操作启动那个原始类型转换获得那个原始值,然后在此基础上继续处理该值,可以在需要时继续应用合适的转换操作。

举个例子:

  • 数学操作(除了那个两元加号操作符)都倾向于ToNumber转换:
let obj = {
  toString() { // toString handles all conversions in the absence of other methods
    return "2";
  }
};

alert(obj * 2); // 4, ToPrimitive gives "2", then it becomes 2
  • 两元加号操作符检查原始类型操作数——如果为字符串,就执行字符串连接,否则的话就执行ToNumber,并进行数值运算。

字符串示例:

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

alert(obj + 2); // 22 (ToPrimitive returned string => concatenation)

数值示例:

let obj = {
  toString() {
    return true;
  }
};

alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber)

历史

因为历史的原因,方法toStringvalueOf应该返回一个原始类型:如果它们返回值是一个对象,并不会产生任何的错误,但是该方法会被忽略(好像该方法不存在)。

相反,Symbol.toPrimitive必须返回一个原始类型,否则的话,会产生一个错误。

总结

在JavaScript 中,很多内建函数或者操作符在期待一个原始类型的数据作为操作数时,往往会自动调用它们的对象到原始类型的转换。

存在三种类型的提示:

  • “string” 对于alert或者其他的字符串转换。
  • “number” 对于数学操作。
  • “default” 少量的操作符。

JavaScript 的规范详细和明确地描述了不同的提示对应不同的转换操作。当然,也存在少量不明确的操作符,它们不清楚自己想要的原始类型,所以使用”默认“提示。一般地,对于内建的对象而言,”默认“提示通常与”数值“提示具有相同的处理逻辑,所以,在实践中,上述的后两个提示经常被一起处理。

整个的转化算法简化如下:

  1. 如果方法存在,就调用 obj[Symbol.toPrimitive](hint)
  2. 否则的话,如果提示为”字符串“,就依次尝试 obj.toString() 和 obj.valueOf() 方法。
  3. 否则的话,如果提示为“数值”或者“默认”,就依次尝试 obj.valueOf() 和 obj.toString() 方法。

在实践中,只实现一个obj.toString()方法,作为一个处理所有转换类型的”全能型方法”也是足够的,它可以返回一个人类可读的对象表达式,可用于日志记录或者代码调试。


天天编码 , 版权所有丨本文标题:4.5 对象到原始类型转换
转载请保留页面地址:http://www.tiantianbianma.com/object-to-primitive.html/
喜欢 (0)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址