细读 JS | valueOf 和 toString 方法
在另外一篇文章:JavaScript 的数据类型以及那些事... 中讲述 ToPrimitive
和 OrdinaryToPrimitive
操作时,涉及到这两方法,所以今天来简单写一下。
其实我们一般很少主动去调用这两个方法,那它们什么时候会被使用到呢?当我们需要将一个对象(严格来说是,引用类型的值)转化为原始值的时候,JavaScript 可能会调用到它们。
举个例子:
const obj = {}
console.log('This is ' + obj) // "This is [object Object]"
内部过程是这样的:
1. 'This is ' + obj 操作,使得 obj 会自动转换为原始值。
2. 由于 obj 内部没有定义 @@toPrimitive 属性,所以它会先调用 toString 方法或 valueOf 方法。
3. 由于 obj 本身没有 toString 方法,JavaScript 会从原型上找到 Object.prototype.toString(),执行结果是 [object Object]。
4. 由于 toString 方法已经返回原始值了,就不会再调用 valueOf 方法了。(假设上面 toString 没有返回原始值,接着调用 valueOf 方法,如果结果还不是原始值,则会抛出 TypeError 错误)
5. 所以最后执行拼接操作的是两个字符串:'This is ' + '[object Object]',所以结果就是它了。
接着我们来验证一下:
// 示例 1:验证第 2 点。在类型转换时,优先寻找 @@toPrimitive 方法(即下面的 [Symbol.toPrimitive])
const obj = {
[Symbol.toPrimitive]: hint => {
console.log('注意:根据 ECMAScript 标准,若该方法返回引用类型,会抛出 TypeError')
return 'abc'
}
}
console.log('This is ' + obj) // "This is abc"
// 示例 2:验证第 3 点
const obj = {}
Object.prototype.toString = () => 'rewrite toString method'
console.log('This is ' + obj) // "This is rewrite toString method"
// 示例 3:验证第 4 点
const obj = {
valueOf: hint => {
console.log('执行 valueOf 方法')
return {}
},
toString: hint => {
console.log('执行 toString 方法')
return 'tostring'
}
}
console.log('This is ' + obj) // "This is tostring"(在控制台可以看到,先后执行了 valueOf、toString 方法)
console.log(String(obj)) // "tostring"
一、valueOf
当 OrdinaryToPrimitive(O, hint)
抽象操作的 hint
为 "number"
时,JavaScript 会首先调用 valueOf()
方法。
关于 OrdinaryToPrimitive(O, hint) 的介绍可以看文章。
1. Object.prototype.valueOf
Object.prototype.valueOf()
方法返回对象的原始值。
Object.prototype.valueOf.call({}) // {}
Object.prototype.valueOf.call([]) // []
当我们创建一个对象 const obj = {}
,当我们调用 obj.valueOf()
时,访问的就是 Object.prototype.valueOf()
方法。
但是,JavaScript 里面内置了很多全局性的对象,如 Array
、Boolean
、Date
、Function
、Number
、Object
、String
。它们都重写了自己的 valueOf
方法。其中 Math
和 Error
对象没有 valueOf
方法。
通过以下方式,可以判断一个内置对象是否有重写自己的 valueOf
方法:
// 结果为 false 表示有重写(toString 同理)
Array.prototype.valueOf === Object.prototype.valueOf // true
Function.prototype.valueOf === Object.prototype.valueOf // true
Boolean.prototype.valueOf === Object.prototype.valueOf // false
Date.prototype.valueOf === Object.prototype.valueOf // false
Number.prototype.valueOf === Object.prototype.valueOf // false
String.prototype.valueOf === Object.prototype.valueOf // false
Symbol.prototype.valueOf === Object.prototype.valueOf // false
BigInt.prototype.valueOf === Object.prototype.valueOf // false
// 还有很多内置对象没列出来,可自行翻查 MDN 或 ECMAScript 文档...
对象 | 返回值 |
---|---|
Boolean | 返回布尔值。 |
Date | 返回的时间是从 1970 年 1 月 1 日 00:00:00 开始计的毫秒数(UTC)。 |
Number | 返回数字值。 |
String | 字符串值。 |
Object | 返回对象本身。这是默认情况。 |
需要注意的是,
Math
、Error
对象没有valueOf()
方法。
假设我们自行创建一个对象,可以这样去覆盖默认的 Object.prototype.valueOf()
方法:
// 构造函数
function MyObject(my) {
this.my = my
}
// 在原型上定义 valueOf 方法(该方法不应传入参数)
MyObject.prototype.valueOf = function() {
return this.my
}
// 实例化
var myObj = new Object('This is myObj.')
console.log('' + myObj) // "This is myObj."
2. 其他内置对象的 valueOf 方法
其实好像没什么好说的,放链接自己看吧。
- Boolean.prototype.valueOf()
- Date.prototype.valueOf()
- Number.prototype.valueOf()
- String.prototype.valueOf()
二、toString
同样的,一般比较少主动去调用 toString()
方法。
1. Object.prototype.toString()
Object.prototype.toString()
返回一个表示该对象的字符串。
它实际访问的是对象内部的 [[Class]]
属性,返回的形式如:"[object type]"
,常用于检测对象类型。
function getClass(x) {
const { toString } = Object.prototype
const str = toString.call(x)
return /^\[object (.*)\]$/.exec(str)[1]
}
getClass(null) // "Null"
getClass(undefined) // "Undefined"
getClass({}) // "Object"
getClass([]) // "Array"
getClass(JSON) // "JSON"
getClass(() => {}) // "Function"
;(function() { return getClass(arguments) })() // "Arguments"
更多详情,请看文章 — 对象的内部属性的章节。
Array.prototype.toString === Object.prototype.toString // false
Function.prototype.toString === Object.prototype.toString // false
Boolean.prototype.toString === Object.prototype.toString // false
Date.prototype.toString === Object.prototype.toString // false
Number.prototype.toString === Object.prototype.toString // false
String.prototype.toString === Object.prototype.toString // false
Symbol.prototype.toString === Object.prototype.toString // false
BigInt.prototype.toString === Object.prototype.toString // false
2. Array.prototype.toString()
Array.prototype.toString()
方法,返回一个包含用逗号 ,
分隔的每个数组元素的字符串。
var arr = [1, 2, 3]
console.log(arr.toString()) // "1,2,3"
// 结果相当于 Array.prototype.join.call(instance, ',')
arr.join(',') // "1,2,3"
3. Function.prototype.toString()
Function.prototype.toString()
方法,返回一个表示当前函数源代码的字符串。
function fn() {}
console.log(fn.toString()) // "function fn() {}"
4. Boolean.prototype.toString()
Boolean.prototype.toString()
方法,返回指定的布尔对象的字符串形式。
console.log(true.toString()) // "true"
console.log(false.toString()) // "false"
5. String.prototype.toString()
String.prototype.toString()
方法,返回指定对象的字符串形式。
console.log(new String('foo').toString()) // "foo"
6. Symbol.prototype.toString()
Symbol.prototype.toString()
方法,返回当前 Symbol 对象的字符串表示。
需要注意的是,Symbol 原始值不能转换为字符串,只能将其转换成对应的包装对象,再调用
toString()
方法。
console.log(Symbol('foo') + 'bar' ) // TypeError: Cannot convert a Symbol value to a string
// Symbol('foo') 结果是 Symbol 的原始值,再调用其包装对象的属性时,会自动转化为包装对象再调用其 toString() 方法
console.log(Symbol('foo').toString() + 'bar' ) // "Symbol(foo)bar"
7. BigInt.prototype.toString()
BigInt.prototype.toString()
方法,返回一个字符串,后面的 n
不是字符串的一部分。
console.log(1024n.toString()) // "1024"
console.log(1024n.toString(2)) // "10000000000"
console.log(1024n.toString(16)) // "400"
The end.