前端基础重建 --- 1. JS数据类型及各种情况下的转换

998 阅读11分钟

写在前面

我,16 年二流大学非计科专业毕业,通过培训班强行转行,经历过 JQ 时代的刀耕火种,也经历了 Vue(React) 等 MVVM 框架的崛起。蹉跎四年时光,其间也有奋力汲取知识的时光。但是渐渐的,层出不穷的各种优秀框架、NodeJs、项目工程化、不断提高的算法要求,让我逐渐感觉身体被掏空(汇仁肾宝救不了我)。

经过认真的自我审视后,发现一切问题的源头都是 ===> JS 语言的基础没打牢 ,导致上层建筑如空中楼阁,底层基础摇摇欲坠。 由于目前的颓然,一直缺乏勇气将所有东西推倒重建,将地基重新打深打牢。昨夜无眠,思索背井离乡所图为何,辗转间又重新燃起原先的梦想。所以下定决心,打碎全身筋骨,摒弃所有不好的思想(我最喜欢的一句话:心里有光,哪都美!),望有一日可以重塑金身。

我期待我的人生做出改变,但千里之行,积于跬步。所以,一起来吧?


JS 中的数据类型

JS 中的数据类型分为两大类,基本数据类型引用数据类型

  • 基本数据类型
    • Number
    • String
    • Boolean
    • null
    • undefined
    • Symbol
    • Bigint
  • 引用数据类型
    • Object
      • 普通对象({}、Map)
      • 实例对象(new xxx)
      • 数组对象(Array、Set)
      • 正则对象(RegExp)
      • 日期对象(Date)
      • 数学函数对象(Math)
      • prototype 原型对象
      • 。。。
    • Function

注:

  1. 很多同学认为引用数据类型只有 object,function 属于 object。但是更专业的说法是 function 虽然属于 object,但是其主类型是 function,所以应该单独列举。举个简单的例子验证:

     typeof function(){} // 输出 ==> "function"
    
  2. Number 类型中存在一些奇怪的值:

    • NaN 不是有效数字,但是属于数字类型。

      typeof NaN  // ==> 'number'
      NaN === NaN  // ==> false
      // 所以
      // 可以通过isNaN()函数,检测一个数字是否为有效数字。
      // 在进行检测的时候,会先判断传入的值是否为数字类型。
      // 如果传入的值不是数字类型,会先进行隐式转换,
      // 将原值转换成数字类型的值(通过Number())再进行判断,检测是否为非有效数字
      isNaN(10)  // ==> 输出false
      isNaN('Test') // ==> 输出true
      isNaN('1') // ==> 输出false
      isNaN('') // ==> 输出false
      // =========================================
      // 还有一个例外情况,通过Object.is()方法判断NaN和NaN是否相等,得到的结果为true
      Object.is(NaN,NaN)  // ==> 输出 true
      
    • Infinity 表示无穷大的值

  3. Symbol(ES6),代表创建唯一值。 Symbol([value])

      console.log(Symbol('A') == Symbol('A')) // 输出 ==> false
      new Symbol()  // 报错
    
  4. BigInt(ES10),代表大数字值。可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数BigInt()

    作为预备知识,我们需要了解:

    在 JS 中,所有数字都以双精度 64 位浮点格式表示(按照 IEEE 754-2008 标准),在此标准下,无法精确表示的非常大的整数将被自动四舍五入。

    基于此原因,JS 中存在最大/小安全数,范围是**[-9007199254740991,9007199254740991]**。

    console.log(Number.MAX_SAFE_INTEGER) ==> 9007199254740991
    console.log(Number.MIN_SAFE_INTEGER) ==> -9007199254740991
    9007199254740991+2   // 当超过此范围的数字进行运算时,会出现不是预期的运算结果   ==> 9007199254740992
    

    所以又引入了 BigInt 类型,它可以对这些超过最大/小安全数的数字进行运算

    9007199254740991n + 9007199254740991n  // 输出 18014398509481982n
    

    另外,不能对混合使用NumberBigInt操作数执行算术操作。还不能将BigInt传递给 Web api 和内置的 JS 函数,这些函数需要一个 Number 类型的数字。尝试这样做会报TypeError错误

    10 + 10n // 输出TypeError
    Math.max(2n, 4n, 6n) // 输出TypeError
    

数据类型检测

检测方法汇总:

  • typeof :操作符返回一个字符串,表示未经计算的操作数的类型

    Undefined "undefined"
    Null "object"
    Boolean "boolean"
    Number "number"
    BigInt "bigint"
    String "string"
    Symbol (ECMAScript 2015 新增) "symbol"
    Function 对象 (按照 ECMA-262 规范实现 [[Call]]) "function"
    其他任何对象 "object"

    注:

    • 为什么typeof Null 返回“object”

    答:计算机在存储数据时使用的是二进制存储的,000 开头的数据代表的是对象,但是由于null的特殊性,它也是一个以 000 开头的数据,所以在识别的时候会误认为是对象,所以typeof Null => "object"。但是Null的类型其实不是object

    • typeof的局限性:

    由于 typeof 检测其他任何对象都会返回”object“,所以它无法判断细分的对象类型,比方说 Array、Date...

  • instanceof:用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

  • constructor: // 通过构造器判断

  • Object.prototype.toString.call([value]): // 通过 Object 对象上的 toString 方法

  • Object.is(a,b): 判断 a 与 b 是否完全相等,与===基本相同,不同点在于 Object.is 判断 +0 不等于-0NaN 等于自身

  • isNaN(any): 调用此方法判断是否为非数值。 会先将传入的值转为数字,在进行判断是否是有效数字。

根据数据类型汇总的相应方法

  • 原始类型判断: 见上面的 typeof 方法

  • 非原始类型判断(包含特殊值 null)

    • 判断数组
      • 使用Array.isArray()判断数组
      • 使用[] instanceof Array判断是否在 Array 的原型链上,即可判断是否为数组
      • 通过[].constructor === Array,构造函数判断是否为数组
      • 通过调用Object.prototype.toString.call([value])方法判断结果是否为'[object Array]'来判断数组
    • 判断对象
      • 通过调用Object.prototype.toString.call({})方法判断结果是否为'[object Object]'判断对象
      • 使用{} instanceof Object判断是否在 Object 的原型链上,即可判断是否为对象
      • 通过{}.constructor === Object,构造函数判断是否为对象
    • 判断函数
      • 通过func typeof function判断 func 是否为函数
      • 通过func instanceof Function判断 func 是否为函数
      • 通过func.constructor === Function判断 func 是否为函数
      • 通过调用Object.prototype.toString.call(func)方法判断结果是否为'[object Function]'来判断 func 是否为函数
    • 判断 null
      • 通过value === null是否为true来判断value是否为null,因为null === null
      • 通过(!value && typeof (value) != 'undefined' && value != 0 && value == value)判断 value 是否为 null
      • 通过Object.prototype.__proto__ === value 判断 value 是否为原始对象原型的原型即 null
      • 通过typeof (value) == 'object' && !valuetypeof判断value为对象,再根据对象类型只有null转换为Boolean值时为false判断value为 null

数据类型转换

我们将数据类型转换总体上分为四类,我们先列举出来,然后逐个分析。

  1. 将其他数据类型转换为Number类型
  2. 将其他数据类型转换为String类型
  3. 将其他数据类型转换为Boolean类型
  4. ==中涉及的类型转换

:数据类型转换主要分为显式类型转换隐式类型转换,从这两个方面进行分析。

1. 其他数据类型转换为Number类型

  • 显式类型转换

    • Number([val]),Number()函数的转换规则如下:
      • 如果是Boolean值,truefalse将分别转换成 0 和 1
      • 如果是null,返回 0
      • 如果是undefined,返回NaN
      • 如果是NaN,返回NaN
      • 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换成对应的十进制数字
      • 如果字符串中包含十六进制,则会转换为相同大小的十进制数值
      • 如果是对象,则先调用对象的 valueOf()方法,再依照前面的规则转换。如果转换的结果是NaN,则调用对象的 toString()方法,然后再按照前面的规则转换返回的字符串的值,得到最终值
      • 如果字符串中包含除了上述格式之外的字符,则将其转换为 NaN
  • parseInt([val])/parseFloat([val]):转换规则如下

    • 如果第一个字符不是数字字符或者符号,parseInt()就会返回NaN
    • parseInt() 方法首先查看位置 0 处的字符,判断它是否是个有效数字;如果不是,该方 法将返回 NaN,不再继续执行其他操作。但如果该字符是有效数字,该方法将查看位置 1 处的 字符,进行同样的测试。这一过程将持续到发现非有效数字的字符为止,此时 parseInt() 将 把该字符之前的字符串转换成数字。
    • parseInt()也能解析各种整数格式,如十六进制、八进制等
    • parseInt()提供了第二个参数,转换时使用的进制(有兴趣的同学移步 MDN)
    • parseFloat 转换规则与 parseInt 基本相同,但是可以多解析一个小数点.
  • 隐式类型转换(默认使用Number([val])方法转换为数字类型)

    • isNaN([val])
    • 数学运算(特殊情况:+在出现字符串的情况下不是数学运算,是字符串拼接)
    • 在==比较的时候,有些值需要转换为数字再进行比较
    • ...

2. 其他数据类型转换为String类型

  • 显式类型转换
    • toString()方法
    • String()
  • 隐式类型转换(使用toString()方法转换为字符串类型)
    • 加号运算的时候,如果某一边出现字符串,则当做是字符串拼接
    • 把对象转换为数字,需要先基于valueof方法取得对象的原始值,没有原始值的情况下再调用对象的toString()方法将其转换为字符串,然后再去转换为数字
    • 基于 alert/confirm/prompt/document.write...这些方式输出的内容,都是把内容先转换为字符串(toString()方法),然后再输出
    • ...

3. 其他数据类型转换为Boolean类型

  • 显式类型转换
    • Boolean()方法
  • 隐式类型转换(使用toString()方法转换为字符串类型)
    • !: 转换成Boolean类型值后取反
    • !!: 转换成Boolean类型
    • 在循环或者条件判断中,条件处理需要先转换成Boolean类型值再得出结果
    • ...

注: 其他数据类型转Boolean类型遵循以下规则:

只有 0NaNnullundefined空字符串 五个值会转换为Boolean类型的FALSE。其他情况均为TRUE


4. == 中涉及的类型转换

===

在分析==之前,我们先了解一下===:

  • ===属于严格判断,直接判断两者类型是否相同,不同则返回false
  • 如果两边类型相同再比较大小,不会进行任何隐式转换
  • 对于引用类型来说,比较的都是引用内存地址,所以===这种方式的比较,当且仅当两者的内存地址相同才相等,反之false

==:

  • 左右两端值类型相同:
    • {}=={}:输出false (对象比较的是堆内存的地址)
    • []==[]:输出false
    • NaN==NaN : 输出false
  • 左右两端值类型不同
    • null == undefined:输出true
    • null === undefined: 输出false(因为类型不一致)
    • null/undefined == 其它任何数据类型: 输出false。即null/undefined和其它任何数据类型值都不相等
    • String == Object : 将Object转换为String,进行比较
    • 剩下如果==两边数据类型不一致,全部需要转换为Number再进行比较
    • 把对象转换为数字,需要先基于valueof方法取得对象的原始值,没有原始值的情况下再调用对象的toString()方法将其转换为字符串,然后再去转换为数字

练手题:

parseInt("");    // 输出NaN
parseInt(null);  // 输出NaN
parseInt("12px") // 输出12
parseFloat("1.6px") + parseInt("1.2px") + typeof parseInt(null)  // 输出'2.6NaN'  1.6 + 1 + typeof NaN
Number("")       // 输出0
Number(null)     // 输出0
Number("12px")   // 输出NaN
Number(!!Number(parseInt("0.8")))  // 输出0  0.8 => 0 => 0 => false => 0
isNaN("")        // 输出false "" => 0 =>false
isNaN(null)      // 输出false  null => 0 => false
isNaN("12px")    // 输出true
isNaN(Number(!!Number(parseInt("0.8"))))  // 输出false isNaN(0)
typeof !parseInt(null) + !isNaN(null)
// 输出'booleantrue'
// typeof !(NaN) + !(isNaN(Number(null)))
// typeof true + !(isNaN(0))   => 'boolean'+ true => 'booleantrue'
let result = 12+false+undefined+[]+'Test'+null+true+{};
console.log(result);

// 12 + false => 12 + 0 => 12
// 12 + undefined => 12 + NaN => NaN
// NaN + [] => NaN + Number([]) => NaN + Number([].toString()) => NaN + Number('') => NaN
// NaN+'Test' => 'NaNTest'
// 'NaNTest'+null => 'NaNTestnull'
// 'NaNTestnull'+true => 'NaNTestnulltrue'
// 'NaNTestnulltrue'+ {} => 'NaNTestnulltrue'+ {}.toString() => 'NaNTestnulltrue[object Object]'

写在最后

欢迎访问我的博客fxflying.com