一、前言
js中数据类型分为基本数据类型
和引用数据类型
,基本数据类型
有:String、Number、Null、Undefined、Boolean、Symbol(新增)、BigInt(新增);引用数据类型
有:Object,Object中又含有Array、Function。众所周知js是一门弱类型语言,定义变量是可以不指定变量类型,只有在变量使用时才知道类型;而当变量之间类型不同但是要进行运算或是比较时会发生隐式类型转换,这就很容易导致代码出错。下面我们就来了解一下js中的强制类型转换
和隐式类型转换
;
二、判断类型的方式
2.1 typeof
基本数据类型都可以使用typeof
来判断,引用类型中需要注意的是typeof
无null、Array和Object的返回值都为’object’;这是因为JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回 "object"
。这是一个创建这门语言就有的bug,一直未被修复。
typeof 2 // 'number'
typeof null // 'object'
typeof Symbol(2) // 'symbol'
typeof 3n // 'bigInt'
typeof [] // 'object'
typeof {
} // 'object'
typeof function(){
} // 'function'
总结:
- typeof 可以判断除 null 之外所有原始数据类型
- typeof null === ‘object’ 是一个历史遗留问题,无法修改。
- typeof无法判断Array和Object类型
2.2 instanceof
instanceof 运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。这里涉及到构造函数、原型和原型链的知识,举个例子理解一下:
// 常量arr是调用Array的构造函数返回的实例
const arr = [1,2,3,4];
// arr实例可以调用Array数组中的属性和方法,arr的原型链指向Array原型中
arr.__proto__ === Array.prototype // true
// 所以就可以使用instanceof判断是否是数组
arr instanceof Array; // true
// 注意:上面讲到typeof无法区分Array和Object,这是因为Array的原型链指向Object的原型上,而原型链的终点是null,所以就会出现下面情况
arr instanceof Object; // true
// arr完整的原型链
arr.__proto__.__proto__.__proto__ === null;
上面例子只是简述了一下instanceof的查找原理,原型和原型链没有深入讲解,下次单独整理讲述一下;
总结:
- instanceof不适合判断基本类型,只能用来判断引用类型;
- 也不能准确的判断Object,倘若判断变量是否是Object时,arr也会返回true;
2.3 constructor
constructor 属性返回Object的构造函数(用于创建实例对象)。可以理解为这个变量的构造函数是谁。可以为除了 null
和 undefined
(因为这两者没有相应的构造函数)之外的任何类型都有 constructor
属性;
// 原始类型
console.log((1n).constructor === BigInt); // true
console.log(('str').constructor === String); // true
console.log(Symbol(3).constructor === Symbol); // true
// 引用类型
console.log(([]).constructor === Array); // true
console.log(({
}).constructor === Object); // true
// 内置函数对象的构造函数为 Function
console.log(Array.constructor === Function); // true
console.log(String.constructor === Function); // true
总结:
- constructor 能够准确判断出数据类型,但 无法判断null 和 undefined
- 在创建实例前修改实例的原型,会导致constructor不可靠
- 内置函数对象的构造函数都是 Function
2.4 Object.prototype.toString.call()
每个对象都有一个 toString()
方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()
方法被每个 Object
对象继承。如果此方法在自定义对象中未被覆盖,toString()
返回 “[object type]”,其中 type
是对象的类型。
Object.prototype.toString.call(null).slice(8,-1); // Null
Object.prototype.toString.call([]).slice(8,-1); // Array
Object.prototype.toString.call({
}).slice(8,-1); // Object
总结:
- 这个是判断类型最准确的一种方法
- 推荐使用这个判断任意类型
三、强制类型转换
这里强制类型转换主要介绍的是其他类型转换为基本数据类型
,而基本数据类型转换引用类型需要看对应基本数据类型原型上是否有对应方法。
3.1 转换为String
其他类型强制转换为string类型有两种方式,String(val)
和val.toString()
。其中需要注意的是val.toString()
无法转换null
和undefined
。因为toString()方法是调用目标值原型对象上内置的toString方法,而 null和
undefined`没有对应原型对象,所以无法调用toString()方法。
null.toString(); // TypeError: Cannot read properties of null (reading 'toString')
undefined.toString();// TypeError: Cannot read properties of null (reading 'toString')
String(null); // 'null'
String(undefined); // 'undefined'
String(1); // '1'
String({
}); // '[object Object]'
String([]); // ''
3.2 转换为Number
其他类型转换为number,调用方法 Number
即可。其中需要注意的是对象转换为字符串是NaN
; 如果字符串中包含有不是数字的值转换也会变成NaN
。
Number(null); // 0
Number(undefined); // NaN
Number(true); // 1
Number(false); // 0
Number([]); // 0
Number({
}); // NaN
Number('12'); // 12
Number('12a'); // NaN
3.3 转换为Boolean
强制转换为Boolean很简单,只需要调用方法Boolean()
即可。只有六种情况转换的值才为false,其他都为true。转换为false:0、false、NaN、' '、null、undefined
3.4 null和undefined
null和undefined是JS中比较特殊的值,undefined
表示’无’的原始值;null表示尚未存在的对象,他们都没有自己的原型对象;所以不存在其他类型转换为这两种类型。
3.5 引用数据类型
当引用类型进行类型转换时,会调用js内部一个方法toPrimitive(), 此方法接收两个参数,一个参数为需要转换的对象,另一个方法接收一个期望类型,string或number。
- 当期望值为number时会调用valueOf方法,如果返回的值不是原始值,则继续调用toString方法。
- 当期望值为string时会调用toString方法,如果返回的值不是原始值,则继续调用valueOf方法。
四、隐式类型转换
触发隐式类型转换的场景:
- 算术运算时
- 逻辑比较时
4.1 算术运算时
4.1.1 +
加法运算符:
- 当进行加法运算的类型都为数字类型时,正常进行加法运算。
- 当有其他类型进行元素时,都会先转换为字符串,然后进行字符串的加法拼接。
- 进行字符串连接时,引用数据类型会调用自身toString方法,如果返回不是原始值,会继续调用自身valueOf方法,非引用数据类型:v.isString()如果是true,它将调用v.toString()。否则,它将值转换为字符串。
4.1.2 -、/和*
运算符:
- 非数字类型会转为数字类型
- 如果是原始数据类型会调用Number()方法进行转换
- 如果是引用数据类型会调用自身valueOf方法进行转换,如果转换后不是原始值,则会调用toString方法进行转换,如果转换后不是数字,则会调用Number()进行转换,如果转换后不是数字则会返回NaN。
4.2 逻辑比较时
4.2.1 ==
相等和!=
不等于比较
- 如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回
true
。 - 如果一个操作数是
null
,另一个操作数是undefined
,则返回true
。 - 如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:
- 当数字与字符串进行比较时,会尝试将字符串转换为数字值。
- 如果操作数之一是
Boolean
,则将布尔操作数转换为 1 或 0。- 如果是
true
,则转换为1
。 - 如果是
false
,则转换为0
。
- 如果是
- 如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的
valueOf()
和toString()
方法将对象转换为原始值。
- 如果操作数具有相同的类型,则将它们进行如下比较:
String
:true
仅当两个操作数具有相同顺序的相同字符时才返回。Number
:true
仅当两个操作数具有相同的值时才返回。+0
并被-0
视为相同的值。如果任一操作数为NaN
,则返回false
。Boolean
:true
仅当操作数为两个true
或两个false
时才返回true
。
4.2.2 !
转换
!
会将后面的数据先转成布尔值,然后取反。
4.2.3 <、<=、>、>=
比较转换
- 如果两个操作数都为数字则直接比较大小
- 如果两个操作数都为字符串则是逐位比较ascii码
- 如果是其他基本数据类型数字,或是字符串;
- 如果是引用数据类型则会转换为字符串。
五、综合例子练习
console.log(123 + +'123' == '123123');
console.log({
} == []);
console.log({
} == true);
console.log('777' < '8');
// 思考题
100 + true + 222 + null + undefined + "123" + [] + null + false = ???
- false,
+'123'
发生隐式转换为数字的123,最后是246 == '123123'
;所以为false - false,两个都为引用类型转换后为
'[object Object]' == ''
;所以为false - false,请看上面的4.2.1规则,true转换为1,最终是
'[object Object]' == 1
; 所以为false。 - true,请看4.2.3规则,两个操作数都为字符串时比较是ASCI值
- 题目五:结果
'NaN123nullfalse'
总结:
- 在进行算术运算时避免不同数据类型进行运算,可以手动转换为相同类型再进行计算;避免发生计算值出现NaN导致语法错误;
- 进行比较时要考虑到两个数值之间的隐式类型转换;
- if进行判断时不光要考虑隐式转换还需要考虑运算符之间的优先级(这个之前总结过);
- JS对于语法包容性很强,但书写代码时必须要严格要求自己:
- 算术运算符与变量之间一定要有空格,避免发生隐式转换;
- 上一句语句结束后
;
结尾; - 变量命名要见名知意;
- 转换为相同类型后在进行运算比较;