JavaScript基础之数据类型转换规则总结

232 阅读12分钟

个人笔记

本文详细说一下各种数据类型之间相互转化规则

需求与遇到的情景基本上都是把其他类型转换成Number,BooleanString这几种数据类型

其它数据类型转换为Boolean

统一遵循以下规则: 只有0NaNnullundefined''(空字符串)这五个值会转换为false,其余都转换为true

转换方法:

  • Boolean([value])
  • !![value]
  • ![value] 转换为布尔类型取反
  • 条件判断 例如:if(1){},括号中的条件会转换 注意:
  • A||BA&&B不会对数据进行转换,他们的返回值是A或B中的一个

例子:

console.log(!![])//true
console.log(!!-1)//true

把其它数据类型转换为String

分为显示转换和隐式转换。

显示转换使用String方法进行转换。隐式转换是使用+号运算符,+号两边有一边出现字符串,就会把另一边也转换为字符串并且拼接。

以上两种方法在转换时都遵循以下规则(即转换为字符串之后的结果):

  • 如果是原始值转换结果则是相当于直接用引号将原始类型包起来(注意: Bigint会去除n)
  • 如果是对象转换结果则比较特殊,下面详细说

分开讨论

原始值转换结果

规则:原始值转换结果是直接用引号包起来(注意: Bigint会去除n)

显式转换,即使用String方法时:

String(1)//"1"
String(false)//"false"
String(null)//"null"
String(undefined)//"undefined"
String(Symbol(1))//"Symbol(1)"
String(12312312312323534523423423n)//"12312312312323534523423423"

隐式转换,即使用+拼接

1+''//"1"
false+''//"false"
null+''//"null"
undefined+''//"undefined"
12312312312323534523423423n+''//"12312312312323534523423423"

注意:

  1. Bigint:
    • Bigint会去除n.
    • 如果不加n,一个普通的大数字转换成字符串结果会出错
    image.png image.png
  2. Symbol(1)+'' Symbol(1)+1都会报错,即Symbol类型无法进行隐式转换

对象转换

规则:不是所有对象都是字符串拼接

  • 先去调取对象的 [Symbol.toPrimitive] 属性值,如果没有这个属性
  • 再去调取对象的valueOf() 获取原始值,如果不是原始值
  • 再去调用对象的 toString() 转换为字符串「如果是想转换为数字,则还会调用Number处理」

Symbol.toPrimitive MDN

Object.prototype.valueOf() MDN

Object.prototype.toString() MDN

console.log(10 + [10, 20]);  //->"1010,20"

为什么会出现以上这样的情况?原因是Symbol.toPrimitiveundefinedvalueOf返回的也不是原始值,所以最后返回的是toString(),是一个字符串,所以在遇到+号就变成了字符串拼接

如果我们重写Symbol.toPrimitive

const x = [10,20]
x[Symbol.toPrimitive] = function (){ return 'reWrite'}
String(x)//"reWrite"
x+''//"reWrite"
10+x //"10reWrite"

而以下情况:new Number(10).valueOf()有原始值的(10),所以最后是加法


const ten = new Number(10)
ten[Symbol.toPrimitive]//undefined
ten.valueOf()//10
10+ten//20

console.log(10 + new Number(10)); // ->20   new Number(10).valueOf()有原始值的

下面Date比较特殊

String(new Date())//"Sun Feb 28 2021 15:49:52 GMT+0800 (中国标准时间)"
new Date().toString()//"Sun Feb 28 2021 15:50:10 GMT+0800 (中国标准时间)"
new Date()+''//"Sun Feb 28 2021 15:47:47 GMT+0800 (中国标准时间)"
''+new Date()//"Sun Feb 28 2021 15:47:59 GMT+0800 (中国标准时间)"

+new Date()//1614498482848 特殊
const time = new Date()
time[Symbol.toPrimitive]('number')//1614506495466
time[Symbol.toPrimitive]('string')//"Sun Feb 28 2021 18:01:35 GMT+0800 (中国标准时间)"
time[Symbol.toPrimitive]('default')//"Sun Feb 28 2021 18:01:35 GMT+0800 (中国标准时间)"

重写Symbol.toPrimitive

let obj = {x: 10};
console.log(10 + obj); // ->"10[object Object]"   
let obj = {
    x: 10,// obj[Symbol.toPrimitive] && valueOf && toString
    [Symbol.toPrimitive](hint) {
          console.log(hint); // 有这几个值,=>”default“、”string“、”number“,是浏览器为了识别帮我们转换成什么的一个参数,如果不知道就是default(这里就是),如果返回的明确知道是string,就是"string",因为加号不一定是什么,有可能是字符串拼接,有可能是加法运算,所以是default
        return this.x;
    }
};
console.log(10 + obj);  ->20   

注意:toString()方法也可以主动调用

Object.prototype.toString()//"[object Object]"
String({a:1})//"[object Object]"
''+{a:1}//"[object Object]"
'xxx'+{a:1}//"xxx[object Object]"

({a:1})+''//"[object Object]"//如果用括号括起来就跟普通String一样
String({})//"[object Object]"
({}).toString()//"[object Object]"
''+{}//"[object Object]"
({})+''//"[object Object]" 如果用括号括起来就跟普通String一样

这里是因为数组的toString方法被重写了

String([1,2])//"1,2"
[1,2]+''//"1,2",这里不需要括号括起来,因为本身就不会被识别成代码块{},本身就是一个变量
''+[1,2]//"1,2"
[1,2].toString()//"1,2"
String(function x(){})//"function x(){}"
''+function x(){}//"function x(){}"
function x(){}+''//0  特殊
(function x(){})+'' //"function x(){}"

特殊情况:左侧的{}当做代码块,不参与运算

把左侧的{}当做代码块,不参与运算,运算的只有 +n ,相当于将其转换为数字,下面也会说明

{a:1}+''//0   (特殊)
{a:1}+'123xyz'//NaN (特殊)
{a:1}+'123'//123 (特殊)
{}+''//0  (特殊)
function x(){} +''//0 特殊
function x(){ return 1} +'' //0  特殊
function x(){ } +'123'//123  特殊

总结

总结:把其他类型转换为字符串,结果一般都相当于""直接包起来,只有{}普通对象调用的是其原型上的toString()方法(Object.prototype.toString()),不过这个方法如果不重写,返回的一般是其数据类型,如"[object Object]",像数组,其toString方法就被重写了

额外:基于alert/confirm/prompt/document.write...这些方式输出内容,都是把内容先转换为字符串,然后再输出的

附加:+号转换为字符串的一些特殊情况

+号转换和对象转换为字符串有一些特殊情况,下面来说明

+号转换特殊情况 1:+只有一边

  let n = '10';
  console.log(+n); // 10 ->转换为数字
  console.log(++n);  //11 ->转换为数字然后累加1
  console.log(n++); // 11 ->转换为数字然后累加1

i++i=i+1 以及 i+=1 三个是否一样?

  • i=i+1 & i+=1 是一样的
  • i++一定返回的是数字 但是i+=1就不一定了,有可能是字符串拼接
let i = 10;
console.log(5 + (++i));  //先i累加1,累加后的结果运算  输出 16 ,i->11
console.log(5 + (i++)); // 先运算 再累加1  输出15, i->11

+号转换特殊情况 2:+有一边出现对象

  let n = 10;
    {}+n// -> 10  把左侧的{}当做代码块,不参与运算,运算的只有 +n
    n+{} //-> '10[object Object]' 字符串拼接

其他类型转化为Number

分为Number()型和parser型转换

  • Number([val])
  • parseInt([val])/parseFloat([val])

Number()型转换

可以直接手动调用Number([val])进行转换,也可用于隐式转换。

隐式转换有以下几种情况会调用Number()

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

Number()的转换机制(结果)如下

  • 字符串->数字 : ''(空字符串)变为0,字符串中只要出现非有效数字字符结果就是NaN,有正常数字就转化为正常的数字
  • 布尔->数字 : true变为1 false变为0
  • null->数字: 0
  • undefined->数字:NaN
  • Symbol->数字:报错
  • BigInt->数字:正常转换
  • 对象遵循 Symbol.toPrimitive=>valueOf=>toString=>Number 的规则,会先经过前三步转化为字符串,再调用Number方法

以下是原始值举例:

Number('')//0
+''//0
-""//-0
*''// Uncaught SyntaxError: Unexpected token '*'
1*''//0
1/''//Infinity

Number('1xxx')//NaN

+true//1
-true//-1
+false//0
-false//-0
Number(true)//1

Number(null)//0
Number(undefined)//NaN
+null//0
+undefined//NaN

Symbol(1)+1//Uncaught TypeError: Cannot convert a Symbol value to a number

Number(182391823798473394859034n)//1.8239182379847338e+23
Number(BigInt(10))//10
182391823798473394859034n+1//Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

182391823798473394859034n+1n//182391823798473394859035n

对象变为数字,应该先valueOf(),没有原始值,再toString()转化为字符串,在调用Number()方法

如下所示

const a  = {x:1}
console.log(a+1)//"[object Object]1"

valueOf(),没有原始值,再toString()转化为字符串,转化为字符串就直接拼接了

对象转换实验:

const a  = {x:1}
console.log(a[Symbol.toPrimitive])//undefined
console.log(a.valueOf())//{x: 1} 返回对象本身,不是原始值,
console.log(a.toString())//"[object Object]"
console.log(a+1)//"[object Object]1"//对象变为数字,应该先`valueOf()`,没有原始值,再`toString()`转化为字符串,在调用`Number()`方法
console.log(Number(a))//NaN
console.log(String(a))//"[object Object]"
const a  = new Number(10)
console.log(a[Symbol.toPrimitive])//undefined
console.log(a.valueOf())//10 返回数字类型原始值,
console.log(a.toString())//"10" 返回原始值的字符串
console.log(a+1)//11 返回数字类型
console.log(Number(a))//10
console.log(String(a))//"10"

总结 : js中,加号两边出现字符串,则变为字符串拼接(特殊性)。如果出现对象也变为字符串拼接(因为原本应该吧对象转换为数字,但是对象转数字需要先转换为字符串,遇到+号则拼接为字符串拼接 例如 1+[]//"1"

parser型转换

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

parseInt机制:先转化为字符串,从字符串左侧第一个字符开始,查找有效数字字符,遇到非有效数字字符就停止查找,不论后面是否还有数字字符,都不再找了,把找到的有效数字字符转换为数字,如果一个都没找到,结果就是NaN.parseFloat会多识别一个小数点

parseInt('')//NaN
Number('')//0
isNaN('') //false ,相当于isNaN(0)
parseInt(null) //NaN
Number(null)// 0
isNaN(null)//false相当于isNaN(0)
parseInt('12px')//12
Number('12px')//NaN
isNaN('12px')//true

JS中验证两个值是否相等

JS中验证两个值是否相等

==:相等

如果两边类型一样:那就直接比较,注意特殊的几个:

  • {}=={}=>false 对象比较的是堆内存的地址
  • []==[]=>false
  • NaN==NaN=>false

如果两边的类型不一样,首先会隐式转化为相同的类型,然后再做比较,规则如下(注意,只有==左右不一样才会进行转换,===类型不一样直接就是false

  • 对象==字符串 要把对象转换为字符串

  • null==undefined ->true「三个等于号是不相等的」,但是null/undefined和其它任何值都不会相等

  • NaN==NaN ->false

  • Symbol()==Symbol() ->false

  • 剩余的情况「例如:对象==数字字符串==布尔...」都是要转换为数字,再进行比较的

    例子:

[]== false//true

对象==布尔,都转化为数字(隐式转换),[]转化为数字,先基于valueOf()获取原始值,没有没有原始值,再调用toString()=>'',再转化为数字0,false转化为数字,也是0

===:绝对相等

「要求两边的类型和值都要相等 例如:switch case」

Object.is([val],[val])

Object.is() MDN

面试题

parseFloat('1.6px') + parseInt('1.2px') + typeof parseInt(null) //"2.6number"
//1.6+ 1+ 'number'
isNaN(Number(!!Number(parseInt('0.8'))))//false

过程:
    parseInt('0.8')//0
!!Number(0)//false
Number(false)//0
isNaN(0)//false
10+false+undefined+[]+'Tencent'+null+true+{}//"NaNTencentnulltrue[object Object]"
10+false //10
10+undefined// NaN    10+NaN=>NaN
NaN+[] //'NaN'  因为[]要先转化成字符串'',然后拼接为'NaN' 
'NaN' +'Tencent'+null+true+{} //"NaNTencentnulltrue[object Object]"  全部都是字符串拼接

题2

如何实现打印出ok

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

// 解决思路2:我们可以劫持对象的成员访问
//   + 全局下声明的变量是window的一个属性
//   + Object.defineProperty数据劫持的办法
let i = 0;
Object.defineProperty(window, 'a', {
    get() {
        return ++i;
    }
});
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
} 

parseInt困难面试题深度解析

let arr = [10.18, 0, 10, 25, 23];
arr = arr.map(parseInt);
console.log(arr);
arr = arr.map((item, index) => {
    // 循环遍历数组中的某一项就会触发回调函数
    // 每一次还会传递当前项和当前项的索引
}); 

parseInt([value],[radix])

  • [radix]这个值代表进制,不写或者写0默认都按照10处理,即十进制.(特殊情况:如果[value]是以0X开头,则默认值不是十进制而是十六进制)

  • 进制有一个取值的范围:2~36之间,如果不在这之间,整个程序运行的结果一定是NaN

  • [value]看做[radix]进制,最后把[radix]进制转化为十进制

  • 从字符串左侧第一个字符开始查找,找到符合[radix]进制的值(遇到一个不合法的,则停止查找),把找到的值变为数字,再按照把[radix]转换成为十进制的规则处理

parseInt('10.18',0)
//	'10' -> 10

parseInt('0',1) //  =>NaN

parseInt('10',2)
//	10  把它看做2进制,最后转换为10进制
//	1*2^1 + 0*2^0 => 2

parseInt('25',3)
//	2  当做3进制转换为10进制
//	2*3^0 => 2

parseInt('23',4)
//   23 当做4进制转换为10进制
//2*4^1 + 3*4^0  => 11

把一个值转换为十进制

位权值:每一位的权重,个位是0,十位是1...

147(八进制) => 十进制

1*8^2 + 4*8^1 + 7*8^0

12.23(四进制)=> 十进制

1*4^1 + 2*4^0 + 2*4^-1 + 3*4^-2

参考:

07 类型与类型转换