一文学会JS数据类型、类型转换和检测方法

230 阅读8分钟

JavaScript 数据类型

JavaScript的数据类型分两类。一种是基础数据类型,一种是引用数据类型。具体如下:

dataType.png

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 类似,只是多识别一个小数点):

  1. 忽略字符串前面的空格
  2. 如果第一个字符不是数字或者负号,返回 NaN
  3. 如果第一个字符是数字或者负号,继续解析
  4. 解析到非数字字符,停止解析
  5. 返回解析到的数字
  6. 如果字符串以 0x 或者 0X 开头,会将其解析为十六进制数
  7. 如果字符串以 0 开头,会将其解析为八进制数
  8. 如果字符串以其他数字开头,会将其解析为十进制数
  9. 如果字符串以 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。
  • valueradix 进制的字符串。需要将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 转换为字符串,在将字符串转换为数字。

在上面的例子中:

  1. [] 基于 valueOf 获取原始值,[].valueOf() => [] ,发现 [] 没有原始值。
  2. [] 没有原始值就调用 toString 转换为字符串,[].toString() => ''
  3. '' 转换为数字为 0
  4. false 转换为数字为 0
  5. 所以两者相等。

再看一个违反直觉的例子:

![] == 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基础中的基础,请务必学好。