这篇文章主要记录从对象转换为 Primitive Value 的过程,至于基本类型之间的转换,可以看看其他的文章,或者直接去看规范。
首先,我们需要知道,下文中会出现的一些函数,比如 ToObject
、ToString
、ToNumber
以及 ToPrimitive
等等,都是在规范中实现的函数,我们没有办法直接通过 JavaScript 访问到。
valueOf
一般情况下,所有的对象都可访问到 valueOf
方法,比如 Array
,虽然自身没有 valueOf
方法,但是可以根据原型链从 Object.prototype
中找到。
Object
原型上的 valueOf
方法会调用规范中的 ToObject(argument)
函数。顾名思义,这个函数会返回一个对象。如果 argument
本身就是对象,就返回自身;如果是 null
与 undefined
则抛错;像 boolean
、string
、number
以及 symbol
这样的基本类型,就会返回对应的包装对象。相反,如果这些包装类型调用 valueOf
方法,则会返回对应的基本类型值,并不是返回自身,因为这些包装类型都实现了自己的 valueOf
方法。
我们可以将 ToObject()
大致类比为 Object()
,只不过后者在处理 null
和 undefined
的时候会返回 {}
。
toString
提一下 Number.prototype.toString
以及 Array.prototype.toString
吧。
-
Number.prototype.toString
接受一个参数radix
,可以是二进制,八进制等等,当然默认是十进制。 -
Array.prototype.toString
内部调用的是join
方法,join
的实现在这里
ToPrimitive(input[, preferredType])
简单讲 ToPrimitive
会将一个值转换为 Primitive Value。在梳理 ToPrimitive
的执行过程前,我们先了解一个内置的 Symbol
值。
-
Symbol.toPrimitive
这个值可以作为对象的属性名,指向一个方法,用于控制对象如何转换为 Primitive Value。这个方法会在
ToPrimitive
的执行过程中使用到。并不是所有内置对象都有这个属性的,只有Date.prototype
以及Symbol.prototype
存在这个属性。虽然普通对象上没有这个属性,但我们是可以手动添加上这个属性,比如:var o = { [Symbol.toPrimitive]: function(hint) {} }
下面就梳理下 ToPrimitive
的执行过程:
-
如果 input 的类型是
object
a. 声明一个变量
hint
,如果 preferredType 不存在,将hint
赋值为default
b. 如果 preferredType 为
string
或number
,将hint
赋值为string
或number
c. 判断 input 是否存在
Symbol.toPrimitive
属性,如果存在则调用该属性指向的方法- 如果返回值是 Primitive Value,则返回该值;否则抛错
d. 不存在该方法,当
hint
为default
是,重新赋值为number
e. 如果
hint
为string
,则按顺序调用 input 的toString
以及valueOf
方法,直到返回 Primitive Valuef. 如果
hint
为number
,则按顺序调用 input 的valueOf
以及toString
方法,直到返回 Primitive Valueg. 如果最终没有返回 Primitive Value,则抛错
-
input 本身就是一个 Primitive Value,直接返回 input
其中步骤 e-f 对应的是规范中的 OrdinaryToPrimitive 函数
ToNumber(argument)
当尝试把一个对象转换为数字时,会有以下两个步骤:
-
调用
ToPrimitive(input, Number)
,返回值为primValue
-
调用
ToNumber(primValue)
比如我们会用到的 Number(value)
,就会使用到 ToNumber
这个内部函数(前提是你传了一个参数,不然的话就直接返回 0 了)。另外,一元 +
运算也相当于 Number(value)
举个例子:Number({}) // NaN
-
将
{}
作为 argument,调用ToNumber(argument)
-
将
{}
作为 input,调用ToPrimitive(input, Number)
,hint
被赋值为number
-
因为
{}
不存在Symbol.toPrimitive
属性,所以按顺序调用valueOf
以及toString
-
调用
valueOf
返回自身,不是 Primitive Value -
调用
toString
返回[object Object]
-
将
[object Object]
返回,作为primValue
-
调用
ToNumber(primValue)
返回NaN
简单讲:一般情况下,尝试将对象转换为数字时,会调用 valueOf
以及 toString
,直到返回 Primitive Value。
ToString(argument)
与 ToNumber
类似,当尝试把对象转换为字符串时,也会有两个步骤:
-
调用
ToPrimitive(input, String)
,返回值为primValue
-
调用
ToString(primValue)
举个例子:String({}) // [object Object]
-
将
{}
作为 argument,调用ToString(argument)
-
将
{}
作为 input,调用ToPrimitive(input, String)
,hint
被赋值为string
-
因为
{}
不存在Symbol.toPrimitive
属性,所以按顺序调用toString
以及valueOf
-
调用
toString
返回[object Object]
,是一个 Primitive Value -
将
[object Object]
返回,作为primValue
-
调用
ToString(primValue)
返回[object Object]
简单讲:一般情况下,尝试将对象转换为字符串时,会调用 toString
以及 valueOf
,直至返回 Primitive Value。
二元 + 运算
我们知道 +
不仅能进行数学加法,又可以连接字符串。不仅 1 + '1'
可以执行,甚至像 null + 1
、[] + {}
等等运算都可以执行。我们可以根据规范梳理下二元 +
的运算过程,在这个过程中也用到了 ToPrimitive
函数:
-
对两个操作数调用
ToPrimitive(input)
,此时没有指定 preferredType,hint
会被赋值为default
-
判断两个返回值的类型,如果其中有一个为
string
a. 对两个返回值执行
ToString
b. 进行字符串连接
-
对两个返回值执行
ToNumber
,执行数学加法
以 [] + {}
为例,分析一下:
-
执行
ToPrimitive([])
,根据规则,会调用[].toString()
,返回值为''
-
执行
ToPrimitive({})
,同样会调用({}).toString()
,返回值为[object Object]
-
进行字符串连接得到
[object Object]
那么,如果手动改变了 valueOf
或者 toString
的行为呢,比如:
var o1 = {
valueOf: function () {
return 1
}
}
var o2 = {
toString: function () {
return 2
}
}
当执行 o1 + o2
时,最终就是执行数学加法,结果为 3。
== 运算
根据规范,x == y
运算的执行过程如下:
-
如果 x 与 y 的类型相同,执行
x === y
(===
执行步骤) -
如果 x 与 y 中,其中一个为
undefined
另一个为null
,则返回true
-
如果 x 与 y 中,其中一个为
string
另一个为number
,则返回ToNumber(one) == another
的结果 -
如果 x 与 y 中,存在一个
boolean
,则返回ToNumber(one) == another
的结果 -
如果 x 与 y 中,其中一个为
object
,另一个为任一string
、number
或者symbol
,则返回ToPrimitive(one) == another
的结果 -
以上情况之外,返回
false
我们以 [] == ![]
为例来分析这个过程:
-
![]
会调用ToBoolean([])
并取反,得到结果false
-
比较
[] == false
,根据上文步骤 4,得到[] == 0
-
根据上文步骤 5,得到
'' == 0
-
根据上文步骤 3,得到
0 == 0
-
返回
true
再看 Symbol.toPrimitive
上文提到,在一般情况下,当对象转字符串或者数字时,会调用 valueOf
以及 toString
。那么,除一般情况以外会是怎么样呢?
回到 Symbol.toPrimitive
属性,我们现在手动为普通对象添加这个属性(上文说过,除了 Date.prototype
和 Symbol.prototype
,其他对象都没有这个属性)。
var o = {
[Symbol.toPrimitive]: function (hint) {
switch (hint) {
case 'number':
return 1
case 'string':
return 'str'
case 'default':
return 'default'
default:
throw new Error()
}
}
}
Number(o) // 1
String(o) // 'str'
o + 1 // 'default1'
如果理解了上文的 ToPrimitive
函数,就可以知道:如果一个对象存在 Symbol.toPrimitive
属性,那么 valueOf
以及 toString
方法都不会被调用了。
最后
总结,emmmm...全文都是总结
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!