JavaScript 数据类型
JavaScript的数据类型分两类。一种是基础数据类型,一种是引用数据类型。具体如下:
NaN 是一个数值类型,但不是一个具体的数字。它和任何值都不相等,包括它自己。
基本数据类型保存在栈内存当中, 保存的是一个具体的值。
引用数据类型保存在堆内存当中,声明一个引用类型的变量保存在栈中,实际上保存的是一个引用类型的地址。
数据类型转换
在JavaScript的数据转换中, 分为显示转换和隐式转换。
其他类型转换为 Number 类型
- 手动转换
Number([value])parseInt([value])/parseFloat([value])
- 隐式转换
isNaN([value])- 数学运算 +、-、*、/、%(+运算,如果有字符串的情况下是字符串拼接)
- 比较运算 >、<、>=、<=、==、!= 的时候,有些值会转换为数字再比较
手动转换
Number()转换
代码示例:
// 字符串转换为数字
Number("123"); // 123
Number("123abc"); // NaN
Number(""); // 0
// 布尔值转换为数字
Number(true); // 1
Number(false); // 0
// null转换为数字
Number(null); // 0
// undefined转换为数字
Number(undefined); // NaN
Number(Symbol()); // 报错
Number(BigInt(1)); // 1
// 对象转换为数字 => 先基于 valueOf 方法转换为原始类型,没有原始类型在基于 toString 转换为字符串,在将字符串转换为数字
Number({}); // NaN
Number({ a: 1 }); // NaN
Number([1]); // 1
parseInt()/parseFloat()转换
parseInt 转换机制(parseFloat 类似,只是多识别一个小数点):
- 忽略字符串前面的空格
- 如果第一个字符不是数字或者负号,返回 NaN
- 如果第一个字符是数字或者负号,继续解析
- 解析到非数字字符,停止解析
- 返回解析到的数字
- 如果字符串以 0x 或者 0X 开头,会将其解析为十六进制数
- 如果字符串以 0 开头,会将其解析为八进制数
- 如果字符串以其他数字开头,会将其解析为十进制数
- 如果字符串以 0b 或者 0B 开头,会将其解析为二进制数
代码示例:
// 字符串转换为数字
parseInt("123"); // 123
parseInt("123abc"); // 123
parseInt(""); // NaN
// 布尔值转换为数字
parseInt(true); // NaN
parseInt(false); // NaN
// null转换为数字
parseInt(null); // NaN
// undefined转换为数字
parseInt(undefined); // NaN
parseInt(Symbol()); // 报错
parseInt(BigInt(1)); // 1
扩展
下列代码的结果是什么?
parseInt(""); // NaN
Number(""); // 0
isNaN(""); // true
parseInt(null); // NaN
Number(null); // 0
isNaN(null); // false
parseInt("1px"); // 1
Number("1px"); // NaN
isNaN("1px"); // true
parseInt("1.1px") + parseFloat("1.1px") + typeof parseInt(null); // 2.1number
isNaN(Number(!!Number(parseInt("1.8")))); // false
对于 parseInt 函数,有一个经典的面试题:
let arr = [1, 2, 3, 4];
arr = arr.map(parseInt);
console.log(arr); // [1, NaN, NaN, NaN]
为什么会出现这样的结果?这里要了解一下 parseInt([value], [radix]) 函数的规则。
radix是一个进制,不写或者写 0 表示 10 进制(特殊情况:如果 value 是以 0x 开头,则默认值是 10 或者 16),写 2 表示 2 进制,写 8 表示 8 进制,写 16 表示 16 进制。- 进制的范围是 2 ~ 36,超出这个范围,返回 NaN。
value是radix进制的字符串。需要将value转换为 10 进制的数字。
那么上面的执行就是下面的样子:
arr.map(parseInt)
=>
parseInt('1', 0); // 1
parseInt('2', 1); // NaN
parseInt('3', 2); // NaN
parseInt('4', 3); // NaN
=> [1, NaN, NaN, NaN]
隐式转换
isNaN([value])
在 JavaScript 中,isNaN(val) 函数用于检查val是否为 NaN(不是数字)。当调用isNaN(val)时,JavaScript 会尝试将val转换为数字类型。如果转换成功,则返回 false,表示val不是 NaN;如果转换失败,则返回 true,表示val是 NaN。
isNaN(123); // false,因为 123 是一个数字
isNaN("123"); // false,因为 '123' 可以被转换为数字 123
isNaN("abc"); // true,因为 'abc' 不能被转换为数字
isNaN(true); // false,因为 true 被转换为数字 1
isNaN(false); // false,因为 false 被转换为数字 0
isNaN(undefined); // true,因为 undefined 不能被转换为数字
isNaN(null); // false,因为 null 被转换为数字 0
isNaN({}); // true,因为 {} 不能被转换为数字
isNaN([]); // false,因为 [] 被转换为数字 0
isNaN(function () {}); // true,因为 function(){} 不能被转换为数字
数学运算(+、-、*、/、%)
示例:
1 + false; // 1
1 + true; // 2
1 +
null + // 1
"123"; // 123
+"123abc"; // NaN
+true; // 1
+false; // 0
+null; // 0
+undefined; // NaN
"123" % 1; // 0
"123abc" % 1; // NaN
true % 1; // 0
false % 1; // 0
null % 1; // 0
undefined % 1; // NaN
"123" * 1; // 123
"123abc" * 1; // NaN
true * 1; // 1
false * 1; // 0
null * 1; // 0
undefined * 1; // NaN
PS: 这里要注意的是,+ 运算如果两边有一个是字符串的话,就是字符串拼接。
比较运算(>、<、>=、<=、==、!=)
在比较运算中,会将两边的值转换为数字,然后再进行比较。
示例:
"123" < 1; // false
"123abc" < 1; // false
true < 1; // false
false < 1; // true
null < 1; // false
undefined < 1; // false
但是
看一段代码:
[] == 0 // true
[] == false // true
对象与数字/布尔的比较,都是转换为数字(隐式转换),然后再进行比较。
对象转换为数字先基于 valueOf 方法转换为原始类型,没有原始类型在基于 toString 转换为字符串,在将字符串转换为数字。
在上面的例子中:
[]基于 valueOf 获取原始值,[].valueOf() => [],发现[]没有原始值。[]没有原始值就调用toString转换为字符串,[].toString() => ''。''转换为数字为0false转换为数字为0。- 所以两者相等。
再看一个违反直觉的例子:
![] == false; // true
![] == 0; // true
这个例子的比较要先看符号的优先级, ! 优先级高于 == ,所以先执行 ![] ,然后再进行比较。这个就放到下面的其他类型转换为 Boolean 类型的内容中讲解。
其他类型转换为 String 类型
- 手动转换
String([value])toString([value])
- 隐式转换
+运算,如果有字符串的情况下是字符串拼接。- 有对象参与
+运算也会变成字符串拼接(例如:1 + [])。原因在与对象转换为数字的过程中会先将对象转换为字符,+遇到字符串就会变成字符串拼接。
手动转换
String()转换
代码示例:
String(123); // '123'
String(true); // 'true'
String(null); // 'null'
String(undefined); // 'undefined'
String({}); // '[object Object]'
String([]); // ''
String([1]); // '1'
String([1, 2]); // '1,2'
String(function () {}); // 'function() {}'
toString()转换
代码示例:
(123)
.toString()(
// '123'
true
)
.toString()(
// 'true'
null
)
.toString()(
// 'null'
undefined
)
.toString()(
// 'undefined'
{}
)
.toString()(
// '[object Object]'
[]
)
.toString()(
// ''
[1]
)
.toString()(
// '1'
[1, 2]
)
.toString()(
// '1,2'
function () {}
)
.toString(); // 'function() {}'
隐式转换
这个就不多讲了。我也没怎么用过。
其他类型转换为 Boolean 类型
- 手动转换
Boolean([value])!![value]![value]
- 隐式转换
if([value])语句whille([vale])语句- 逻辑运算符
(&&、||)
在 JavaScript 中,除了以下值会被转换为 false 以外,其他值都会被转换为 true:
- 0
- 空字符串("")
- null
- undefined
- NaN
- fasle 本身
具体代码就省略了。
数据类型检测
JavaScript 原生提供了多种方法来检测数据类型。
typeof
typeof是 JavaScript 中的一个内置的运算符,它返回一个字符串,表示操作数的数据类型。
-
优点:简单易用,性能强,能够检测基本数据类型(除了 null 之外)和函数类型。
-
确定: 不能检测对象类型,因为所有的对象类型都返回 "object"(包括 null)。
-
代码示例:
typeof 123; // "number" typeof "hello"; // "string" typeof true; // "boolean" typeof undefined; // "undefined" typeof function () {}; // "function" typeof null; // "object" typeof []; // "object" typeof {}; // "object" -
适用场景: 基本数据类型和函数类型的检测。
instanceof
instanceof 是 JavaScript 中的一个运算符,用于判断一个对象是否是某个构造函数的实例,也可以间接的判断一个对象是否是某个类的实例。
- 优点: 可以准确的检测所有 JavaScript 对象类型。
- 缺点:不能检测基本数据类型,对于基本数据类型,
instanceof会返回false。因为基本数据类型没有原型链。 - 代码示例:
'' instanceof String; // false 123 instanceof Number; // false true instanceof Boolean; // false null instanceof Object; // false undefined instanceof Object; // false function() {} instanceof Function; // true [] instanceof Array; // true {} instanceof Object; // true [] instanceof Object // true - 适用场景: 可以用来判断引用类型。
Object.prototype.toString.call()
Object.prototype.toString.call() 是 JavaScript 中一个常用的方法,用于获取一个对象的类型信息。他会返回一个表示对象的类型的字符串。例如:[object Array]。
- 优点: 可以准确的检测所有 JavaScript 对象类型,包括原始类型和复杂类型,并且可以自定义
toString方法。 - 缺点: 性能开销大,代码可读性差。
- 代码示例:
Object.prototype.toString.call(""); // "[object String]" Object.prototype.toString.call(123); // "[object Number]" Object.prototype.toString.call(true); // "[object Boolean]" Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call(function () {}); // "[object Function]" Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call({}); // "[object Object]" - 适用场景: 几乎任何场景下都可以适用。
constructor
constructor 属性是 JavaScript 中每个对象都具有的一个属性,它指向创建该对象的构造函数。通过检查 constructor 属性,我们可以判断一个对象的数据类型。
- 优点: 可以准确的检测所有 JavaScript 对象类型(除了 null 和 undefined 之外),也可以判断实例的构造函数。
- 缺点:无法区分基本数据类型和它的包装对象、无法检测 null 和 undefined、constructor 属性容易被修改、不直观和兼容性差。
- 代码示例:
"".constructor; // String (123).constructor; // Number true.constructor; // Boolean [].constructor; // Array ({}).constructor; // Object (function () {}).constructor; // Function - 适用场景: 可以用来判断某个对象是不是某个构造函数的实例。从而间接判断是不是引用数据类型。
扩展
对于 JavaScript 中的数组对象 Array 类型检测,我们除了用 typeof 之外的其他检测方式,也可以用 ES6 新增的方法 Array.isArray() 来检测。
代码示例:
Array.isArray([]); // true
Array.isArray({}); // false
总结
JavaScript 数据类型、类型转换和检测方法是JavaScript基础中的基础,请务必学好。