从 ES 规范的角度讲解 +、Number 类型转换

734 阅读1分钟

分析下面代码的执行流程。

var obj = {
  value: 3,
  valueOf() {
    return 4;
  },
  toString() {
    return '5'
  },
}
+obj // 4
''+obj // '4'
`${obj}` // '5'


ToPrimitive 转换为原始值

Symbol.toPrimitive

原始值 primitive valueUndefined, Null, Boolean, Number, Symbol, String 之一,原始值是直接在语言实现的最低级别表示的数据。

当对象要被转换为数字或者字符串时,将会涉及到 ToPrimitive ( input [ , PreferredType ] ) 转换。

  • PreferredType 表示对象将要转换的类型
    • PreferredType 未传递,则令 hintdefault
    • PreferredType 为 String,则 hintstring
    • PreferredType 为 Number,则 hintnumber

Symbol.toPrimitive 属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 "number"、"string" 和 "default" 中的任意一个。

通过Symbol.toPrimitive 我们可以自定义在转换为目标原始值时的一些行为和返回值(返回值不能是非原始值,否则报错),如果没有这个属性,则会调用 valueOf 或者 toString

var obj = {
  value: 3,
  valueOf() {
    return 4;
  },
  toString() {
    return '5'
  },
  [Symbol.toPrimitive](hint) {
    console.log('hint', hint)
    return 6
  }
}

image

valueOftoString

如果没有定义 Symbol.toPrimitive,那么转换会通过 valueOftoStirng 处理之后返回。下面看看转换顺序和规则。

如果 hint 推断类型是 default(Date 对象除外) ,则认为它是 number,现在就只有 numberstring 两种类型。

  • hintstring,即要转换为字符串,则先用 toString处理,如果是原始值则返回,否则再用 valueOf 处理,如果返回是原始值则返回,否则会报 TypeError 错误;
  • hintnumber,则先用 valueOf 再用 toStringvalueOf 执行能返回原始值则直接返回不会执行 toString,如果 toString 返回的结果不是原始值则会报 TypeError 错误;

上面的规则应用于大多数场景。但对 Date 不适用,日期对象没有 hint,认为 hintstring 如下面的例子,'' + d,对一般对象,hintdefault,由前面知道 defaultnumber,对日期对象 hintstring

看下面三个例子

对象默认的 valueOf 返回自己。

var obj = {}

obj === obj.valueOf() // true
var obj = {
  value: 3,
  valueOf() {
	console.log('valueof')
    return this;
  },
  toString() {
    return '5'
  },
}

+obj
// valueof   因为要转换位数字,所以执行了 valueOf,但是返回是对象,所以又执行 toString
// 5
var d = new Date()

+d // 1571718957902 时间戳
''+d // "Tue Oct 22 2019 12:35:57 GMT+0800 (China Standard Time)" 字符串,涉及到先转换后拼接
`${d}` // "Tue Oct 22 2019 12:35:57 GMT+0800 (China Standard Time)" 字符串

一元 + 运算符

+'5'

规范

ToNumber(GetValue(expr))

概括来说就是转换时 hintnumber,对于原始值类型(Undefined, Null, Boolean, Number, Symbol, String),直接使用内部 ToNumber 转换成数字,下面是转换规则。对于对象,先使用 ToPrimitive 再使用 ToNumber

image

来几个例子。

+undefined // NaN
+null  // 0
+true // 1
+false // 0
+'6.11' // 6.11
+'6.1e15' // 6100000000000000
+'6.1e21' // 6.1e+21
+'  6  ' // 6
+'  6 7 8 ' // NaN
+'0xf' // 15
+'f' // NaN

var obj = {
  value: 3,
  valueOf() {
    return 4;
  },
  [Symbol.toPrimitive](hint) {
    console.log('hint', hint)
    return 6
  }
}
+obj
// hint number
// 6

var obj = {
  value: 3,
  valueOf() {
    return 4;
  },
}
+obj
// 4

Number

Number 当做一个函数来使用。

规范

如果提供了 value,返回 ToNumber(value) 计算出的数字值(非 Number 对象),否则返回 +0。

从规范看出,Number+ 运算符少了 GetValueGetValue 有和没有对原始值是没有影响的,只对对象有影响。

Number(x)+x 的结果在大多数时候可以认为是完全一样的,处理字符串时完全可以互换。

参考


今天的文章就到这里,感谢阅读~

欢迎大家关注我的掘金和公众号,专注于提升前端程序员的核心竞争力