数据类型以及类型之间的相互转换

180 阅读8分钟

一、JS的数据类型

基本数据类型

  • number

  • string

  • boolean

  • null 空对象指针

  • undefined 未定义

  • Symbol 创建一个唯一值,Symbol不能被new

    let a1 = Symbol('AA'); 
    
    let a2 = Symbol('AA');
    
    let a3 = a1;
    
    console.log('A1 === A2'); //false
    

    应用场景:

    1. 没有ES6之前,属性名类型只能是字符串,ES6之后,现在属性名也可以是Symbol类型

      let key = Symbol('BB');
      let obj = {
          n: 10,
          10: 100,
          [Symbol('AA')]: 300,
          [key]: 400
      }
      console.log(obj[Symbol('AA')]); //undefined
      console.log(obj[key]); //4400
      
    2. Symbol.asyncIterator / iterator / hasIntance / toPrimitive / toStringTag... 是某些JS知识底层实现的机制

    3. vuex在派发行为标识统一进行管理的时候,可以基于Symbol类型的值, 保证行为标识的唯一性

  • BigInt大数类型

    Number.MAX_SAFE_INTEGER:最大安全数900719925474099116位」
    
    Number.MIN_SAFE_INTEGER:最小安全数-9007199254740991
    
    超过安全数后再进行运算或者访问,结果会不准确!!
    

    需求:

    服务器数据库存储值有logInt(大数类型),如果服务器返回这样的值,而且需要客户端在此基础上进行运算,但是由于客户端有最大安全数,超过这个数字进行运算, 运算结果不一定准确
    

    解决方案:

    - 服务器返回给客户端的大数,按照“字符串”格式返回
    - 客户端把其变为BigInt,然后按照BigInt进行运算
    - 最后把运算后的BigInt转换为字符串,再传递给服务器就可以
    console.log((BigInt('9007199254740991898') + BigInt('12345')).toString())
    

对象类型【引用数据类型】

  • 标准对象Object

  • 标准特殊对象:

    • 数组Array
    • 正则RegExp
    • 日期Date 实例
    • 数学Math
    • Error
  • 非标准特殊对象:Number、String、Boolean:基于构造函数或者Object创造出来的原始值对象类型的格式信息,类型属于对象类型(Symbol和BigInt不能被new)

  • 可调用执行对象:函数function

二、JS中有关于小数(浮点数)的计算会出现精度丢失的问题:0.1+0.2 !== 0.3

原因: JS中所有值都是以二进制在计算机底层进行存储的,就会涉及到一个问题,它会默认把十进制值转为二进制值「而浮点数转为二进制会出现无限循环的情况」,但是我们在计算机底层存储的时候最多存储64位,那就说明舍弃了一些值,直接按64位存储,说明值本身就失去了精准度,所以如果拿失去精准度的值和另外一个失去精准度的值运算,运算的结果肯定也是失去精准度的。

解决方案:

  • toFixed保留小数点后面N位,他自己会四舍五入
  • 扩大系数法

image.png

十进制转二进制:

(10).toString(2); //'1010'
(0.1).toString(2) //'0.0001100110011001100110011001100110011001100110011001101'

附图:第一张图为10转为二进制,第二张图为0.1转二进制

image.png

image.png

三、数据类型检测

typeof

  • 所有的数据类型值在计算机底层都是按照"644位"二进制进行存储

  • typeof是按照二进制进行检测类型的

  • 二进制的前三位是零,认为是对象,然后再去看有没有实现call方法,如果实现了,返回'function',如果没有实现,返回'object'

局限性

  • typeof null :null的二进制是64个0,而typeof认为前三位是0的都是'object'

  • typeof 检测对象,除函数对象会返回"function",其余对象返回的都是"object",不能细分对象

  • 检测未被声明的变量,不会报错,会返回'undefined' 应用

  • 检测除null之外的原始值类型都可以使用typeof

  • 检测是否为对象,if ( obj !== null && /^(object|function)$/i.test(typeof obj))

  • 检测某个东西是否兼容:if (typeof Symbol !== 'undefined') {...}

  • 支持更多的模块导入方案

         (function(){
             let utils = {};
             if( typeof window !== 'undefined' ) window.utils = utils;
             if( typeof module === 'object' && typeof module.export === 'object' ) module.export = utils;
         })();
    

instanceof 检测对象是否为这个类的实例: [对象] instanceof [构造函数]

局限性:

  • 对原始值类型无效,instanceof左边只要是原始值类型,结果就是false

  • 因为可以通过setPrototypeOf修改原型链指向,所以检测结果不一定准确

  • 无法检测是否为“标准普通对象(Object的实例,直接找object原型),因为所有对象都是Object的实例,检测结果都是true(当使用instance判断是否是该对象的实例时,会调用Symbol.hasInstance这个方法)

         //当执行arr instance  Array的时候
            arr instance  Array ; //true
        //相当于
            Array[Symbol.hasInstance](arr); //true
    

原理:

  • 传统版本:instanceof检测是按照实例的原型链进行查找的,只要构造函数的prototype出现在了对象的原型链上,那么检测结果都是true

  • 新版本(es6):先检测构造函数是否拥有Symbol.hasInstance方法「ES6之后,Function.prototype设置了Symbol.hasInstance方法,所以函数都具备这个属性」,如果有这个方法,构造函数Symbol.hasInstance,返回的值就是我们要的值

        let arr = [];
        arr instanceof Array; //true
        arr instanceof RegExp; //falsee
        arr instanceof Object; //true
    
        1  instanceof  Number;//false,不能检测基本数据类型
    
        let obj = {}
        Object.setPrototypeOf(obj, RegExp.prototype)
        console.log(obj instance RegExp) //true
    

constructor 本意:获取构造函数

  • constructor可以随便改,所以也不准确

  • 可以检测是否为标准普通对象(如下面例子数组.constructor 不等于object)

  • 对原始值也有效(除null/undefined,因为他俩无法进行成员访问)

        let arr = [];
        arr.constructor === Array ; //true
        arr.constructor === RegExp ; //false
        arr.constructor === Object ; //false
    
        let num = 1;
        num.constructor === Number //true
    

Object.prototype.toString.call( [value] )

除Object.prototype.toString之外,其余构造函数原型上的toString一般用来“转换字符串的”,只有它是用来检测数据类型的,检测结果为[object xxx]

原理:

首先会看这个被检测的value值有没有Symbol.toStringTag这个属性,有的话,属性值是什么,则结果中的xxx 就是谁,没有这个属性,一般当前实例所属的构造函数(以内置的为主)

  • Array.isArray()
  • isNaN

四、数据类型间相互转换

1.把其他类型转换为number

一般用于浏览器的隐式转换中

@1 数学运算

@2 isNaN检测

@3 ==比较

规则:

  • 字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN

  • 把布尔转换为数字:true->1 false->0

  • null->0 undefined->NaN

  • Symbol无法转换为数字,会报错:Uncaught TypeError: Cannot convert a Symbol value to a number

  • BigInt去除“n”(超过安全数字的,会按照科学计数法处理)

  • 把对象转换为数字:

      +  先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法
    
      + 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值
    
      + 再调用对象的 toString 把其变为字符串
    
      + 最后再把字符串基于Number方法转换为数字
    

2.parseInt([val],[radix]) parseFloat([val])

  • 一般用于手动转换

  • 规则:[val]值必须是一个字符串,如果不是则先转换为字符串;然后从字符串左侧第一个字符开始找,把找到的有效数字字符最后转换为数字「一个都没找到就是NaN」;遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了;parseFloat可以多识别一个小数点;

      练习题:
    
      let arr = [27.2, 0, '0013', '14px', 123];
    
      arr = arr.map(parseInt);
      
    

3.把其他数据类型转换为String

转化规则:

  • 拿字符串包起来

  • 特殊:Object.prototype.toString

出现情况:

  • String([val]) 或者 [val].toString()

  • “+”除数学运算,还可能代表的字符串拼接

      + 有两边,一边是字符串
    
      + 有两边,一边是对象
    
      + 只出现在左边
    
      + ...
    
    
      练习题:
    
      let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
    
      console.log(result);
    

4.把其他数据类型转换为Boolean

转换规则: 除了“0/NaN/空字符串/null/undefined”五个值是false,其余都是true

出现情况:

@1 Boolean([val]) 或者 !/!!

@2 条件判断

5.==”比较时候的相互转换规则

“==”相等,两边数据类型不同,需要先转为相同类型,然后再进行比较

  • 对象==字符串 对象转字符串「Symbol.toPrimitive -> valueOf -> toString」

  • null==undefined -> true null/undefined和其他任何值都不相等

  • null===undefined -> false

  • 对象==对象 比较的是堆内存地址,地址相同则相等

  • NaN!==NaN

  • 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较的

“===”绝对相等,如果两边类型不同,则直接是false,不会转换数据类型「推荐」

练习题:

console.log([] == false);

console.log(![] == false);

五、什么情况下a == 1 && a ==2 && a==3

var a = ?;

if (a == 1 && a == 2 && a == 3) {

    console.log('OK');

}

方案一、利用==把a转为数字,再根据对象转数字会经历一系列的操作,此时可以重写一些步骤来实现

var a = {
      i: 0,
      //重写Symbol.toPrimitive/valueOf/toString
      [Symbol.toPrimitive](){
        return ++this.i;
      }
}
// a==1 ->Number(a) -> a[Symbol.toPrimitive]()
if(a==1 && a==2 && a==3) {
      console.log('OK')
}

方案二、在全局上下文中基于var声明变量,是给window对象设置属性,再利用数据劫持


var i = 0;
Object.defineProperty(window, 'a', {
       get() {
              return ++i;
       }                     
})
if(a==1 && a==2 && a==3) {
      console.log('OK')
}

方案三

var a = [1,2,3]
a.toSting = a.shift;
if(a==1 && a==2 && a==3) {
      console.log('OK')
}