JS之数据类型分类与转换

142 阅读10分钟

数据类型分类

原始数据类型

  •  number: NaN【不是一个有效数字】、Infinity【无穷大的值】,1,11
  •  string: 基于 单引号/双引号/反引号` 包起来的都是字符串
  •  boolean: true/false
  •  null
  •  undefined
  •  symbol: 唯一值
  •  bigint:大数

对象类型

  •  标准普通对象 object,如{num:100}
  •  标准特殊对象 Array/RegExp/Date/Error/Math/ArrayBuffer/DataView/Set/Map...,如[10,20]
  •  非标准特殊对象 Number/String/Boolean/Symbol/Bigint...【基于构造函数或Object创造出来的原始值对象类型的格式信息,类型属于对象类型】
  •  可调用对象【实现了call方法】 function,如fn(){}

要点:

NaN

  • NaN === NaN//false 不能基于“是否等于NaN”来检测值是否为有效数字
  • isNaN([value]) 不论[value]是什么类型,默认隐式转换为数字类型Number([value]),再校验是否为有效数字,如果是有效数字,返回false,不是有效数字返回true
Object.is(NaN,NaN):true【不兼容IE,Edge除外】
Object.is不会隐式转换
Object.is('123',123)//false
Object.is(NaN,'123a')//false
isNaN('123a')//true
Object.is(10,20)//false

Symbol

console.log(Symbol() === Symbol()); //false 创建了两个唯一值
console.log(Symbol('AA') === Symbol('AA')); //false

适用场景

  • 对象的唯一属性

给对象设置唯一的属性「对象的属性名类型:字符串 & Symbol类型」

let key = Symbol()
let obj = {
    [key]: 100 //这里必须使用[]
}

console.log(obj[key])//100
let arr = Object.getOwnPropertySymbols(obj)//获取当前对象所有Symbol类型的私有属性,输出结果是数组
arr.forEach(item => {
    console.log(obj[item])//100
})

对象作为属性,会被覆盖

let a = {
    name: 'zhufeng'
};
let b = {
    name: 'web'
};
let obj = {};
obj[a] = 100; //obj["[object Object]"]=100
obj[b] = 200; //obj["[object Object]"]=200
console.log(obj[a]); //200
  • 宏观管理标识

保证标志唯一性

redux/vuex公共状态管理的时候,派发的行为标识就可以基于Symbol类型进行宏管理

  • Symbol.hasInstance\Symbol.toStringTag\Symbol.toPrimitive\Symbol.iterator...很多JS底层的处理机制,就是基于这些属性方法实现的

Symbol.hasInstance
Symbol.iterator
Symbol.toPrimitive
Symbol.toStringTag
...

Symbol.toPrimitive应用

原理

xxx[Symbol.toPrimitive](hint){
    // hint:'number' / 'string' / 'default'
    //   + number:获取当前对象的数字类型的原始值
    //   + string:获取当前对象的字符串类型的原始值
    //   + default:根据操作获取数字或者字符串类型的原始值
}

重写

let obj = {
    name: 'zhufeng',
    age: 12,
    [Symbol.toPrimitive](hint) {
        let result;
        switch (hint) {
            case 'number':
                result = 0;
                break;
            case 'string':
                result = JSON.stringify(obj);
                break;
            default:
                result = "";
        }
        return result;
    }
};
console.log(Number(obj)); // hint:"number"
console.log(String(obj)); // hint:"string"
console.log(10 + obj); // hint:"default"
console.log(10 - obj); // hint:"number" 

bigint

数字后面加个n就是大数类型

  • 10 Number类型
  • 10n BigInt类型

若超过最大安全数或小于最小安全数,就会出现计算不准确,受限于浏览器的计算精准度

console.log(Number.MAX_VALUE)//1.7976931348623157e+308
console.log(Number.MAX_SAFE_INTEGER)//9007199254740991
console.log(Number.MIN_VALUE)//5e-324
console.log(Number.MIN_SAFE_INTEGER)//-90071992zuida54740991

问题:服务器中有longInt长整型这种值,如果把这样的值返回给客户端,则客户端无法进行有效的处理,一般服务器都是以字符串返回,但是字符串进行计算还是需要转换为数字才可以,还是不准确

可以使用BigInt处理,把服务器返回的值变为bigint格式的,然后进行运算「保证了运算的准确性{进行运算的另外一个值也应该是bigint类型的}」;把运算的结果,再次变为字符串,发送给服务器即可!!

console.log((BigInt('9007199254740992134') - BigInt('1')).toString())//'9007199254740992133'

面试题:

  • 精确计算0.1+0.2
(0.1+0.2)*10/10	//0.3
  • 精确计算0.1+0.0000002
parseInt((BigInt('100000')+BigInt('2')).toString())/1000000  //0.100002
字面量:原始值
let n = 10;
构造函数:对象值
let m = new Number(10);
typeof n	//'number'
typeof m 	//'object'
注意SymbolBigInt不能通过这种方式
new Symbol()	//Uncaught TypeError: Symbol is not a constructor
new BigInt()	//Uncaught TypeError: BigInt is not a constructor
但是:
let c = new Object(Symbol(10))
typeof c //"object"
let d = new Object(BigInt(10))
typeof d //"object"

数据类型转换规则

其它类型 【原始值】 转换为 对象:Object([value])

其它类型 转换为 数字

Number([value])

  • 一般用于隐式转换[数学运算、isNaN、==比较...];
  • 用于字符串转换为数字,空字符串会变为0,字符串只要出现非有效字符串结果就是NaN
  • 布尔转换为数字    true 1 false 0
  • null 转换为 0
  • undefined转换为 NaN
  • 转换Symbol会报错
  • 转换BigInt 就会 正常转换
  • 对象转换遵循 Symbol.toPrimitive/valueOf/toString/Number
Number('')//0
Number('ab')//NaN
Number('10n')//NaN
Number(true)//1
Number(null)//0
Number(undefined)//NaN
Number(Symbol())//报错Uncaught TypeError: Cannot convert a Symbol value to a number
Number(10n)//10

parseInt/parseFloat([value])

  • 首先会把 [value] 变为字符串,从字符串左侧第一个字符开始查找,查到找到一个非有效数字字符为止,把找到的结果转换为数字,一个都没找到,结果就是NaN ,而parseFloat会多识别一个小数点
parseInt('12.ad')//12
parseFloat('12.ad')//12
parseFloat('12.01ad')//12.01
parseFloat('ab12.01ad')//NaN
parseInt('null')//NaN

其它类型 转换为 字符串

原始值转换

直接用引号包起来【bigint会去除n】;

'12'//'12'
(10n).toString()//'10'
(Symbol()).toString()//"Symbol()"

把对象obj转换为字符串

两种方式:

  • String(obj):Symbol.toPrimitive -> valueOf -> toString 浏览器默认隐式转换用的是String(obj)
  • obj.toString() :直接调用这个方法转字符串,不会在执行以上的规则

除对象转换为字符串是比较特殊的,其它都是下面的情况:

  • 使用toString() 可以转换为字符串【 排除Object.prototype.toString{检测数据类型}】
  • 字符串/模板字符串拼接 【"+"在JS中除了数学运算,还有字符串拼接功能,但是其它运算符一般都是数学运算】

带“+”表达式的情况分析

CASE1:“+”只有一边,直接转换成数字

基于:Number(xxx)

let n = '10'
+n	//10	转换为数字
++n	//11	转换为数字然后累加1
n++	//11	转换为数字然后累加1

面试题:i++ 和i=i+1以及i+=1 三个是否一样?

不一样,i=i+1和i+=1是一样的

i++一定返回的是数字,但是i=i+1不一定返回的是数字,有可能是字符串拼接

CASE2:“+”有两边,有一边出现了字符串或者部分对象

按照字符串拼接处理的,对象基于String(xxx),其他基于toString()

1+'1'	//'11'
'number'+[];//'number'+[].toString()=>'number'+""=>'number'
'number'+{};//'number'+String({})=>'number'+"[object Object]"=>'number[object Object]'

CASE3:“+”有两边,有一边是对象

遵循规则:

@1 调用objSymbol.toPrimitive

@2 没有这个属性,则再次调用valueOf

@3 valueOf获取的不是原始值,则继续toString,此时获取的结果是字符串,“+”就变为字符串拼接了

let n = "10"
{}+10 //10 把左侧当成代码块,,不参与运算,运算只有+n
10+{} //先调用{}的toString方法,转为字符串"[object Object]",再进行字符串拼接,所以就是"10[object Object]"	字符串拼接
[]+10 //10
10+[]//10
console.log(10 + [10]);
// 没有Symbol.toPrimitive -> valueOf获取的也不是原始值 -> 调用toString "10"  => "1010"
console.log(10 + {});
// 没有Symbol.toPrimitive -> valueOf获取的也不是原始值 -> 调用toString  "[object Object]"  => "10[object Object]"
console.log(10 + new Date());
// 调用日期的Symbol.toPrimitive('default') => "10Sun Jul 25 2021 11:28:37 GMT+0800 (中国标准时间)"
console.log(10 + new Number(10));
// 没有Symbol.toPrimitive -> valueOf 10 => 20
console.log(10 + new String('10'));
// 没有Symbol.toPrimitive -> valueOf "10" => "1010"

let obj = { x: 10 }
console.log(10 + obj)	//"10[object Object]"
//若要得到20的结果,怎么做呢?
//利用上述规则改造
let obj = {
    x: 10,
    [Symbol.toPrimitive](hint) {//hint取值:default、string、number。推荐使用hint
        return this.x
    }
}
console.log(10 + obj)		//20

CASE4:   “+”有两边, 剩下的情况一般都是数学运算

1+2//3
100+true;
//100+Number(true)=>101
100+undefined
//100+Number(undefined)=>100+NaN=>NaN

其它类型 转换为 布尔类型

规则

只有"0、NaN、null、undefined、空字符串"会变为false,其余都是转换为true

转换方式(五种)

  • Boolean([value])
  • !![value]
  • ![value] 转换为布尔类型取反
  • 条件判断 例如: if(1){}
  • A||B    A&&B
!![]//true
!!-1//true

JS中验证两个值是否相等

  • ==:相等【若两边类型不一样,首先会隐式转换为相同类型,然后再做比较】
    • 对象==字符串 对象会转成字符串
    • null==undefined //true 【===不相等】,但是null/undefined和其它任何值都不会相等
    • NaN==NaN    //false    
    • Symbol()==Symbol( )    //false
    • 其它情况【例如:对象==数字、字符串==布尔...】都是转换为数字,再进行比较的
  • === : 绝对相等 【要求两边类型和值相等,例如:switch case】
  • Object.is([val], [val])
  • ...
Object.is(NaN, NaN)//true NaN本身并不相等
NaN === NaN //false
NaN == NaN //false
Object.is('10', 10)//false
null == ''//false
null == 0//false
undefined == ''//false
undefined == 0//false
isNaN(NaN)//true
'' == false//转换成0 == 0true
''==0;//转换成0 == 0 ,true

题目:

  1. 下面两个答案是
![]==false	//true []转成true,![]就是false
[]==false	  //true	属于对象==布尔比较,[]遵循对象转换为数字规则,最终[].toString()得到'',
									最后Number('')即为0,false转换为数字就是0,最终[]==falsetrue
  1. a等于多少,才能输出'OK'?

方案1:重写Symbol.toPrimitive

掌握数据类型转换规则,若a是一个对象,我们就可以利用“对象->数字”的规则去做一些处理

利用 == 比较的时候,会把对象转换为数字 Number(a)

  • Symbol.toPrimitive
  • valueOf
  • toString
  • 把字符串变为数字
var a = {
	i:0,
  [Symbol.toPrimitive](){
  	return ++this.i
  }
}

方案2:重写toString

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

方案3:数据劫持

在全局上下文中,基于 var/function 声明的变量,并不是给VO(G)设置的全局变量「基于let/const声明的变量才是」,而是给GO(window)全局对象设置的属性 var a=? ==> window.a=?

let i=0;
Object.defineProperty(window, 'a', {
	get(){
  	return ++i;
  }
})

面试题

let result = 100+true+21.2+null+undefined+'Tencent'+[]+null+9+false;
console.log(result)	//'NaNTencent9null9false'
//100+true 加号两边没有对象,true会使用Number(true)隐式转换成数字1,结果:101
//101+21.2+null 加号两边没有对象,null会使用Number(null)隐式转换成数字0,结果:122.2
//122.2+undefined 加号两边没有对象,undefined会使用Number(undefined)隐式转换成NaN,结果:NaN
//NaN+'Tencent' 加号有字符串,NaN会隐式转换为字符串'NaN',结果:'NaNTencent'
//'NaNTencent'+[]+null+9+false 由于出现了字符串,所以后面都转成字符串拼接,[]调用toString方法转成空字符串'', 结果:'NaNTencent9null9false'

parseInt剖析

parseInt([string],[radix]) [radix]是进制,取值范围2-36,默认值10进制,但是若字符串是以“0x”开头,默认16进制,若radix值是0,和radix等于10效果一致.

  • 把[string]看做[radix]进制从左侧找到所有符合这个进制的字符,遇到不符合的结束查找,把找到的字符转换为【10进制】数字
  • 若radix<2或radix>36,则结果为NaN

注:浏览器看到0123会自动转成83,所以parseInt(0123)实际上是parseInt(83),则结果就是83

let arr = [27.2, 0, '0013', '14px', 123]
arr = arr.map(parseInt) //重点:还原map完整写法
console.log(arr)
==================================
arr = arr.map((item,index)=>{
	//数组有多少项遍历多少次,回掉函数执行多少次
  //item->当前项 index->当前项索引
  //回调函数的返回值会替换数组中当前项的结果【原始数组不变,以新数组形式返回】
  return 'xxx'
})