细品 JavaScript

细读 JS | valueOf 和 toString 方法

2021-05-17  本文已影响0人  越前君

在另外一篇文章:JavaScript 的数据类型以及那些事... 中讲述 ToPrimitiveOrdinaryToPrimitive 操作时,涉及到这两方法,所以今天来简单写一下。

其实我们一般很少主动去调用这两个方法,那它们什么时候会被使用到呢?当我们需要将一个对象(严格来说是,引用类型的值)转化为原始值的时候,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 里面内置了很多全局性的对象,如 ArrayBooleanDateFunctionNumberObjectString。它们都重写了自己的 valueOf 方法。其中 MathError 对象没有 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 返回对象本身。这是默认情况。

需要注意的是,MathError 对象没有 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 方法

其实好像没什么好说的,放链接自己看吧。

二、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.

上一篇下一篇

猜你喜欢

热点阅读