强制类型转换

​这篇会详细介绍各种类型转换,值得一看

抽象值转换

先介绍ToString,ToNumber,ToBoolean这三种抽象值转换操作,以便于后面了解具体转换

ToString

处理非字符串到字符串的强制类型转换

对于基本类型值的字符串转换:null转换为"null",undefined转换为"undefined",true转换为"true",false转换为"false",数字转换为字符串也是遵循这种通用规则(对于极大和极小值,会转换为指数形式的字符串)

var a=1000*1000*1000*1000*1000*1000*100000;console.log(a.toString());//1e+23

那么对于普通的对象来说,除非这个对象定义了toString()方法,否则toString()(Object.prototype.toString.call())方法返回的就是对象内部的[[class]]属性(参见《类型与值》)

(将对象强制转换为string是通过ToPrimitive抽象操作完成的,下面会说)

对于数组,略显不同,就是会添加一个“,”号

var arr=[1,2,3];console.log(arr.toString());//1,2,3

 还有一个JSON字符串化(这个也是蛮重要的)

JSON.stringify()在将JSON对象字符串化时也用到了ToString

对于大多数简单值来说,JSON字符串化和ToString的效果基本相同,只是序列化的结果总是字符串

console.log(JSON.stringify(42));//"42"console.log(JSON.stringify("ab"));//""ab""(含有双引号的字符串)console.log(JSON.stringify(null));//"null"console.log(JSON.stringify(true));//"true"

所有安全的JSON值都可以使用JSON.stringify()方法进行字符串化,那么什么又是安全的JSON值呢?

安全的JSON值太多了,来看不安全的JSON值,记住这些就够了

undefined,function,symbol以及包含循环引用的对象都是不安全的值,无法使用JSON.stringify()方法

console.log(JSON.stringify(undefined));//undefinedconsole.log(JSON.stringify(function(){}));//undefinedconsole.log(JSON.stringify(Symbol()));//undefined

JSON.stringify()方法在对象中遇到undefined,function,symbol会自动忽略,在数组遇到则会返回null

console.log(JSON.stringify([1,undefined,function(){},Symbol(),7]));//[1,null,null,null,7]console.log(JSON.stringify({  a:2,  b(){},  c:undefined}));//{"a":2}

在包含循环引用的对象中使用JSON.stringify()方法会抛出错误

如果对象中包含了toJSON()方法,那么当使用JSON.stringify()方法时会调用对象中的toJSON()方法,然后用这个方法的返回值进行字符串化

var a={};var b={  q:"1",  w(){},  e:undefined,  r:a}a.t=b;console.log(JSON.stringify(b));//报错

但是,当对象中添加这个方法就不会报错了

b.toJSON=function(){  return { q:this.q }}console.log(JSON.stringify(b));//{"q":"1"}

再来仔细看看这个JSON.stringify()方法,实际上有三个参数可使用

JSON.stringify(value[, replacer [, space]])

第一个参数都知道就不说了,看第二个第三个

replacer:如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为null或者未提供,则对象所有的属性都会被序列化

var a={  b:23,  c:"qwer",  d:[1,23]}console.log(JSON.stringify(a,["b","c"]));//{"b":23,"c":"qwer"}console.log(JSON.stringify(a,function(key,value){  if(key !== "c") return value;}));//{"b":23,"d":[1,23]}

我觉得这个可选参数就类似于toJSON()方法,甚至比之还简单好用

 

space:指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;如果该参数为字符串(字符串的前十个字母),该字符串将被作为空格;如果该参数没有提供(或者为null)将没有空格

var a={  b:23,  c:"qwer",  d:[1,23]}console.log(JSON.stringify(a,null,3));//{   "b": 23,   "c": "qwer",   "d": [      1,      23   ]}

 

ToNumber

处理非数字到数字的强制类型转换

对于基本类型值转换:true转换为1,false转换为0,null转换为0,undefined转换为NaN,非纯数字字符串转换为NaN

console.log(Number(undefined));//NaNconsole.log(Number(true));//1console.log(Number(false));//0console.log(Number(null));//0console.log(Number("2"));//2console.log(Number("a"));//NaNconsole.log(Number("2e"));//NaNconsole.log(Number("undefined"));//NaN

对于对象而言,首先会被转换为基本类型值,然后判断是否为数字的基本类型,不是,就按照上面的规则进行转换

至于对象是如何转换为基本类型值的,抽象操作ToPrimitive首先会检查该对象是否有valueOf()方法(参见《原生函数》),有且返回一个基本类型值,就会使用该返回值进行强制类型转换。如果没有则会检查是否有toString()方法,有且有返回值,也是使用该返回值进行强制类型转换。如果还是两个都不返回基本类型值,则会抛出TypeError错误

解释一下为什么要使用这俩个方法,第一个方法是为了获取封装对象(参见《原生函数》)中的基本类型值,或者复杂对象自定义了该方法(不定义则返回NaN)且返回基本类型值,第二个方法是为了获取复杂对象返回的基本类型值(在复杂对象中也是要自定义,不定义则返回NaN)

这里还有一个特例,就是由Object.create(null)创建出来的对象,它没有valueOf()和toString()方法,所以无法进行强制类型转换(转换了是报错而不是返回NaN)。如果自定义了这俩个方法则可以强制类型转换

//valueOf()方法var num=new Number(2);var no_num={  a:2}var new_num={  a:4,  valueOf(){    return this.a;  }}console.log(Number(num));//2console.log(Number(no_num));//NaNconsole.log(Number(new_num));//4//toString()方法var str={  a:"21",  toString(){    return this.a;  }}var no_str={  a:"21"}console.log(Number(str));//21console.log(Number(no_str));//NaN//当两个方法同时存在时var obj={  a:"21",  b:"10",  valueOf(){    return this.b;  },  toString(){    return this.a;  }}console.log(Number(obj));//10//Object.create(null)var obj=Object.create(null);obj.a=2;obj.valueOf=function(){  return this.a}console.log(Number(obj));//2

ToBoolean

在JavaScript中的值可以分为两类:

  1. 可以被强制类型转换为false的值

  2. 其他值(被强制类型转换为true)

假值有这些:

◦ undefined

◦ null

◦ +0 -0 NaN

◦ false

◦ ""

这些假值转换为布尔值都为false,除此之外都转换为true,就连[]这个空对象也是true

还有一些长得像假值的对象,比如下面这些,都包装了假值,但是其实这些对象转换为布尔值都为true

var w=new Boolean(true);//truevar e=new Boolean(false);//truevar r=new Number(0);//truevar t=new String("");//truevar a=Boolean(w && e && r && t);console.log(a);//true
var a=[];var b={};var c=function(){}var d=Boolean(a && b && c );console.log(d);//true

只要记住那几个假值就行,除此之外都是真值

 

显示强制类型转换

什么是显示强制类型转换呢?就是那种一眼就能看穿的,就叫显示强制类中转换。对我来说,现在啥也看不穿,啥都是显示强制类型转换

 

先说字符串和数字之间的转换

对于这两个类型的转换,可以通过String()和Number()函数(参见《原生函数》)进行转换

var num=234;console.log(String(num));//"234"var str="212";console.log(Number(str));//212

这两个函数都是分别遵循前面所说的ToString规则和ToNumber规则

当然还有其他方式进行转换

var a=42;var s=a.toString();console.log(typeof s);//stringvar b="23";var c= +b;console.log(typeof c);//number

第一个肯定晓得,第二个呢就是利用 + 这个一元运算符对字符串显示强制类中转换,这个比较常见

还有~运算符也可以,它会对运算数执行ToNumber操作

var num="23";var num_2=~num;console.log(num_2);//-24console.log(typeof num_2);//numbervar no_num="23s";var no_num_2=~no_num;console.log(no_num_2);//-1console.log(typeof no_num_2);//number

可以结合字符串的indexOf方法一起使用

var a="hello js";function b(){  if(~a.indexOf("el")){    console.log("true")  }  if(!~a.indexOf("ho")){    console.log("false");  }}b();//true false

indexOf方法在未找到指定字符时返回-1,~-1为0,0为假值,!0为真值

还有~~运算符,也是可以将字符串转换为数字的。注意,只会截取整数部分

var a="234.123";var b=~~a;console.log(b);//234console.log(typeof b);//numbervar c="234.123a";var d=~~c;console.log(d);//0console.log(typeof d);//number

 

可能还会记得这两个函数,parseInt()和parseFloat()。这俩也可以将字符串转换为数字

但是,也是有区别的,Number()是强制转换,而parseInt()和parseFloat()是半解析字符串半强制。对于字符串中含有非数字字符,Number是会返回NaN的,而parseInt()则会解析字符串中的数字并返回

parseInt()和parseFloat()中传入的参数如果不是字符串,则会进行强制类型转换,遵循ToString规则

//第一个例子var str="23a";var str_2="a23";var num_1=Number(str);var num_2=parseInt(str);var num_3=parseInt(str_2);console.log(num_1,num_2);//NaN 23console.log(num_3);//NaN//第二个例子var num=parseInt(new String("23"));console.log(num);//23var obj={  num:24,  toString(){ return this.num }}console.log(parseInt(obj));//24var obj_2={  num:24}console.log(parseInt(obj_2));//NaN

解析整数使用parseInt(),解析浮点数使用parseFloat()。parseInt()和parseFloat()都是从左往右解析的下

 

 

现在看看布尔值的转换

同上,Boolean()也是显示强制类型转换,遵循ToBoolean规则

var a=[];var c={};var d="0";var e=null;var f;var g=0;var h='';console.log(Boolean(a));//trueconsole.log(Boolean(c));//trueconsole.log(Boolean(d));//trueconsole.log(Boolean(e));//falseconsole.log(Boolean(f));//falseconsole.log(Boolean(g));//falseconsole.log(Boolean(h));//false

当然不止这一种方法,还有!运算符,估计都没少用。!运算符可以将值强制类型转换为布尔值,但它还将值进行反转了。所以,!!才应该是正确的用法,反转再反转

console.log(!!a);//trueconsole.log(!!c);//trueconsole.log(!!d);//trueconsole.log(!!e);//falseconsole.log(!!f);//falseconsole.log(!!g);//falseconsole.log(!!h);//false

这样写是不是简单多了呢,学到了

 

隐式强制类型转换

先瞅瞅字符串和数字之间的转换

+运算符,既能用于数字加法,也能用于字符串拼接。估计都知道,但是如何判别呢?或者说这其中是如何操作的呢?

var a="20";var b="0";var c=20;var d=10;
console.log(a+c);//"2020"console.log(a+b);//"200"console.log(c+d);//30

这个例子确实是很简单,但下面这个例子呢?

var a=[1,2,3];var b=[4,5,6];var c=20;var d="20";console.log(a+b);//1,2,34,5,6console.log(a+c);//1,2,320console.log(a+d);//1,2,320

这些运行输出的结果都知道吗?为什么会这样呢?

简单说就是,在+运算操作中,一个操作数是字符串(或者能通过valueOf()方法以及toString()方法得到字符串),则执行字符串拼接,否则进行数字加法。这么说,才应该是比较合理的

那么就可以这么解释上面的例子了。数组通过toString()方法得到一个字符串,当进行+运算时,进行字符串拼接

知道这个+运算符的操作过程之后,应该也能理解下面这个语句的意思了

var a=20;var b=a+"";console.log(typeof b);//string

但是,这里有一个小的细节要注意,如果a是对象且定义的valueOf()和toString()方法的返回值不同,那么当你对这个对象进行隐式和显示强制类型转换时,会发现结果不一样

var obj={  valueOf(){return 1;},  toString(){return 2;}}console.log(obj+"");//1console.log(String(obj));//2

出现这种结果,是因为进行隐式时,先执行valueOf(),然后对返回值进行ToString抽象操作返回字符串。但是显示时,则直接调用toString()方法

 

再来看看布尔值到数字

var bol=false;var bol_2=true;console.log(Number(bol));//0console.log(Number(bol_2));//1
function onlyOne(a,b,c){  return !!((a && !b && !c) || (!a && b && !c) || (!a && !b && c))}var bol_1=true;var bol_2=false;console.log(onlyOne(bol_1,bol_2,bol_2));//true

这个例子就是当且仅当一个参数为true时,这个函数返回true。但是,如果这么写,就会显得比较麻烦了, 比如有很多个参数呢?还要一个一个写出来吗?这时候就可以用到布尔值转换为数字了

function onlyOne(){  let sum=0;  for (let i = 0; i < arguments.length; i++) {    if(arguments[i]){      sum +=arguments[i];    }  }  return sum==1;}var bol_1=true;var bol_2=false;console.log(onlyOne(bol_1,bol_2,bol_2,bol_2));//trueconsole.log(onlyOne(bol_1,bol_2,bol_2,bol_1));//false

这里sum +=arguments[i];就将布尔值转换为了数字,简单方便

这个例子可以用于满足某一条件来控制事件的发生

 

至于隐式强制类型转换为布尔值,只要记住那几个假值即可

在下面这些情况中会发生隐式强制类型转换

(1)if语句

(2)for(...,...,...)中第二个表达式

(3)while(){}和do...while(){}表达式

(4)?:三元表达式

(5)&& 和 || 逻辑运算符

前4个感觉都用的非常多,应该不用解释。至于最后一个,反正我不是很了解,这里来说说

对于&& 和 || 逻辑运算符,其实它们俩的返回值并不是布尔值,而是两个操作数中的一个

var a=23;var b=0;console.log(a||b);//23console.log(a&&b);//0

首先,会对&& 和 || 的第一个操作数执行条件判断,如果不是布尔值就先进行ToBoolean强制类型转换,然后再执行条件判断,最后返回其中的一个操作数,就类似于?:这个三元运算符

var a=23;var b=0;console.log(a?a:b);//23 ===》相当于 ||console.log(a?b:a);//0 ===》相当于 &&

每次我都会记混了&&和||的返回值,现在联想到三元运算符就好多了

那么下面这两个例子应该也能理解了

//第一个function foo(a,b){  a=a||"hello";  b=b||"javascript";  console.log(a+","+b);}foo();//hello,javascriptfoo("l","like you");//l,like you//第二个function foo(){  console.log(a);}var a=42;a && foo();//42

 

最后看看宽松相等和严格相等(== 和 ===),这里也有隐式强制类型转换

可能对于这两个的理解是这样的,“==检查值是否相等,===检查值和类型是否相等”,听起来确实不错。然而在这里却是,“==允许在相等比较中进行强制类型转换,===不允许”,至于为什么会这么理解,看完便知

 

先说字符串和数字之间的相等

var a=42;var b="42";console.log(a==b);//true

如果两个值类型不同,就对其中之一或两者都进行强制类型转换

那具体怎么转换呢?a从42转换为字符串还是b从“42”转换为数字呢?

在ES5规范中是这么说的

(1)如果Type(x)是数字,Type(y)是字符串,则返回 x==ToNumber(y)的结果

(2)如果Type(x)是字符串,Type(y)是数字,则返回 ToNumber(x)==y的结果

可以看出都是转换为数字来比较的,数字优先比较

 

还有就是其他类型和布尔类型之间的相等

看这样一个例子

var a="42";var b=true;console.log(a==b);//falsevar a="42";var b=false;console.log(a==b);//false

仔细看看,先是b为true,然后对比a为false。再是b为false,对比a还是false。是不是有点奇怪?a居然不是真值也不是假值?其实错了,不是这么看的。得先理解在==中其他类型和布尔类型的比较规则才行

在ES5规范中是这样说的

(1)如果Type(x)是布尔类型,则返回 ToNumber(x) == y 的结果

(2)如果Type(y)是布尔类型,则返回 x == ToNumber(y) 的结果

同样也是数字优先,先将布尔值转换为数字再进行比较

再回头看看这个例子,b先转换为数字0和1,然后a也转换为数字42,然后进行对比才得出false。并不是a单纯的和布尔值进行比较

 

怎么能忘了null和undefined呢?

这两个比较特殊,要谨记

在ES规范中规定

(1)如果x为null,y为undefined,则结果为true

(1)如果x为undefined,y为null,则结果为true

console.log(null == undefined);//true

在==中,undefined和null相等(它们也与其自身也相等),除此之外不和任何值相等(注意,这里是直接进行==比较,而不是进行显示强制类型转换后再进行比较的),看下面例子就明白了

//这是直接进行比较的var a;var b=null;console.log(a == a);//trueconsole.log(a == false);//falseconsole.log(a == 0);//falseconsole.log(a == '');//false      console.log(b == b);//trueconsole.log(b == false);//falseconsole.log(b == 0);//falseconsole.log(b == '');//false//这是进行显示强制类型转换后再比较的var a;var b=null;console.log(Number(a) == a);//falseconsole.log(Number(a) == false);//falseconsole.log(Number(a) == 0);//falseconsole.log(Number(a) == '');//falseconsole.log(Number(b) == b);//falseconsole.log(Number(b) == false);//trueconsole.log(Number(b) == 0);//trueconsole.log(Number(b) == '');//true

第一个例子中,a和b不会和假值相等,与其自身相等。看第二个例子,这里对a和b进行显示强制类型转换为数字,a是undefined,b是null,显示转换后,a是NaN,b是0,再进行==比较,b能和假值相等了

再注意看这两行

console.log(Number(b) == b);//falseconsole.log(Number(b) == 0);//true

说明在==中,null不会强制转换为其他类型(除非手动),除了undefined之外

说了这么多,总结一下就是,在==比较中,undefined和null能进行强制类型转换并相等。与其他类型值进行比较时,不会进行强制类型转换,所以不会相等

 

基本类型值之间的比较都说完了,现在该说对象与基本类型值之间进行比较了

关于对象(对象 | 函数 | 数组)和基本类型(布尔 | 字符串 | 数字 )之间的比较,在ES5中有如下规定

(1)如果Type(x)是基本类型值,Type(y)是对象,则返回 x == ToPrimitive(y)的结果

(2)如果Type(y)是基本类型值,Type(x)是对象,则返回ToPrimitive(x) == y的结果

如果忘了ToPrimitive是啥,可以往前翻翻,在ToNumber中有介绍,简单说就是查找valeuOf()方法和ToString()方法的返回值

var a=[123];var b="123";console.log(a==b);//truevar obj={  a:"24",  toString(){    return this.a;  }}var str="24";console.log(obj==str);//true

再看这个例子

var a="23";var b=Object(a);console.log(a==b);//true

这个例子中,先对b进行封装,然后在==中对b对象进行了拆封,拿到了b的基本类型值,也就是a,所以会相等

但是,这下面几个值就不一样了,也是封装再拆封,但是返回值不一样

var a=null;var b=Object(null);console.log(a==b);//falsevar c=undefined;var d=Object(undefined);console.log(c==d);//falsevar e=NaN;var f=Object(e);console.log(e==f);//false

因为undefined和null都没有封装对象,所以不能对它们俩进行封装,Object()返回的是一个常规对象(参见《原生函数》)

至于NaN,可以被封装,然后拆封之后仍然是NaN,但是NaN不等于自身

 

好了,到这里已经介绍完了大多数的隐式转换,现在看看一些比较特殊的例子

先回想一下前面所看的内容,然后再套用在下面的例子中,看看能否都解释出来

console.log("0" == null);//falseconsole.log("0" == undefined);//falseconsole.log("0" == false);//true console.log("0" == NaN);//falseconsole.log("0" == 0);//trueconsole.log("0" == "");//falseconsole.log("=============");console.log(false == null);//falseconsole.log(false == undefined);//falseconsole.log(false == NaN);//falseconsole.log(false == 0);//true console.log(false == "");//true ====>坑console.log(false == []);//true ====>坑console.log(false == {});//falseconsole.log("=============");console.log("" == null);//falseconsole.log("" == undefined);//falseconsole.log("" == NaN);//falseconsole.log("" == 0);//true ====>坑console.log("" == []);//true ====>坑console.log("" == {});//false console.log("=============");console.log(0 == null);//falseconsole.log(0 == undefined);//falseconsole.log(0 == NaN);//falseconsole.log(0 == []);//true ====>坑console.log(0 == {});//false

其实解释不出来也没多大关系,毕竟这些还是很少碰到的,尤其是标注了坑的那5个语句

console.log(false == "");//true ====>坑console.log(false == []);//true ====>坑console.log("" == []);//true ====>坑console.log("" == 0);//true ====>坑console.log(0 == []);//true ====>坑

这其中包含==false的语句应该避免使用,这样使用其实不是很规范,很容易产生误解,也不易理解

剩下的3种,这边建议记住哦

还有一个看起来可能更加难理解的例子

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

首先看到![] 应该想到ToBoolean抽象操作,所以此时[] == false,然后false被强制转换为数字0,而[]同样被强制转换为数字0,所以返回true。至于为什么不会等于自身,这两个数组不是同一个对象,不会相等

 

鉴于上面这些难以理解的语句,在使用==时,应该要注意下面两个原则

(1)如果等式两边有true或false,不要使用==

(2)如果等式两边有[],"",或0,也不要使用==

是不是完美规避了上面那些语句呢?不懂这个,我不用不就行了嘛。当然,你不用不代表别人不用,所以还是记一记比较好

 

到这里还没结束呢。还有最最最后一种方式也用到了隐式转换

var a=23;var b="24";console.log(a<b);//true

虽然很少会这么写,但是确实有

比较双方首先会调用ToPrimitive抽象操作,如果结果出现非字符串,就根据ToNumber规则将双方强制类型转换为数字进行比较

var a=[2];var b=["3"];console.log(a<b);//true

如果结果都是字符串,则按照字母顺序进行比较

var a=["20"];var b=["012"];console.log(a<b);//falsevar a=[2,3];var b=[0,1,2];console.log(a<b);//false

第一个例子是比较"20"和"012","0"在字母顺序上小于"2",所以a大于b

第二个例子是比较"2,3"和"0,1,2","0"在字母顺序上小于"2",所以a大于b

再来看看对象的比较,这里比较有趣,可能会和你想的不大相同

var a={b:2};var b={b:3};console.log(a<b);//falseconsole.log(a>b);//falseconsole.log(a==b);//falseconsole.log(a<=b);//trueconsole.log(a>=b);//true

看完输出结果是不是有点懵?来,慢慢分析

首先,ab是对象,执行ToPrimitive抽象操作,得到a的字符串[object Object],b同样得到[object Object],所以a>b和a<b都返回false。解释一下为什么a不会等于b,因为ab是两个不同的对象,不会相等。那么问题来了,为什么a<=b和a>=b会输出true呢?

可能你会把<=和>=理解为“小于或等于”和“大于或等于”。但是,注意,在JavaScript中并没有这种理解,而是把<=理解为“不大于”,>=理解为“不小于”。看起来相同,实则操作不同。“不大于”意味着会将a<b处理为!(a>b),“不小于”则是将a>b处理为!(a<b)。是不是蛮有趣的,和所理解的完全不一样。至于为什么,等我学习了底层知识再来讲讲

这么一解释,那上面输出的内容就很好理解了

 

建议多看两遍,哈哈。如果遇到类型转换的,可以参考这篇,我觉得写的还是蛮详细的。当然还有很多没顾及到的,不可能面面俱到,希望可以指出来

最后的最后,如果你看到这里了,希望能点个赞

要是觉得不错,还可以关注一下我的公众号,每周更新,篇篇干货

                                                                   

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值