关于valueOf、toString和[Symbol.toPrimitive]方法

前言

在掘金上看到一篇文章,大意是如何让a === 1 && a === 2 && a === 3等于true;一开始确实一脸懵逼,不过后来想到了访问修饰符能够解决这个问题,毕竟每访问一次值就加上1就好了;当然,我居然忘了全局变量本身就是window的一个属性这个点,有点疏忽大意。

1
2
3
4
5
6
7
8
let val = 1
Object.defineProperty(window, 'a', {
get () {
return this.val++ // 这里的this就是全局对象window
}
})

console.log(a === 1 && a === 2 && a === 3) // true

不过,我又想到貌似对象还有一些方法能够改变访问到的值;比如:

  • Object.prototype.valueOf()
  • Object.prototype.toString()
  • [Symbol.toPrimitive](hint)

然而,经过折腾发现这三种方法并不像访问修饰符那样的机制,在使用全等号===进行判断时并没有触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = {
valueOf () {
return 1
},
toString () {
return '2'
},
[Symbol.toPrimitive] () {
return 3
}
}

console.log(a === 1) // false
console.log(a === '2') // false
console.log(a === 3) // false

所以,心里自然就有疑问,这几个方法到底在什么时候才会触发?

Object.prototype.valueOf()

对象的valueOf旨在返回对象的原始值,会在需要将对象转换成原始值的地方自动执行

img

根据MDN上的资料[1]可知,内置的一些对象已经定义valueOf方法,而对象默认则返回本身作为valueOf()的返回值。

Object.prototype.toString()

同理,toString()方法会返回表示该对象的字符串,会在对象预期要被转换成字符串的地方自动执行。对象默认的toString()方法会返回[object type],这个type就是对象构造函数的名称

Symbol.toPrimitive

[Symbol.toPrimitive](hint)方法作用同valueOf()一样,但是优先级要高于valueOf();而且该方法还会接受一个参数hint,这个参数用来表示期望转换的原始值的具体类型,有以下几种:

  • number:数字类型
  • string:字符串类型
  • default:默认

对象转换成原始值

很明显,以上三种方法都是在对象被预期转换成某种原始值时触发,那么触发的时机是什么,需要何种类型的原始值就需要弄清楚了。

预期被转换成字符串类型

对应的hint类型为string

  1. 进行输出的地方,如alert()

  2. String(obj)

    1
    2
    3
    4
    5
    6
    7
    let a = {
    toString () {
    return '2'
    }
    }

    console.log(String(a)) // 2
  3. 字符串连接+)操作:

    1
    2
    3
    4
    5
    6
    7
    let a = {
    toString () {
    return '2'
    }
    }

    console.log(a + 'vv')
  4. 模板字符串

    1
    2
    3
    4
    5
    6
    7
    8
    let a = {
    [Symbol.toPrimitive] (hint) {
    console.log(hint) // string
    return 2
    }
    }

    console.log(`你是老${a}?`) // 你是老2?

预期被转换成数字类型

对应的hint类型为number

  1. 除法

    1
    2
    3
    4
    5
    6
    7
    let a = {
    valueOf () {
    return 2
    }
    }

    console.log(2 / a, a / 2) // 1 1

    可以使用[Symbol.toPrimitive]()方法来验证一下:

    1
    2
    3
    4
    5
    6
    7
    8
    let a = {
    [Symbol.toPrimitive] (hint) {
    console.log(hint) // number
    return 2
    }
    }

    console.log(2 / a) // 1
  2. Number(obj)

    1
    2
    3
    4
    5
    6
    7
    8
    let a = {
    [Symbol.toPrimitive] (hint) {
    console.log(hint) // number
    return 2
    }
    }

    console.log(Number(a)) // 2
  3. 正负号(注意不是加减运算):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let a = {
    [Symbol.toPrimitive] (hint) {
    console.log(hint) // number
    return 2
    }
    }

    console.log(+a) // 2
    console.log(-a) // -2

预期被转换成默认类型(其他)

对应的hint类型为default

  1. 数字加法(即与对象相加的一方为数字类型):

    1
    2
    3
    4
    5
    6
    7
    8
    let a = {
    [Symbol.toPrimitive] (hint) {
    console.log(hint) // default
    return 2
    }
    }

    console.log(1 + a) // 3

    这一点有点意外,原以为像这种情况预期转换的类型应该是数字类型,但事实上却是default

  2. 布尔运算:所有对象都被转换成true

    1
    2
    3
    4
    5
    6
    7
    8
    let a = {
    [Symbol.toPrimitive] (hint) {
    console.log(hint) // 没有触发
    return false
    }
    }

    console.log(Boolean(a), a && 123) // true 123

    布尔运算包括==

三种方法触发的顺序

  1. 首先判断对象是否有[Symbol.toPrimitive](hint)方法,若有则执行该方法;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let a = {
    [Symbol.toPrimitive] (hint) {
    console.log(hint)
    return 'right your mother right'
    },
    toString () {
    return 'f**k'
    },
    valueOf () {
    return 'sb'
    }
    }

    console.log(String(a)) // right your mother right
  2. 如果预期被转换成字符串类型时,则优先执行toString()方法;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let a = {
    toString () {
    return 'f**k'
    },
    valueOf () {
    return 'sb'
    }
    }

    console.log(String(a)) // f**k
  3. 如果预期被转换成默认类型或者数字类型时,则优先执行valueOf()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let a = {
    toString () {
    return 123
    },
    valueOf () {
    return 456
    }
    }

    console.log(Number(a)) // 456

    :若没有valueOf()方法,但是定义了toString()方法,则会执行toString()方法;

    1
    2
    3
    4
    5
    6
    7
    let a = {
    toString () {
    return 123
    }
    }

    console.log(Number(a)) // 123

参考文档


  1. Object.prototype.valueOf() - JavaScript | MDN ↩︎