十分钟搞定JS数据类型原理,面试必备

88 阅读9分钟

数据类型

原始数据类型

  • number 数字

  • string 字符串

  • boolean 布尔值

  • null 空对象指针

  • undefined 未定义

  • symbol 唯一值

  • bigint 大数

  • 补充说明

    • bigint
      • 作用:当数值大于最大安全数或者小于最小安全数时,数值的访问及操作都会不准确,可以通过bigint操作。
      • 使用场景:
        • 当数据库中存储了大于安全数的值并返回给前端时,前端无法操作及访问。
        • 解决方案:
          • 后端先将该值转化为字符串再传给前端
          • 前端再通过bigint做相应的逻辑处理
          • 前端再将bigint转换为字符串传递给后端即可
      • code
          console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
          console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
          // 如果值为 9007199254740992 > max
          let num = BigInt('9007199254740992') + BigInt('1')
          return num.toString(); // 9007199254740993
        
    • symbol
      • 作用:创建唯一值
      • 使用场景:
        • 给对象设置'唯一值'属性名
          • 默认情况下,对象属性值均为字符串
          • 通过symbol可以创建非字符串的属性名
        • 给Map数据类型作属性名使用
        • JS用于内置的API实现
          • for-of -- Symbol.iterator
          • instanceof -- Symbol.hasInstance
          • async/await -- Symbol.asyncIterator
          • 原始值转换 -- Symbol.toPrimitive
      • code
          let key = Symbol('AA');//创建唯一值
          let obj = {
              a:1,
              [key]:2,
              [Symbol('AA')]:3
          }
          console.log(obj[key]);//2
          console.log(obj[Symbol('AA')]);//undefined 每次Symbol创建的值都是唯一的,这种方式也是生成新的Symbol值
          console.log(Symbol('AA') === Symbol('AA'));//false
        

复杂数据类型[对象类型]

  • 标准普通对象 object
  • 标准特殊对象 Array、RegExp、Date、Math、Error...
  • 非标准特殊对象 Number、String、Boolean...
  • 可调用/执行对象 [函数] function

类型检测

通用型检测

typeof

  • 检测数据类型[引用类型不准确]
    • 原理:
      • 前置:所有的数据类型,在计算机底层都是按照计算机位(64/32)的二进制值进行存储的
      • 原理:根据数据存储于内存中的二进制值规律进行判断[效率高]。
        • 二进制的前三位是0,认定为对象,若该对象存在call方法,则为function,反之则为object
        • 0000... => null null值为64位0,所以null值会被认为是object
        • 1开头 => 整数
        • 010开头 => 浮点数
        • 110 => 布尔值
        • -2^30 undefined
        • 未被声明的变量 => undefined
    • code
        // 使用
        typeof 1 'number'
        typeof NaN 'number'
        typeof Infinity 'number'
        typeof '' 'string'
        typeof null 'objcet'
        typeof true 'boolean'
        typeof undefined 'undefined'
        typeof function a(){} 'function'
        typeof Symbol() 'symbol'
        typeof 10n 'bigint'
        typeof [] 'object'
        typeof {} 'object'
      

instanceof

  • 检测当前实例是否属于该类[无法区分对象与其他,一般情况下,对象原型是所有对象实例的基类]
    • 原理:只要当前类出现在实例的原型链上,结果都是true
    • 弊端:
      • 由于对象可以随意修改原型,所以可能出现不准确的情况
      • 不可鉴别出通过对象字面量创建的基本数据类型
    • code
      •   let arr = [];
          console.log(arr instanceof Array);// true
          console.log(arr instanceof Regexp);// false
          console.log(arr instanceof Object);// true
        
          let Fn = function Fn(){}
          Fn.prototype = Object.create(Array.prototype) // 修改构造函数原型
          const fn = new Fn();
          console.log(fn instanceof Array);// true -- 不准确
        
          console.log(1 instanceof Number);// false -- 不准确
        
          let num = new Number(1)
        
          console.log(num instanceof Number) ;// true -- 但是没用,基础类型基本不会这样声明
        
          /*
            手撕instanceof
            function _instanceof(example, classFn) {
              let classPrototype = classFn.prototype;
              let _proto = Object.getPrototypeOf(example); // 获取example.__proto__[浏览器下不允许直接这样获取]
              while (true) {
                  if (_proto === null) {
                      return false // 若是到头了,则说明没有找到
                  }
                  if (_proto === classPrototype) {
                      return true
                  }
                  _proto = Object.getPrototypeOf(_proto); // 获取原型上的__proto__,直至object
              }
            }
            let obj = {}
        
            console.log(_instanceof(obj, Array))
          */
        

constructor

  • 通过实例的构造器判断是否属于该类
    • 优点:比instance准确,但也有弊端
    • 弊端:与instanceof一样,constructor可以修改
    • code
      •   let arr = [];
          arr.constructor === Array;//true
          arr.constructor === Object;//false
          arr.constructor === Regexp;//false
        
          let num = 1;
          num.constructor === Number;// true
        
        

Object.prototype.toString.call

  • 改变this指向,返回当前实例所属类的信息,并不是返回字符串[标准数据类型检测方法]
    • 返回结果:[object Number/String/Boolean/Object....]
    • code
      •   Object.prototype.toString.call(1);//['object Number']
          Object.prototype.toString.call(null);//['object Null']
        
        

特定类型检测

  • Array.isArray
  • isNaN

类型转换

#其他数据转数字

  • Number[val]
    • 场景
      • 数字运算
      • isNaN检测
      • ==比较
    • 规则
      • 字符串转换为数字:空字符串转换为0,出现任何非数字字符,则转换为NaN
      • 布尔值转换为数字:true -> 1,false -> 0
      • null -> 0 undefined -> NaN
      • Symbol无法转换为数字[error]
      • BigInt去除n,若超过安全数的,按科学计数法处理
      • 对象转换为数字(对象为x)
        1. 调用x[Symbol.toPrimitive],有则调用该方法并返回该方法返回值,无则继续往下。
        2. 调用x[valueOf],获取原始值
          • 若x无原始值或原始值非数字,继续往下
          • 若x为原始值数字,则直接返回
        3. 调用x[toString],所有的对象都继承自Object,所以都有toString方法,
        4. 最后将该字符串转换为数字
        • 补充:
          • 若把对象x转换为字符串,走上述规则且没有第四步
          • x.toString() == String(x),不一定相等,步骤不同。
      • code
        let time = new Date();
        console.log(Number(time));// => 1.time[Symbol.toPrimitive]('number') => 当前时间戳
        let arr = [10];
        console.log(Number(arr));
        // => 1. arr[Symbol.toPrimitive] => undefined; 2. arr[valueOf] undefined; 3.arr[toString] => '10' => Number('10') => 10
        let num = new Number(10);
        console.log(Number(num));// 1.num[Symbol.toPrimitive] => undefined; 2. num.valueOf() => 10
      
  • parseInt & parseFloat
    • 使用:parseInt(val,radix),parseFloat(val) -> 十进制转换,直接转为字符串
      • 若val非字符串,则先隐式转换为字符串,通过String(val)的方式
      • radix为进制,默认为10
        • 若不传或者传0,则使用默认值
        • 若val以0x开头(16进制),则radix默认为16
        • 有效范围:2~36,若传入值不在范围内,返回NaN
      • 从val字符串左侧第一位开始查找,查找到符合radix进制的值(遇到不符合的则结束查找,不论后面是否有符合的),把符合的内容按照radix进制,转换为10进制返回。
        • 按权展开求和 -- 根据位数乘幂相加 parseInt('103',5) => 1 * 5^2 + 0 * 5^1 + 3 * 5^0 =》 25 + 3 -> 28
      • 补充:JS遇到以0开始的数字,会先自动以8进制转为10进制
    • code
        console.log(parseInt('10102px',2)); // => 2进制只识别1/0,10102px => 1010 => 转为10进制 => 1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 0 * 2^0 -> 10
        // 字节面试题
        let arr = [27.2,0,'0013','14px',123];
        arr = arr.map(parseInt);
        console.log(arr); // [27,NaN,1,1,27]
        // 实际代码执行
        arr.map((item,index)=>{
            /*
                27.2 - 0 => 0则默认10进制 ; 
                0 - 1 => 1不在进制范围,NaN ; 
                '0013' - 2 => 二进制只能识别1/0 => 1 ;
                '14px' - 3 => 3进制只能识别0123 => 1 ;
                '123' - 4 => 1 * 4^2 + 2 * 4^1 + 3 * 4^0 => 16 + 8 + 3 => 27
            */
            return parseInt(item,index);
        })
    
  • 其他类型转换String
    • 非{},用''包裹
    • {},Object.prototype.toString.call() -> '[object object]'
  • 其他类型转布尔值
    • 除了0/NaN/''/null/undefined五个值为false,其余皆为true

隐式转换

  • +号拼接
    • 当"+"号两端有一端是字符串或者某些对象,则先将对象转字符串,然后以字符串拼接的规则处理。
    • "+"号出现一端
      • let a = '10'; a++ -> 10; a -> 11
      • let a = '10';+a => 10
    • 数字其他类型
      • 布尔值 -> 1/0
      • null -> 0
      • undefined -> NaN -> 100 + NaN -> NaN
    • code
        console.log(10 + '10');//'1010'
        console.log(10 + new Number(10));// new Number(10)[Symbol.toPrimitive] -> undefined ;new Number(10).valueOf() -> 10; 10 + 10 -> 20
        console.log(10 + new Date()); // new Date([Symbol.toPrimitive])('default') -> 'GMT date' -> 10 + 'GMT date'
        console.log(10 + [10]);// [10][Symbol.toPrimitive] -> undefined;[10].valueOf() -> [10]; [10].toString() -> '10'; 10 + '10' -> '1010'
        let result = 100 + true + 21.2 + null + undefined + 'Tencent' + [] + null;
        /* true -> 1 ; null -> 0; 
          数字规则:undefined; 100 + true -> 101; 101 + 21.2 -> 122.2 -> 122 +null -> 122 -> 122 + undefined -> NaN 
          字符串规则:NaN + 'Tencent' -> 'NaNTencent' -> 'NaNTencent' + [] -> 'NaNTencent' + null -> 'NaNTencentnull'*/
        console.log(result);
      
  • ==
    • 两边数据类型不同,则需要先转为相同类型,再进行比较
      • 对象 == 字符串 对象转字符串 [Symbol.toPrimitive] => valueOf() => toString() ;当某一步骤有原始值字符串,则直接使用
      • null/undefined == undefined/null -> true ; null/undefined 和其他值都不相等
      • null === undefined -> false
      • 对象 == 对象,比较堆内存地址,地址相同则相等
      • NaN !== NaN ; NaN和任何数都不相同,所以补充机制(Object.isNaN())
    • 除了以上情况
      • 若只要两边类型不同,统一转为数字,然后再进行===比较,
        • ===比较,若两边类型不同,直接false,不会隐式转换。
    • code
      undefined == undefined ; // (==/===) -> true
      null == null ; // (==/===) -> true
      console.log([] == false); // true; 对象 & 布尔值 => 都转为数字再 [] [Symbol.toPrimitive] => valueOf() => toString() => '' => 0 === => 0 === 0 -> true
      console.log(![] == false); // true;![] -> false; false === false -> true
    
  • 包装类 & 解包装类
    • 当原始值调用方法是,会将原始值包装为对象调用方法
    • 当原始值对象参与运算时,会将原始值对象解包装为原始值参与运算
    • 以上两者皆为浏览器行为
    • code
        let num = 10;
        num.toFixed(2); // num => new Number(10).toFixed(2) => '10.00';
        let num1 = new Number(10);
        num1 + num; // num1 => [Symbol.toPrimitive] => valueOf() => toString() ... => 10 + 10 => 20
      

思考题

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

/*
  根据==判断会转换数据类型,判断出a为对象,则在其转换为数字的某一步骤重写方法即可
     [Symbol.toPrimitive] => valueOf() => toString() => Number()
     思路1:重写方法
     思路2:数据劫持+外部变量
     思路3:利用数组特性(开放性思路)
*/
// 当a为几,输出ok
if(a ==1 && a== 2&& a==3){
  console.log('ok')
}

// 一般思路
var a  = {
  i:0,
  [Symbol.toPrimitive](){
    return ++this.i
  }
  // toString(){
  //   return ++this.i
  // }
  // valueOf(){
  //   return ++this.i
  // }
};
// 大神思路
var a  = [1,2,3];
a.toString = a.shift;// a.shift => 截取数组第一位

0.1 + 0.2 ?== 0.3;// 为什么不等于0.3,精准度丢失

/*
  结论:JS中关于浮点数的计算会出现精度丢失的问题,计算机设计问题,非JS问题。
  原因:JS中所有值都是以二进制在计算机中存储
        整数转换为二进制是取余 -> 10 -> 10/2 = 5余0 => 5/2 = 2余1 => 2/2 = 1余0 => 1/2 =0余1 => 余数倒序拼接 -> 1010
        浮点数转换为二进制是乘法取整,可能会出现无限循环的情况
           0.1 * 2 = 0.2取0 => 0.2*2 = 0.4取0 => 0.4*0.2 = 0.8取0 => 0.8*2 = 1.6取1 => 0.6*2 = 1.2取1 => 0.2*2 = 0.4取0 ....
                          0                0                   0            1                   1           0   0 00
                          -> 000110001100011...
        计算机底层存储时,是以64/32位存储的,所以就舍弃了一些值,所以值就失去了精准度
  解决手法:
    运算结果.toFix(2); (0.1 + 0.2).toFix(2); 再转为数字; +(0.1+0.2).toFix(2)
    转成整数运算完再归成小数
*/