- 作者:老汪软件技巧
- 发表时间:2024-12-20 00:08
- 浏览量:
前言
在写了上篇文章:JS类型转换:那些你不知道的隐性陷阱和规则 - 掘金 我兴致满满的去刷了类型转换的题目,却发现自己在面对+"1" 、+[]、[]+{}这类题目时还是很懵逼
于是我便花了一上午时间阅读了一些大佬的文章终于对JS类型转换机制有了更清晰的认识,接着便写了这篇更深入的JS类型转换机制的文章,并加入toString、valueOf、toPrimitive的详细解析。
Primitive => Object
原始值通过调用 String()、Number() 或者 Boolean() 构造函数,转换为它们各自的包装对象。
话不多说,先上代码:
var a = 1.234;
console.log(typeof a);
var b = new Number(a);
console.log(typeof b);
console.log(b.toFixed(1))
console.log(a.toFixed(1))
运行一下
number
object
1.2
1.2
可以看到,代码中我们使用Number构造函数创建了一个新的Number对象b,并将变量a的值作为参数传递给构造函数。所以当我们调用Number对象b的toFixed方法,并传入参数1(该方法用于将数字格式化为指定小数位数的字符串)时,输出了1.2。可是为什么a.toFixed(1)也能成功打印1.2,a难道不是一个原始的数字类型吗???
这就涉及到了我们今天分享的第一个知识点——包装类
Object => Primitive1.对象转布尔值
对象转布尔值只需记住一句话:所有的对象(包括数组和函数)都转换为true
代码示例
console.log(Boolean(new Boolean(false)))//true
console.log(Boolean({}))//true
console.log(Boolean([1,2,3]))//true
2. 对象转字符串和数字(难点)ToPrimitive
Js引擎内部的抽象操作ToPrimitive(转换为原始值)的方法大体如下:
/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)
当type为Number时,处理步骤如下:
若对象有 valueOf() ,则调用,若返回结果为原始值,则进一步转换为数字(看情况转换,非必须)并返回;
否则,若对象具有toString(),则调用,若返回结果为原始值,后续同上;
都无法获得原始值,那么抛出TypeError 异常;
代码示例
let objectWihoutPrimitiveValueOf = {
valueOf: function() {
console.log('Calling valueOf')
return this;
},
toString: function() {
console.log('Calling toString')
return '789';
}
}
console.log(Number(objectWihoutPrimitiveValueOf))
let problemObj = {
valueOf: function() {
console.log('Calling valueOf')
return this;
},
toString: function() {
console.log('Calling toString')
return this;
}
}
try{
console.log(Number(problemObj))
}catch(e){
console.log('error')
}
输出结果
Calling valueOf
Calling toString
789
Calling valueOf
Calling toString
error
可以看到:
当type为String时,处理步骤如下:
若对象有 toString() ,则调用,若返回结果为原始值,则进一步转换为字符串(若本身不是字符串)并返回;
若对象没有 toString() 或返回的不是一个原始值,那么调用 valueOf() ,若结果为原始值,后续同上;
都无法获得原始值,那么抛出TypeError 异常;
代码示例
let objectWithStringValue = {
toString: function() {
return 'hello';
},
valueOf: function() {
return 1;
}
}
console.log(String(objectWithStringValue))
let objectWithValueOf = {
toString: function() {
console.log('Calling toString')
return this;
},
valueOf: function() {
return 2;
}
}
console.log(String(objectWithValueOf))
输出结果
hello
toString
2
分析一下:
type 是个可选参数,不传入时,默认按照下面规则自动设置
若对象为 Date 类型,则 type为 String;否则 type 为 Number;toString()和valueOf()
可以看到,当我们在控制台输入Object.prototype时就可以看到 valueOf() 和 toString() 。而所有对象继承自Object,因此所有对象都继承了这两个方法。
Object.prototype.valueOf():对象的valueOf旨在返回对象的原始值,会在需要将对象转换成原始值的地方自动执行。机制如下:
// 1. 基本包装类型直接返回原始值
var num = new Number('123');
console.log(num.valueOf()); // 123
var str = new String('123abc');
console.log(str.valueOf()); // '123abc'
var bool = new Boolean('abc');
console.log(bool.valueOf()); // true
// 2. Date 类型返回一个内部表示:1970年1月1日以来的毫秒数
var date = new Date(2024,12,19);
console.log(date.valueOf())//1737216000000
//3.返回对象本身
var arr = [1,2,3]
console.log(arr.valueOf());//[ 1, 2, 3 ]
Object.prototype.toString():toString()方法会返回表示该对象的字符串,会在对象预期要被转换成字符串的地方自动执行。机制如下:
// 1. 基本包装类型返回原始值
var num = new Number('123abc');
console.log(num.toString()); // NaN
var str = new String('123abc');
console.log(str.toString()); // '123abc'
var bool = new Boolean('abc');
console.log(bool.toString()); // true
// 2. 默认的 toString()
console.log(({a: 1}).toString())//[object Object]
console.log([1,2].toString())//1,2
console.log((function(){var a = 1;}).toString())//function(){var a = 1;}
// 3. 类自己定义的 toString()
// Date类型转换为可读的日期和时间字符
console.log(String(new Date(2024,12,18)))//Sat Jan 18 2025 00:00:00 GMT+0800 (中国标准时间)
console.log(JSON.stringify({a: 1}))//{"a":1}
值得一提的是,在默认的 toString()中:
另外:
因此,会输出"{"a":1}"。一元操作符 +
作为一元运算符时,+用于将操作数转换为数字类型。即ToNumber()方法,如果操作数已经是数字类型,则不会发生任何变化。如果操作数是字符串类型,则会尝试将其转换为数字。如果操作数是对象,则会先调用对象的valueOf()方法,如果该方法返回的不是原始值,则会继续调用toString()方法,然后将结果转换为数字。如果无法转换为数字,则结果为NaN。
在铺垫了这么多知识后,我们把罪魁祸首的题目搬出来拷打一遍
console.log(+"1")
console.log(+[])
console.log(+['1,2,3'])
console.log(+['1'])
console.log(+{})
已知我们需调用ToNumber()处理,且当输入的值为对象时,先调用ToPrimitive(obj,Number),再对返回值调用Number()方法
我以+{}为例,{}先调用valueOf方法,返回其对象本身,因为不是原始值,,所以再调用toString方法,返回[object Object],再对其使用Number方法,打印结果为NaN。
剩下的题目以此类推,得出结果:
1
0
NaN
1
NaN
怎么样,你做对了吗?
二元运算符 +
+符号既可以用作一元运算符,也可以用作二元运算符。当它作为二元运算符时,它执行加法操作。执行步骤如下:
当计算 value1 + value2时:
lprim = ToPrimitive(value1)
rprim = ToPrimitive(value2)
如果返回值中有一方为字符串,那么返回 ToString(lprim) 和 ToString(rprim)的拼接结果(即+作字符串连接)
否则,返回 ToNumber(lprim) + ToNumber(rprim)的运算结果
以一些题目作为例子
console.log(1+'1')//11
console.log(null + 1)//1
console.log([]+{})//[object Object]
console.log({}+{})//[object Object][object Object]
分析:
1 + '1'
因为1与'1'都是基本类型,所以直接返回其本身,即lprim=1 , rprim='1' 因为rprim是字符串,所以执行ToString(1) + '1',得到'11'
null + 1
同上,null 与 1 都是基本类型,所以直接返回其本身,即lprim=null,rprim=1 又因为lprim与rprim都不是字符串,所以返回 ToNumber(null) + ToNumber(1)的运算结果,得到1
[] + {}
1.- lprim = ToPrimitive([]),因为两个对象中间有个运算符+,相当于ToPrimitive([], Number)先调用valueOf方法,返回对象本身,因为不是原始值,调用toString方法,返回空字符串""
rprim = ToPrimitive({}),同上,返回[object Object]
lprim和rprim都是字符串,执行拼接操作,""+[object Object],得到[object Object]
{}+{}
lprim=rprim = ToPrimitive({}),返回了两个[object Object],都是字符串,即进行[object Object]+[object Object],得到[object Object][object Object]
关于==规范
"=="用于比较两个值是否相等,当要比较的两个值类型不一样的时候,就会发生类型的转换。
使用==进行比较时的具体步骤如下:
给出一段代码做一些例子:
console.log([]==![])//true
console.log(42 == ['42'])//true
console.log(true == '2')//false
console.log(1 == '2')//false
console.log(null == undefined)//true
分析
1.[]==![]
首先会执行![]操作,转换成 false,由规范中第7条相当于[] == false,且ToNumber(false)=0,即[] == 0,由规范中第9条=>比较ToPrimitive([])==0,相当于'' == 0由规范中第4条=>ToNumber('')==0相当于0 == 0,结果返回true
2.42 == ['42']看规范第8条:
如果Type(x) 时String或Number ,而Type(y) 时 Object,返回比较结果x==ToPrimitive(y)
以这个例子为例,会使用 ToPrimitive 处理 ['42'],调用valueOf,返回对象本身,再调用 toString,返回 '42',所以
42 == ['42'] 相当于 42 == '42' 相当于42 == 42,结果为 true。
3.true == '2'
由规范中第6、7条可知,当一方出现布尔值的时候,就会对这一方的值进行ToNumber处理,也就是说true会被转化成
true == '2'就相当于1 == '2'就相当于1 == 2,结果自然是false。
4.1 == '2'
做到这是不是知道一眼false了,相当于1==ToNumber('2'),即1==2,false
5.null == undefined
看规范第2、3条,当一方为未定义,另一方为null时,返回true
其他情况就不一一列举了,规范中条例的很清楚
如果有错误或不足,欢迎纠正或补充。希望这篇文章能给大家带来帮助。