We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
这篇文章主要记录从对象转换为 Primitive Value 的过程,至于基本类型之间的转换,可以看看其他的文章,或者直接去看规范。 首先,我们需要知道,下文中会出现的一些函数,比如 ToObject、ToString、ToNumber 以及 ToPrimitive 等等,都是在规范中实现的函数,我们没有办法直接通过 JavaScript 访问到。
ToObject
ToString
ToNumber
ToPrimitive
valueOf() 方法返回指定对象的原始值。 来自MDN
valueOf() 方法返回指定对象的原始值。
valueOf()
来自MDN
一般情况下,所有的对象都可访问到 valueOf 方法,比如 Array,虽然自身没有 valueOf 方法,但是可以根据原型链从 Object.prototype 中找到。
valueOf
Array
Object.prototype
Object 原型上的 valueOf 方法会调用规范中的 ToObject(argument) 函数。顾名思义,这个函数会返回一个对象。如果 argument 本身就是对象,就返回自身;如果是 null 与 undefined 则抛错;像 boolean、string、number 以及 symbol 这样的基本类型,就会返回对应的包装对象。相反,如果这些包装类型调用 valueOf 方法,则会返回对应的基本类型值,并不是返回自身,因为这些包装类型都实现了自己的 valueOf 方法。
Object
ToObject(argument)
argument
null
undefined
boolean
string
number
symbol
我们可以将 ToObject() 大致类比为 Object(),只不过后者在处理 null 和 undefined 的时候会返回 {}。
ToObject()
Object()
{}
toString() 方法返回一个表示该对象的字符串。 来自MDN
toString() 方法返回一个表示该对象的字符串。
toString()
提一下 Number.prototype.toString 以及 Array.prototype.toString 吧。
Number.prototype.toString
Array.prototype.toString
Number.prototype.toString 接受一个参数 radix,可以是二进制,八进制等等,当然默认是十进制。
radix
Array.prototype.toString 内部调用的是 join 方法,join 的实现在这里
join
The abstract operation ToPrimitive converts its input argument to a non-Object type. 来自规范
The abstract operation ToPrimitive converts its input argument to a non-Object type.
来自规范
简单讲 ToPrimitive 会将一个值转换为 Primitive Value。在梳理 ToPrimitive 的执行过程前,我们先了解一个内置的 Symbol 值。
Symbol
Symbol.toPrimitive
这个值可以作为对象的属性名,指向一个方法,用于控制对象如何转换为 Primitive Value。这个方法会在 ToPrimitive 的执行过程中使用到。并不是所有内置对象都有这个属性的,只有 Date.prototype 以及 Symbol.prototype 存在这个属性。虽然普通对象上没有这个属性,但我们是可以手动添加上这个属性,比如:
Date.prototype
Symbol.prototype
var o = { [Symbol.toPrimitive]: function(hint) {} }
下面就梳理下 ToPrimitive 的执行过程:
如果 input 的类型是 object
object
a. 声明一个变量 hint,如果 preferredType 不存在,将 hint 赋值为 default
hint
default
b. 如果 preferredType 为 string 或 number,将 hint 赋值为 string 或 number
c. 判断 input 是否存在 Symbol.toPrimitive 属性,如果存在则调用该属性指向的方法
d. 不存在该方法,当 hint 为 default 是,重新赋值为 number
e. 如果 hint 为 string,则按顺序调用 input 的 toString 以及 valueOf 方法,直到返回 Primitive Value
toString
f. 如果 hint 为 number,则按顺序调用 input 的 valueOf 以及 toString 方法,直到返回 Primitive Value
g. 如果最终没有返回 Primitive Value,则抛错
input 本身就是一个 Primitive Value,直接返回 input
其中步骤 e-f 对应的是规范中的 OrdinaryToPrimitive 函数
当尝试把一个对象转换为数字时,会有以下两个步骤:
调用 ToPrimitive(input, Number),返回值为 primValue
ToPrimitive(input, Number)
primValue
调用 ToNumber(primValue)
ToNumber(primValue)
比如我们会用到的 Number(value),就会使用到 ToNumber 这个内部函数(前提是你传了一个参数,不然的话就直接返回 0 了)。另外,一元 + 运算也相当于 Number(value)
Number(value)
+
举个例子:Number({}) // NaN
Number({}) // NaN
将 {} 作为 argument,调用 ToNumber(argument)
ToNumber(argument)
将 {} 作为 input,调用 ToPrimitive(input, Number),hint 被赋值为 number
因为 {} 不存在 Symbol.toPrimitive 属性,所以按顺序调用 valueOf 以及 toString
调用 valueOf 返回自身,不是 Primitive Value
调用 toString 返回 [object Object]
[object Object]
将 [object Object] 返回,作为 primValue
调用 ToNumber(primValue) 返回 NaN
NaN
简单讲:一般情况下,尝试将对象转换为数字时,会调用 valueOf 以及 toString,直到返回 Primitive Value。
与 ToNumber 类似,当尝试把对象转换为字符串时,也会有两个步骤:
调用 ToPrimitive(input, String),返回值为 primValue
ToPrimitive(input, String)
调用 ToString(primValue)
ToString(primValue)
举个例子:String({}) // [object Object]
String({}) // [object Object]
将 {} 作为 argument,调用 ToString(argument)
ToString(argument)
将 {} 作为 input,调用 ToPrimitive(input, String),hint 被赋值为 string
因为 {} 不存在 Symbol.toPrimitive 属性,所以按顺序调用 toString 以及 valueOf
调用 toString 返回 [object Object],是一个 Primitive Value
调用 ToString(primValue) 返回 [object Object]
简单讲:一般情况下,尝试将对象转换为字符串时,会调用 toString 以及 valueOf,直至返回 Primitive Value。
我们知道 + 不仅能进行数学加法,又可以连接字符串。不仅 1 + '1' 可以执行,甚至像 null + 1、[] + {} 等等运算都可以执行。我们可以根据规范梳理下二元 + 的运算过程,在这个过程中也用到了 ToPrimitive 函数:
1 + '1'
null + 1
[] + {}
对两个操作数调用 ToPrimitive(input),此时没有指定 preferredType,hint 会被赋值为 default
ToPrimitive(input)
判断两个返回值的类型,如果其中有一个为 string
a. 对两个返回值执行 ToString
b. 进行字符串连接
对两个返回值执行 ToNumber,执行数学加法
以 [] + {} 为例,分析一下:
执行 ToPrimitive([]),根据规则,会调用 [].toString(),返回值为 ''
ToPrimitive([])
[].toString()
''
执行 ToPrimitive({}),同样会调用 ({}).toString(),返回值为 [object Object]
ToPrimitive({})
({}).toString()
进行字符串连接得到 [object Object]
那么,如果手动改变了 valueOf 或者 toString 的行为呢,比如:
var o1 = { valueOf: function () { return 1 } } var o2 = { toString: function () { return 2 } }
当执行 o1 + o2 时,最终就是执行数学加法,结果为 3。
o1 + o2
根据规范,x == y 运算的执行过程如下:
x == y
如果 x 与 y 的类型相同,执行 x === y(=== 执行步骤)
x === y
===
如果 x 与 y 中,其中一个为 undefined 另一个为 null,则返回 true
true
如果 x 与 y 中,其中一个为 string 另一个为 number,则返回 ToNumber(one) == another 的结果
ToNumber(one) == another
如果 x 与 y 中,存在一个 boolean,则返回 ToNumber(one) == another 的结果
如果 x 与 y 中,其中一个为 object,另一个为任一 string、number 或者 symbol,则返回 ToPrimitive(one) == another 的结果
ToPrimitive(one) == another
以上情况之外,返回 false
false
我们以 [] == ![] 为例来分析这个过程:
[] == ![]
![] 会调用 ToBoolean([]) 并取反,得到结果 false
![]
ToBoolean([])
比较 [] == false,根据上文步骤 4,得到 [] == 0
[] == false
[] == 0
根据上文步骤 5,得到 '' == 0
'' == 0
根据上文步骤 3,得到 0 == 0
0 == 0
返回 true
上文提到,在一般情况下,当对象转字符串或者数字时,会调用 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 方法都不会被调用了。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
这篇文章主要记录从对象转换为 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
存在这个属性。虽然普通对象上没有这个属性,但我们是可以手动添加上这个属性,比如:下面就梳理下
ToPrimitive
的执行过程:如果 input 的类型是
object
a. 声明一个变量
hint
,如果 preferredType 不存在,将hint
赋值为default
b. 如果 preferredType 为
string
或number
,将hint
赋值为string
或number
c. 判断 input 是否存在
Symbol.toPrimitive
属性,如果存在则调用该属性指向的方法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
的行为呢,比如:当执行
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
,其他对象都没有这个属性)。如果理解了上文的
ToPrimitive
函数,就可以知道:如果一个对象存在Symbol.toPrimitive
属性,那么valueOf
以及toString
方法都不会被调用了。The text was updated successfully, but these errors were encountered: