关于数据类型的这些陷阱,你都避开了吗?👻

518 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

1、判断是不是Object

通过如下代码能否准确判断一个数据是否为Object呢?

function isObject(obj) {
  if (typeof obj === "object") {
    return true;
  }
  return false;
}

由于typeof null的结果也是"object",所以以上代码不能够准确判断出一个数据是否为object

那么typeof null为什么为"object"呢?

这个问题要追溯到JavaScript的第一个版本:

  • 在这个版本中,单个值在栈中占有32位的存储单元,其分为两部分,标记位与数据部分,此版本中只有5种数据类型
// 类型标记位
// 000: object
// 001: integer
// 010: double
// 100: string
// 110: boolean

null是个特别的存在,在机器码中null的标记位与数据部分全是0,也就是32个0,所以typeof 在判断null时会返回object

那么为什么不修复这个问题呢?

考虑到有很多兼容性问题,如果修复了这个历史的问题会导致后续有很多兼容性问题产生,不得不做妥协

2、使用一元运算符+转为数字

const print = console.log;

function toNumber(val) {
  const result = +val;
  print(result);
}

toNumber(null); // 0
toNumber(undefined); // NaN
toNumber(1); // 1
toNumber("123aa"); // NaN
toNumber({}); // NaN
toNumber(true); // 1

当使用+操作nullundefinednumberstringobjectboolean时是没有问题的

但如果使用+操作bigint类型数字或者symbol时会报错:

toNumber(10n);
toNumber(Symbol.for("a"));

均会报如下错误:

此问题产生的原因也是兼容性问题,在ES5的数据类型中使用此方法转换为数字是没有问题的,但在ES6+的新数据类型中要注意此问题的产生。

3、字符串数组批量转为整数

var results = ["1", "2", "3"].map(parseInt);
console.log(results); // [ 1, NaN, NaN ]

以上代码执行完毕的结果为[1, NaN, NaN],这是为什么呢?

我们将此代码拆解开来:

["1", "2", "3"].map((val,index)=> parseInt(val,index))
// parseInt("1",0)
// parseInt("2",1)
// parseInt("3",2)

所以代码执行过程中相当于依次执行parseInt("1", 0)、parseInt("2", 1)、parseInt("3", 2)

parseInt可以接收第二个参数,用于指定低数(进制数)

  1. parseInt("1", 0),第二个参数为0,默认是不生效的,所以会当成10进制,最终结果为1
  2. parseInt("2", 1),第二个参数为1,1进制中是不可能出现2的,所以将字符串2转换为1进制时会变为NaN
  3. parseInt("3", 2),同理,2进制中是不可能出现3的,所以结果也会是NaN

4、if条件判断

比如,想判断obj上是否有name属性,如果有name属性则将obj的name属性值设置给result的name属性

const result = {};
// name存在
if(obj.name){
  result.name = obj.name;
}
return result;

由于if 中的条件会进行布尔值转换,一旦obj的name属性值为0null则均会判断为false,此时if条件内的代码将不会执行

会转换为false的值有:0+0-0nullNaNfalse''(空字符串)undefined

所以如果真想判断一个对象上是否存在某个属性,要使用Object.hasOwnProperty

5、宽松比较

console.log(null == 0); // false
console.log("0" == false); // true

宽松比较规律如下

-   NaNNaN与任何类型的值均不相等,包括其本身
-   bigInt、Symbol:首先会比较是不是同类型,不是同类型直接不相等,然后再比较值是否相等
-   nullundefinednull只会与null或者undefined相等;undefined只会与undefinednull相等
-   布尔类型和其他类型做相等比较时:布尔值会转换成数字再与其他类型值做比较
-   数字类型和字符串类型做相等比较时:字符串会转成数字然后做比较
-   对象类型和原始类型做相等比较时:对象类型会转成原始类型然后做比较

基于以上几点规律,来看上段代码:

  1. 由于在宽松比较中null只与null或undefined相等,所以会输出false
  2. 布尔类型在于其他类型值比较时会转为数字,所以false会转为0,然后相当于"0" == 0,数字与字符串比较时,字符串会转为数字,所以 0 ==0会输出true

Tip: 需要注意的是,以上几点规律适用于宽松比较,比如console.log(null == 0)会输出false,但console.log(null >= 0)会输出true

  • 这是因为在相等操作符中null与undefined不会转换为数字,所以null == 0为false
  • 而在关系运算符中null、undefined会被Number()转换为数字,其中Number(null)为0、Number(undefined)为NaN,所以这也就是为什么null >= 0为true,undefined >=0 为false