阿瑞斯的BLOG

JavaScript复合类型的隐式转换

为什么很重要

在你的编写的代码中,类型转换无处不在。而对于”隐式”转换它就像一个昙花一现般,短暂地出现却又很快地消失。

比如:

1
2
var arr = [1,2,3];
alert(a);

这样类似的代码写的不在少数吧?可是对于alert()这个函数,你知道它接收的参数类型是一个字符串吗?诶不对啊,这里明明传入了一个Array,而且正常显示了,没有报错啊。这就是神秘的隐式转换,悄悄地对你的参数做了转换,最后显示在控制台上。下面来看几段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var bbb = {
i: 10,
toString: function() {
console.log('toString');
return this.i;
},
valueOf: function() {
console.log('valueOf');
return this.i;
}
}
alert(bbb);// 10 toString
alert(+bbb); // 10 valueOf
alert(''+bbb); // 10 valueOf
alert(String(bbb)); // 10 toString
alert(Number(bbb)); // 10 valueOf
alert(bbb == '10'); // true valueOf
alert(bbb === '10'); // false

  1. 重写两个方法的情况下,如果要输出一个对象,那么会输出什么样的值?如何对对象进行类型转换的?

    从第一个alert()中得到的结果是,输出对象值的时候,实际上调用了对象的toString()方法。

  2. 重写两个方法的情况下,加号操作符对对象会产生什么行为?一个加号操作符加一个操作数会发生什么?

    加号操作符让对象调用了valueOf()方法。+bb是+ 运算符的一元(unary)形式,(即只有一个操作数),+运算符显示地将bb转换为数字。

  3. 重写两个方法的情况下,字符串,加号操作符对对象产生什么行为?

    字符串,加号操作符让对象调用了valueOf()方法,但是为什么会是valueOf而不是toString()方法呢?这里明明看上去就是字符串拼接啊。

    “在有操作符的情况下,valueOf()的优先级比toString()更高”

    想要理解这个其实不难,+号操作符和对象在一起时,首先会被认为是一个值运算,所以会调用valueOf()返回一个数值,其次因为加号操作符的两边一边是字符串,一边是数字,对于这种,JavaScript红宝书上是这么说的:

    如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来。

    于是数值又悄悄地进行了一次转换String(),看似简单的结果背后实则发生了两次类型转换,类型转换是不是无处不在?

  4. 重写两个方法的情况下,把对象显示地转换成字符串会产生什么行为?

    调用对象的toString()方法转换为基本值

  5. 重写两个方法的情况下,把对象显示地转换为数字会产生什么行为?

    调用对象的valueOf()方法转换为数值

  6. 一个对象如何和一个基本类型进行比较?

    1. 复合类型(对象/数组)先通过valueOf()或者toString()转换为基本类型
    2. 根据基本数据类型的比较规则进行类型转换最终比较出结果
  7. JavaScript中的valueOf方法和toSting方法是干什么的?

    把复合类型转换为基本类型

  8. 为什么实际中很少调用valueOf()和toString(),但他们还这么重要?
    因为它总在你不知道的时候悄悄地被调用

那么何时调用toString()方法,何时调用valueOf()方法呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
一个具有重写的toString(),一个具有重写的valueOf()
var aa = {
i: 10,
toString: function() {
console.log('toString');
return this.i;
}
}
alert(aa);// 10 toString
alert(+aa); // 10 toString
alert(''+aa); // 10 toString
alert(String(aa)); // 10 toString
alert(Number(aa)); // 10 toString
alert(aa == '10'); // true toString
var bb = {
i: 10,
valueOf: function() {
console.log('valueOf');
return this.i;
}
}
alert(bb);// [object Object]
alert(+bb); // 10 valueOf
alert(''+bb); // 10 valueOf
alert(String(bb)); // [object Object]
alert(Number(bb)); // 10 valueOf
alert(bb == '10'); // true valueOf

只重写toString的情况下,以上问题会发生什么样的行为变化?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
输出:调用toString()转换为基本类型值
值运算:没调用valueOf()了,而是调用了toString()转换为了数值,即使修改i=“10”,并且打印值的类型,还是数字,而不是字符串。这说明先通过toString()转化为基本数据类型,再通过Number()把字符串转换为数字。
分析:1.重写toString方法比原始valueOf方法的优先级,所以复合类型先调用toString()转换为基本类型。2.因为是值运算,所以基本类型string经过Number()二次转换为number。
如何验证:
aa.i = “10";
alert(+aa); // 10 toString
alert(typeof +aa); // number
字符串+对象: 1.调用重写的toString()转换为基本类型 2.数值类型转为字符串类型进行拼接
对象显示转化字符串: 1.调用重写的toString()转换为基本类型Str
对象显示转换为数值: 1.调用重写的toString()转换为基本类型Str 2.String转换为Number
相等判断: 1.调用重写的toString()转换为基本类型Str 2.String转换为Number再进行比较->相等

只重写valueOf的情况下,以上问题会发生什么样的行为变化?

1
2
3
4
5
6
7
输出:1.并没有调用重写的valueOf,而是调用默认的toString()方法,显示[对象 类型]
值运算:1.调用重写的valueOf(),转换为基本类型值Number
字符串+对象:1.调用重写的valueOf()转换为基本类型值Number 2.转换为str进行拼接
对象显示转化字符串:1.没有调用重写的valueOf(),而是直接调用了默认地toString
对象显示转换为数值:1.调用重写的valueOf()转换为基本类型值Number
相等判断:1.调用重写的valueOf()转换为基本类型值Number

去掉从Object继承的toString方法的干扰,以上问题会发生什么样的变化?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.prototype.toString = null;
var cc = {
i: 10,
valueOf: function() {
console.log('valueOf');
return this.i;
}
}
alert(cc);// 10 valueOf
alert(+cc); // 10 valueOf
alert(''+cc); // 10 valueOf
alert(String(cc)); // 10 valueOf
alert(Number(cc)); // 10 valueOf
alert(cc == '10'); // true valueOf

因为默认地toString方法没了,所以只能调用重写的valueOf()

总结

0.基本上,所有JS数据类型都拥有valueOf和toString这两个方法,null除外。它们俩解决javascript值运算与显示的问题。 还有一个对象没有valueOf()和toString()方法,通过Object.create(null)创建的对象[[Prototype]]属性为null
1.toString()用于显示,valueOf()用于值运算。
2.默认情况下,根据不同的操作,两个方法的优先级不同。
3.在有操作符的情况下,valueOf的优先级比toString高。
4.重写会加大它们自动调用的优先级。
5.重写toString的情况下,toString()方法会无视valueOf(),1.都会先调用toString()方法转换为基本类型的值,此时如果是”显示”操作就到此为止2.如果是值运算,基本类型的值转化为Number类型进行值运算.
6.重写valueOf的情况下,值运算优先调用valueOf,显示地转换为字符串&输出会调用默认地toString()。valueOf()不管返回的是什么基本值,值运算都会对其转换为数值.
7.在进行强转字符串的时候将优先调用toString().