概要
JavaScript是一种弱类型语言,在定义变量的时候不需要指定变量类型所带来的后果就是:使用的时候,遇到不符合的类型时就会触发隐式类型转换,稍不注意,就会得到预期之外的结果。所以这篇文章的目的为了更好的避免出错和巧妙的利用js变量之间的类型转换。
我们都知道,js的基本数据类型有:
- String、基本数据类型
- Boolean、基本数据类型
- Number、基本数据类型
- undefined、基本数据类型
- null、基本数据类型
- Object、引用数据类型
- Symbol(es6新增)、基本数据类型
转换为String
输入类型 | 转换方法 | 结果 | 特例 |
Boolean | Boolean.ptototype.toString() | true => "true" false => "false" | 无 |
Number | Number.prototype.toString(radix=10) radix是进制数,默认十进制 | 1.无符号数字,数字加引号变成字符串 2.正号数字,去掉正号,数字部分加引号变成字符串 3.负号数字,保留负号,数字部分加引号变成字符串 12 => "12" ; NaN => "NaN" var num = -3;num.toString(2) => "-11" Infinity => "Infinity" | +0 => "0" -0 => "0" |
undefined | String() | "undefined" | 无 |
null | String() | "null" | 无 |
Object | Constructor.prototype.toString()或 Constructor.prototype.valueOf() | 参考下面介绍 | {} => "[object Object]" [1,2,3] => "1,2,3" function(){} => "function(){}" new Date() => "Thu Jul 18 2019 15:54:16 GMT+0800 (中国标准时间)" new RegExp(/123/,'ig') => /123/ig new Error('asd') => "Error: asd" |
Symbol | Symbol.prototype.toString | 例如:Symbol('bar').toString() => "Symbol(bar)" | 无 |
对象的类型转换(String方法)
-
如果对象有toString方法,则调用之,若该函数返回的是基本数据类型则将值转换为字符串并返回
-
如果对象无toString方法或调用toString方法返回的并不是基本数据类型,则调用对象的valueOf函数,若该方法存在并返回基本数据则返回该值
-
以上两个方法都不存在或者都不返回基本数据类型,则会抛出抛出一个 TypeError 异常
-
以上方法返回的如果是引用类型,并不会递归调用进行转换
测试代码:
var toString = Array.prototype.toString;
var valueOf = Array.prototype.valueOf;
var arr = [1,2,3];
Array.prototype.toString = function(){
console.log('call array toString');
return {};
}
Array.prototype.valueOf = function(){
console.log('call array valueof');
return 'abc';
};
console.log(String(arr));
//打印结果:
//call array toString
//call array valueof
//abc
js内置对象toString()方法和valueOf()方法返回值:
- Object对象
- toString() => "[object Object]"
- valueOf() => 对象本身
var obj = {
a: 'aa'
};
console.log(obj.toString());//'[object Object]'
console.log(obj.valueOf());//▶{a: 'aa'}
- Array数组对象
- toString() => 调用this.join()并返回
- valueOf() => 数组本身
var arr = [1,2,3];
console.log(arr.toString());//"1,2,3"
console.log(arr.valueOf());//▶(3)[1,2,3]
- Function函数对象
- toString() => 返回函数字符串
- valueOf => 函数本身
function foo(){}
console.log(foo.toString());//"function foo(){}"
console.log(foo.valueOf());//ƒ foo(){}
- Date日期对象
- toString() => 返回类似Thu Jul 18 2019 21:44:16 GMT+0800 (中国标准时间),这样的字符串
- valueOf() => 返回时间戳(精确到毫秒)
var date = new Date();
console.log(date.toString());//"Thu Jul 18 2019 21:44:16 GMT+0800 (中国标准时间)"
console.log(date.valueOf());//1563457456397
- RegExp正则对象
- toString() => 返回正则表达式字面量字符串
- valueOf() => 返回正则表达式字面量对象
var reg = new RegExp(/^abc$/,'ig');
console.log(reg.toString());//"/^abc$/gi"
console.log(reg.valueOf());///^abc$/gi
- Error异常对象
- toString() => 返回构造函数名 + 错误字符串 组成的字符串
- valueOf() => 返回错误对象本身
var error = new TypeError('aa');
console.log(error.toString());//"TypeError: aa"
console.log(error.valueOf());///TypeError: aa
转换为Boolean
输入类型 | 转换方法 | 结果 | 特例 |
String | Boolean() | 非空字符串都是true | 空字符串("")为false |
Number | Boolean() | 任何非零数字值(包括无穷大)都是true | +0、0、-0、NaN均为false |
undefined | Boolean() | false | |
null | Boolean() | false | |
Object | Boolean() | true | |
Symbol | Boolean() | true |
在进行条件判断的时候,被判断的数据都会被转换为Boolean类型,所以记住上表中的规律适用于绝大部分。
转换为Number
输入类型 | 转换方法 | 结果 | 特例 |
String | Number() | 字符串中含有非数字类型字符返回NaN,其他直接转换为对应数字 Number('abc') => NaN Number('123') => 123 Number('123abc') => NaN | 1.包含正负号纯数字保留正负号:Number('+123') => 123;Number('-123') => -123 2.包含浮点数则转换为浮点数:Number('-123.45') => -123.45 3.包含十六进制,不含正负号转为10进制:Number('0xff') => 255 4.包含科学计数法转为正常数字:Number("2E4") => 20000; 5.空字符串转为0:: Number('') => 0 |
Boolean | Number() | true => 1 false => 0 | |
undefined | Number() | NaN | |
null | Number() | 0 | |
Object | Constructor.prototype.valueOf()或 Constructor.prototype.toString()最后 Number() | 参考下面介绍 | {} => "[object Object]" => NaN [] => "" => 0 [1] => "1" => 1 [1,2] => "1,2" => NaN new Date() => 时间戳 new RegExp(/123abc/,'ig') => "/123abc/id/" => NaN new Error('abc') => "Error: abc" => NaN |
Symbol | 无法转换 | typeError |
对象转换为Number
- 如果对象有valueOf()方法,则调用之,若该函数返回的是基本数据类型则将值转换数字并返回,基本类型转换为数字遵循上表所述
- 如果对象无valueOf()方法或调用valueOf()方法返回的并不是基本数据类型,则调用对象的toString()函数,若该方法存在并返回基本数据类型则将其转换为数字并返回
-
以上两个方法都不存在或者都不返回基本数据类型,则会抛出抛出一个 TypeError 异常
-
以上方法返回的如果是引用类型,并不会递归调用进行转换
测试代码:
var toString = Array.prototype.toString;
var valueOf = Array.prototype.valueOf;
var arr = [1,2,3];
Array.prototype.toString = function(){
console.log('call array toString');
return "123";
}
Array.prototype.valueOf = function(){
console.log('call array valueof');
return ['a','b','c'];
};
console.log(+arr);
//打印:
//call array valueof
//call array toString
// 123
其他类型数据转换为null并没有什么意义;
任何类型数据通过void转换结果都是为undefined;
任何类型通过Object()函数,转换的都是对象类型,当然这个也没什么意义
任何类型通过Symbol()函数,转换的都是Symbol类型
隐式类型转换
掌握了不同数据类型之间的相互转换,但是在我们进行运算或者操作的时候,往往会触发隐式类型转换,那么隐式类型转换要遵循什么样的原则呢,之间是否有规律可循?
1."+"运算
加法是一个双目运算符,可以进行加法运算和字符串拼接。如果操作符两边都是数字或者字符串还好说,如果两边不都是数字或者字符串,我们就要进行类型转换。这里我总结一个规律——就是"+"运算类型转换的优先级:
- String > Number
- null 等于 undefined 等于 Object 等于 Boolean
第一梯队就是String和Number类型且String类型优先级高于Number;第二梯队就是null、undefined、Object及Boolean且优先级相当。进行"+"运算时,运算规则如下:
- 从左至右进行分割运算
- 优先级低的向优先级高的类型进行转换
- 第二梯队中的类型要向第一梯队的类型中进行转换
- +"运算符左右都是第二梯队中的类型,优先调用valueOf()方法且返回基本类型则使用,再次调用toString()方法返回基本类型,如果没有或者返回不是基本类型则往Number类型上转。
console.log(1 + false + '2' + null);
//从左至右分割运算
//1 + false => 1; //Number > Boolean
//1 + '2' => '12'; //String > Number
//'12' + null => '12null'; //String > Boolean
var obj = {
toString(){
return 'abc'
},
valueOf(){
return 456
}
};
console.log(obj + true + null + [1] + 1);
//从左至右分割运算
// obj + true => 456 + 1 => 457; //都在第二梯队中,优先转为Number
//457 + null => 457; //Number > null
//457 + [1] => "4571"; //[1].valueOf() => [1];[1].toString() => '1';//String > Number
//"4571" + 1 => "45711";//String > Number
2."=="比较
"=="操作符也是双目操作符,操作符两边如果类型相同则不进行类型转换,直接进行比较,如果类型不相同则要进行转换。
类型相同时
- NaN和NaN不等
- Object类型比较堆地址({} == {} => false;var a = b = {}; a == b => true)
- +0、-0、0相等
- 其他都相等
类型不同时
- null和undefined相等
- Object(Date类型转为String类型)、Number、String、Boolean之间的比较都是返回转换为Number类型进行比较
- 其他都不相等(null和undefined与其他类型都不相等)
示例:
console.log([] == ![]);
//左边[]valueOf() => [];调用toString() => '';转换为数字 => 0
//右边![]作为Boolean => false;转换为数字 => 0
//左右相等,返回true
下面介绍一些特例:
console.log({} + 1);
//{} 优先调用valueOf返回{},在调用toString() => "[object Object]"
//"[object Object]" + 1 => "[object Object]1"
但是我们在控制台中直接使用进行运算:
这是因为在控制台中,如果{}前面没有代码的话,会被编译为块级作用域,而不是当做对象来用,所以在控制台中使用{} + 1实际是打印的是 +1 结果。但是在实际代码编写过程中不会出现这种情况。