JS数据类型

38 阅读9分钟

直接上干货!!!

一. JS数据类型

原始值类型【值类型 / 基本数据类型】

  • number 数字

  • string  字符串

  • boolean 布尔

  • null 空对象指针

  • undefined 未定义

  • symbol  唯一值

  • bigint 大数

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

  •  标准普通对象 Object

  • 标准特殊对象 Array、RegExp、Date、Math、Error...

  • 非标准特殊对象 Number、String、Boolean...

  • 可调用/执行对象「函数」function...

1.1 Symbol

Symbol 创建一个唯一值

   - 给对象设置“唯一值”的属性名

             - 字符串

             - Symbol类型

             - Map新的数据结构:可以允许属性名是对象

   - Symbol.asyncIterator/iterator/hasInstance/toPrimitive/toStringTag ...是某些JS知识底层实现的机制

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

let a1 = Symbol('AA');
let a2 = Symbol('AA');
let a3 = a1;console.log(a1 === a2); //false
console.log(a1 === a3); //true
let key = Symbol('BB');
let obj = {   
    n: 10,    
    10: 100,    
    true: 200,    
    [Symbol('AA')]: 300,    
    [Symbol('AA')]: 600,   
    [key]: 400
};
console.log(obj[Symbol('AA')]); //undefined
console.log(obj[key]); //400

1.2 BigInt 

BigInt 大数类型

   Number.MAX_SAFE_INTEGER  9007199254740991 JS中的最大安全数

   Number.MIN_SAFE_INTEGER  -9007199254740991 最小安全数

   超过安全数后,进行运算或者访问,结果会不准确!!!

解决方案:

  1. 服务器返回给客户端的大数,按照“字符串”格式返回!

  2. 客户端把其变为 BigInt ,然后按照BigInt进行运算

  3. 最后把运算后的BigInt转换为字符串,在传递给服务器即可

console.log(BigInt('90071992547409912434234') + BigInt(12345));
console.log((90071992547409912446579n).toString());
// => 90071992547409912446579n
// => 90071992547409912446579

二. JS数据类型转换

JS中涉及数据类型转换场景

 + => Number   其他类型转为Number类型

 + => String 其他类型转为String类型

 + => Boolean 其他类型转为Boolean类型

 + '==' 比较的  会涉及数据类型转换

2.1 把其他类型转为Number类型

Number([val])

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

    @1 数学运算

    @2 isNaN检测

    @3 ==比较

    ...

  • 规则:

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

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

    @3 null->0  undefined->NaN

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

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

    @6 把对象转换为数字:

        + 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法

        + 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值

        + 再调用对象的 toString 把其变为字符串

        + 最后再把字符串基于Number方法转换为数字

parseInt([val], [radix])  /  parseFloat([val])

  • 一般用于手动转换

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

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

// 27.0 0  10进制有效数字  0-9 => 10进制
// 0, 1 => NaN
// '0013' 2 01  001=>10 
console.log(arr); //[27, NaN, 1, 1, 27]

// parseInt(27.2, 0)
// parseInt(0, 1)
// parseInt('0013', 2)
// parseInt('14px', 3)
// parseInt(123, 4)

/* 
parseInt(27.2,0) -> parseInt('27.2',10)   
'27' 把其当做10进制转换为10进制 => 27 
parseInt(0,1)    NaN 
parseInt('0013',2)    
'001' 当做2进制转换为10进制    
0*2^2+0*2^1+1*2^0 -> 0+0+1 => 1 
parseInt('14px',3)    '1' 当做3进制转换为10进制    1*3^0 -> 1 parseInt(123,4) -> parseInt('123',4)    
'123' 当做4进制转换为10进制    1*4^2+2*4^1+3*4^0 -> 16+8+3 => 27
*/

2.2 把其他类型转为String类型

转化规则:

    @1 拿字符串包起来

    @2 特殊:Object.prototype.toString  进行数据类型检测的

出现情况:

    @1 String([val]) 或者 [val].toString()

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

            + 有两边,一边是字符串

            + 有两边,一边是对象

            + 只出现在左边

            + ...

let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result);  //"NaNTencentnull9false"

console.log(10 + new Date()); //"10Mon Feb 20 2023 11:04:43 GMT+0800 (中国标准时间)"
// new Date()[Symbol.toPrimitive]('default') -> 'Mon Feb 20 2023 11:04:43 GMT+0800 (中国标准时间)'

console.log(10 + "10"); //"1010"
console.log(10 + new Number(10)); //20
// new Number(10)[Symbol.toPrimitive] -> undefined
// new Number(10).valueOf() -> 10// 10+10 => 20

console.log(10 + [10]); //'1010'
// [10][Symbol.toPrimitive] => undifined
// [10].valueOf() => [10] 不是原始值
// [10].toString() -> '10'  => 10+'10'

2.3 把其他类型转为Boolean类型

转换规则:

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

出现情况:

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

        @2 条件判断

2.4 “==”比较时候的相互转换规则

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

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

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

            null===undefined -> false

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

        @4 NaN !== NaN -> true 

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

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

console.log([] == false);  
//都转换为数字  0==0  => true

console.log(![] == false); 
//先处理 ![]=>false  false==false => true

三.  数据类型检测

平时项目中会涉及数据类型的检测

  + typeof

  + instanceof

  + constructor

  + Object.prototype.toString.call

  ----

  + Array.isArray()

  + isNaN

  + ...

3.1  数据类型检测 typeof 

typeof [value]

   + 底层机制:按照数据在计算机底层存储的“二进制”值进行检测,效率比较快

   + 局限性:

             + typeof null -> "object"   null的二进制值是64个零,而typeof认为前三位是零的都是object

             + typeof 除了能够区分函数对象,其余对象无法细分

             + typeof 函数 -> "function"

             + typeof [] -> "object"

             + typeof /^\d+$/ -> "object"

             + ...

   应用场景:检测除null以外的其他原始值类型、笼统的检测是否为对象

    if(obj!==null && /^(object|function)$/.test(typeof obj)){

            //obj是一个对象类型

     }

3.2  数据类型检测 instanceof 

instanceof 本意是检测某个实例是否属于这个类,“临时”拉来检测数据类型,可以用于“细分对象”

   + 无法处理原始值类型,返回结果都是false

      1 instanceof Number -> false

      new Number(1) instanceof Number -> true

     + 任何对象基于 instanceof 检测是否为Object实例,结果都是true,所以无法区分是否为“标准普通对象「纯粹对象」”

      let arr = [1, 2]

      arr instanceof Array -> true

      arr instanceof Object -> true

      obj instanceof Object -> true

   + 底层机制:

      实例 instanceof 构造函数

      @1 先检测 构造函数 是否拥有 Symbol.hasInstance 方法「ES6+之后,Function.prototype设置了Symbol.hasInstance这个方法,所以函数都具备这个属性」;

      如果有这个方法:构造函数Symbol.hasInstance 返回的值就是我们要的结果!

       用处:封装插件

     + 我们正常情况下重写是无效的 -> Array[Symbol.hasInstance]=function(){...}

     + 但是基于class创建的自定义类,可以重写其Symbol.hasInstance方法

      @2 如果没有这个方法(当前浏览器不支持es6语法 ),则按照原型链进行查找:按照实例的__proto__一直向上找,直到找到Object.prototype为止,只要在原型链上出现了 “构造函数.prototype”,说明当前实例属于它,结果返回true;如果没找到,结果就是false;

      例如:[10, 20]  Array.prototype和Object.prototype 都出现在了这个数组的原型链中,所以都返回true

      let arr = [10, 20]

      arr instanceof Array -> true

      arr instanceof Object -> true

      所以 所有的对象实例 instanceof Object 都是true, 因为Object出现在了原型链上

    总结:基于instanceof检测数据类型,缺点:

      + 无法检测原始值类型

      + 无法区分是否为“标准普通对象”

      + 一但原型链被重构,检测的结果是不准确

      + ...

    真实项目中,偶尔用于初步检测是否为特殊对象,例如:检测是否为正则、日期对象等...

3.3  数据类型检测 constructor 

if(对象.constructor === 构造函数){...}

     + 不准确:因为constructor可以被“肆意”重写

     + 相当于instanceof来讲,可以检测原始值类型,也可以判断是否为“标准普通对象”

       let arr=[],

           obj={},

           num=10;

       arr.constructor -> Array

       obj.constructor -> Object

       num.constructor -> Number   //默认进行装箱操作

     直接原型链指向Object,则 obj.constructor是Object

3.4  数据类型检测 Object.prototype.toString.call([value]) 

+ 内置构造函数的原型对象上,基本上都有toString这个方法,基本都是用来把值转换为字符串的,除Object.prototype.toString外,它是用来检测数据类型的;

       + dir(Function.prototype)

       + Number.prototype.toString()

       +

       let obj={}; //obj.proto===Object.prototype

       obj.toString(); //=>'[object Object]' 而不是 '{}'

   + 只需要把Object.prototype.toString执行,方法中的this是谁,就是检测谁的数据类型

      + 返回结果 “[object xxx]”

      + xxx一般是自己所属的构造函数

Object.prototype.toString.call(Math) ==> [object Math]  而Math是一个对象不是构造函数

不能new Math

      + 首先会看[value]值是否有 Symbol.toStringTag 属性,有这个属性,属性值是啥,检测出来的xxx就是啥;如果没有这个属性,才一般是按照自己所属的构造函数返回!!

        具备这个属性的值

        + Math[Symbol.toStringTag]:'Math'   Math

        + Promise.prototype[Symbol.toStringTag]:'Promise'   new Promise(()=>{})

        + Generator函数原型链上有   dir(function*(){})

        + Set.prototype[Symbol.toStringTag]:'Set'  Set.prototype

        + Map.prototype[Symbol.toStringTag]:'Map'  Map.prototype

        + ...

+ 优势:基本上属于检测最准确、最全面的方式了,能够区分null、能够检测原始值类型、能够细分对象、即便重构原型对象检测也是准确的...

      + 例:

        let obj2 = {0: 10, length: 1}  

        Object.setPrototype(obj2, Array.prototype)

        但是Object.prototype.toString.call(obj2) ==> [object Object]

四. Symbol的几个方法

4.1 Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

在 Symbol.toPrimitive 属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。hint 参数的取值是 "number"、"string" 和 "default" 中的任意一个。

// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
var obj1 = {};
console.log(+obj1);     // NaN num
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"

// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
var obj2 = {  
    [Symbol.toPrimitive](hint) {    
        if (hint == "number") {      
            return 10;    
        }    
        if (hint == "string") {      
            return "hello";    
        }    
        return true;  
    }
 };
 console.log(+obj2);     // 10      -- hint 参数值是 "number"
 console.log(`${obj2}`); // "hello" -- hint 参数值是 "string"
 console.log(obj2 + ""); // "true"  -- hint 参数值是 "default"
 

4.2 Symbol.hasInstance

Symbol.hasInstance 用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。

4.3 Symbol.iterator

根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。

由 for-of 语句使用”。换句话说,这个符号表示实现迭代器 API 的函数。

for-of 循环这样的语言结构会利用这个函数执行迭代操作。循环时,它们会调用以 Symbol.iterator

4.4 Symbol.toStringTag

Object.prototype.toString.call检测数据类型的底层实现。