想要搞明白数据类型的转换,首先得清楚js有哪几种数据类型
1.数据类型
ES最新标准现共有8种数据类型,其中基本数据类型7种,引用数据类型1种
基本数据类型
- Boolean 布尔值,只含有两个关键字(true,false)
- String 字符串
- Undefined 一个声明的但未定义的值,拥有全局属性undefined来表示,之所以说undefined为全局属性,而非关键字,是因为在非严格模式下,函数作用域内可以对undefined进行重新赋值,因此会产生bug。建议使用void 0来安全获取undefined值。
- Null 定义了但为空,只有一个值,关键字null。该类型有个历史遗留问题,那就是typeof null === 'object',使用该判断条件时,要注意。
- Symbol es6新增,可用于定义唯一的值
- Number 数字类型,代表双精度64位浮点数,其中还包含了+Infinity,-Infinity 和 NaN的值
- BigInt 代表数值比较大的整数,甚至可以超过数字的安全整数限制。BigInt是通过在整数末尾附加 n 或调用构造函数来创建的。
引用数据类型
- Object 拥有数据属性和访问器属性
2.强制类型转换
js中的强制类型转换主要指的是基本数据类型之间值的转换,而基本数据类型转成引用数据类型,则是一种封装,并非实际意义的类型转换。
2.1 ToString (其它数据类型转换成String)
规则:
类型 | 原值 | string值 |
---|---|---|
Boolean | true | false | 'true' | 'false' |
Undefined | undefined | 'undefined' |
Symbol | Symbol('name') | 'Symbol(name)' |
Number | 22 | '22' |
Null | null | 'null' |
Object | {name:'bob'} | '[object Object]' |
方法:(显式或隐式,只是相对的说法,如果你很清楚这里提到的隐式方法,那么它对你来说就是显式的)
显式方法
原生函数String(),如String(true)
隐式方法
- 利用+号运算符,其中只要运算符两侧有一个操作数为字符串,该运算符将是字符串拼接功能,那么另一个操作数就会进行字符串的类型转换
var a = 'string' + 234 // 'string234'
var b = 'string' + null // 'stringnull'
var c = 'string' + {} //'string[object Object]'
//实际应用
var d = true + '' 'true'- 除了Undefined、Null数据类型外,其它都可以使用toString()方法,如true.toString(),这里涉及到基本数据类型的封装,后面会详细介绍。
2.2 ToNumber(其它数据类型转成Number)
规则:
类型 | 原值 | number值 |
---|---|---|
Boolean | true | false | 1 | 0 |
Undefined | undefined | NaN |
Symbol | Symbol('name') | 不能转换 |
string | '22' | '23fds' | '' | 22 | NaN | 0 |
Null | null | 0 |
Object | {name:'bob'} | NaN |
方法:
显式方法
原生函数Number(),如Number('234'),这个需要注意一下StringToNumber,如果我们传入的不是只包含数字的字符串,如'234fasd',那么Number函数会将其转成NaN。但还有个函数却是另一种行为。
parseInt('234fasd',10) // 234
Number('234fasd') //NaNparseInt是全局对象的一个属性,在浏览器中也就是window对象的方法。它看似也实现了数据类型的转换,但实则不是,它是一种数据的解析,并且只能正常解析数字和字符串的数据类型。当传入上述中既包含数字也包含字母的字符串时,它会判断,当遇到第一个非数字的字符时,那么就会将后面的字符全抛弃掉,然后转化之前全为数字的字符串,该函数第二个参数为进制数,默认以十进制,来解析传入的值。
隐式方法
- 在所要转换的数据前加上~~,~是一个取反运算法,这种方式的感觉就像负负得正一样。
~~'2434' // 2434
~~null // 0- +运算符,这个并不是用来计算数字加减或字符串拼接用的那个,而是直接放在被转换数据的前面
+'123' // 123
+true // 1- -、 * 、 / 、 %运算符,和转字符串的+号运算符,道理类型,若两侧的操作数中含有非numbe类型时,会先转换成number,再运算
var a = '123' - '3' // 120
var b = '45' - 5 // 40
var c = 60 - undefined // NaN
//实际使用
var c = '123' - 0 // 123
2.2 ToBoolean(其它数据类型转成Boolean)
规则:
类型 | 原值 | boolean值 |
---|---|---|
Number | 非0 | 0 | true | false |
Undefined | undefined | false |
Symbol | Symbol('name') | true |
string | '22sd' | '' | true | false |
Null | null | false |
Object | {name:'bob'} | {} | [] | true | true | true |
方法:
显式方法
原生函数Boolean()。如Boolean('test'),
隐式方法
- 在转换的数据前加上!!,如!!'test'
!'test' // false
!!'test' // true
- 各种条件判断语句。
- if()
- else if()
- while()
- ?:, 左侧操作数
- && 、 || 左侧的操作数
这些都会对传入的数据进行转Boolean操作
2.3 ToObject (其它数据类型转成Object)
这个一开始提到过,其它基本数据类型转成引用数据类型,并不是真正的数据转换,而是一种封装。
什么是封装
举个例子我们通常会写这么一句代码 ' thisString '.trim() 来清除字符串左右两侧的空格,但你有没有想过,.运算符明明是作用在Object上,用来访问该对象的属性和方法的,为什么字符串这种基本数据类型也可以这么用,答案就是封装对象。
除了普通的对象外,其实还有四种基本包装对象,分别对应着基本数据类型中的Number,String,Boolean,Symbol,除了Symbol,另外三种我们可以通过new 来获取到相应的实例对象。
例如: let num = new Number(234)
那么上面那句代码,真实执行是这样的
' thisString '.trim();
// 内部实际
var str = new String(' thisString ');
str.trim();
str = null;
也就是它会创建一个基本包装对象,然后调用该对象上的方法,执行完成,销毁这个包装对象。那么这种基本包装对象的生命周期只存在于代码执行的那么一瞬间,之后我们不可以在运行的时候为其添加方法与属性。例如:
var s = 'this String';
s.trim();
// 这样是没什么用的
s.name = 'job'
console.log(s.name) //undefined
封装方法
直接使用js提供的Object原生函数,当传入的是上面那四种基本数据类型时,它会转成对应的基本包装对象
2.4 ObjectToOther (引用类型转基本数据类型)
与前面封装的操作相反,对象转成基本数据类型,则是拆封。在JavaScript 标准中,规定了 ToPrimitive 函数,来实现拆封。
拆封的方法就是Number()、String()、Boolean这些用来转类型的原生函数
具体的拆封过程,其实是涉及到对象valueOf和toSting函数的调用,当将一个对象转换基本数据类型时,会先调用该对象的valueOf,如果能返回基本数据类型,则进行后续的数据类型转换,否则,就继续调用toString,拿到字符串,然后进行数据类型转换。如果这两个方法都没返回基本数据类型,则抛出异常,写段代码,就能清晰明白了。
先定义一个返回基本数据类型的valueOf方法的对象
// 先定义一个对象,自定义valueOf和toString方法
var a = {
valueOf: function(){
console.log('调用了valueOf方法')
return '123'
},
toString: function(){
console.log('调用了toString方法')
return '123'
}
}
// 使用之前转number的原生函数来拆箱
Number(a) //输出 调用了valueOf方法 123
// 只执行了valueOf方法
定义一个返回引用数据类型的valueOf方法的对象
// 先定义一个对象,自定义valueOf和toString方法
var a = {
valueOf: function(){
console.log('调用了valueOf方法')
return {}
},
toString: function(){
console.log('调用了toString方法')
return '123'
}
}
// 使用之前转number的原生函数来拆箱
Number(a) //输出 调用了valueOf方法 调用了toString方法 123
// 执行了valueOf方法后,又执行了toString方法
定义一个valueOf,toString都不返回基本数据类型的对象
// 先定义一个对象,自定义valueOf和toString方法
var a = {
valueOf: function(){
console.log('调用了valueOf方法')
return {}
},
toString: function(){
console.log('调用了toString方法')
return {}
}
}
// 使用之前转number的原生函数来拆箱
Number(a) //输出 调用了valueOf方法 调用了toString方法
/* Uncaught TypeError: Cannot convert object to primitive value
at Number (<anonymous>)
at <anonymous>:13:1 */
// // 执行了valueOf方法后,又执行了toString方法,然后抛出了错误
这就是一个拆箱,ToPrimitive 函数的过程。
有一种特殊情况,如果是使用String()函数,它会直接调用对象的toString方法,而不是先valueOf()
在封装对象那节里说到的4种基本包装对象,它们都实现了自己的valueOf和toString方法,因此在拆箱时,能正常返回其基本数据的值。
还有一个知识点,当我们对普通对象转String时,会看到这个东西
var a = {}
a.toString() //"[object Object]"
var b = new Set()
b.toString() //"[object Set]"
其中第二个Object和Set,实际是对象的一个私有属性[[Class]]的值,这个值并不能直接访问并且修改,只能通过Object.prototype.toString() 来获取
Object.prototype.toString.call({}) //"[object Object]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(new Set()) //"[object Set]"
Object.prototype.toString.call(123) //"[object Number]"
这样看来这个Class应该是代表了一个数据的详细种类
最后
如果该文对您有帮助的话,请点个赞哦😯
文中若有错误,欢迎指正;若您有补充,欢迎留言。