细读 JS | valueOf 和 toString 方法

在另外一篇文章: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.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容