JavaScript的类型转换你真的懂吗?

1. 值类型转换

什么是值类型转换?数组

值类型转换有两种,一种是显式类型转换:就是将值从一种类型转换为另外一种类型,一般称为类型转换。一种是隐式类型转换,被称为强制类型转换。安全

两者的区别是显而易见的,能够从代码中直接看出来那些类型转换是显式类型转换,那些类型转换是隐式类型转换bash

var a = 12; 
var b = a + "";   //隐式强制类型转换
var c = String(a);  //显式强制类型转换
复制代码

对变量b来讲,因为+运算符的其中一个操做数是字符串,因此是字符串拼接操做,结果数字12被拼接为了字符串"12",因此能够说b的强制类型转换是隐式的;函数

对于c来讲,是经过String()将a显式强制类型转换为了字符串,因此能够说变量c的强制类型转换是显式的。工具

有人说隐式强制类型转换的弊端大于显式强制类型转换,由于隐式强制类型转换有时候会让开发者摸不着头脑。性能

可是显式和隐式都是相对而言的,若是你明白字符串拼接,那么a + "" 就是显式的,若是你不了解String()能够作字符串强制类型转换,那么它对于你来讲就是隐式的。ui

有位名人说过,咱们编写的代码是给开发者看的,顺便运行在了程序中。this

2. 抽象值操做

在讨论显式和隐式强制类型转换以前,咱们须要先了解一下字符串、数字、布尔值之间类型转换的规则es5

ToStringspa

ToString负责处理费字符串到字符串的强制类型转换。

基本类型之的字符串化规则为: null转换为"null",undefined转换为"undefined",true转换为"true"。

能够查看ESMA规范中的9.8节:

对普通对象来讲,除非是自行定义,不然tostring()返回内部属性[[Class]]的值,如"[object Object]"

而数组比较例外,数组默认的toString()方法通过了从新定义,将全部单元字符串化之后再用",",连接起来,相似于join(",");

var arr = [1,2,3];
a.toString();  //"1,2,3"
arr.join(","); //"1,2,3"
复制代码

toString()能够被显式调用,或者在须要字符串化时自动调用。

JSON字符串化

工具函数JSON.stringify()在将JSON对象序列化为字符串时也用到了ToString()。对大多数简单值来讲,JSON字符串化和toString()的效果基本相同,不一样的是JSON.stringify()序列化的结果老是字符串。 例如

JSON.stingify(42);  //"42"
SON.stingify("42");  //""42"" (对字符串序列化的时候,会生成一个含有双引号的字符串)
SON.stingify(null);  //"null"
SON.stingify(false);  //"false"
复制代码

可是JSON.stringify()并非强制类型转换,谈到它是由于它涉及到了ToString();

ToNumber

有时候咱们须要将非数字当作数字使用。

基本类型的数字转换则为: true转换为了1,false转换为了0,undefined转换为了NaN,null转换为0。

能够查看ESMA规范中的9.3节:

对象(包括数组)会首先被转换为响应的基本类型之,若是返回的是非数字的基本类型值,则遵循上边的规则将其强制转换为数字。

为了将值转换为相应的基本类型值,会首先调用ToPrimitive检查该值是否有valueOf()方法,若是有就返回基本类型值,使用该值进行强制类型转换。若是没有的话就使用toSting()的返回值进行强制类型转换,若是两者都不存在,则产生TyoeError错误。可是要注意的一点是在从ES5开始,使用Object.create(null)建立对象的[[Prototype]]的属性为null,因此是没有valueOf()和toString()方法的,因此是没有办法进行强制类型转换的。 例如

var a = {
    valueOf: function() {
        retrun "12"
    }
};
var b = {
    toString: function() {
        return: "12";
    }
}
var c = [1,2];
c.toString = function() {
    return this.join("")
}

Number(a);  //12
Number(b); //12
Number(c); //12
Number(""); //0
Number([]); //0
Number(["abcd"]); //NaN
复制代码

ToBoolean

咱们知道在JavaScript中有两个经常使用的关键词true和false,表明了布尔类型的真值和假值。而在比较的时候咱们常误觉得true和false等同于1和0,虽然咱们能够将1强制转换为true,将0强制转换为false,反之亦可,可是在JavaSript中布尔值和数字是不同的。

参考ESMA规范

JavaScript中的值能够分为两类

(1)能够被强制类型转换为false的值

(2)能够被强制类型转换为true的值(其余) 由于能够被强制类型转换为true的值太多了,可是能够被转换为false的值缺只有一小部分,咱们只要记住哪些值是假值就能够区分出真值和假值了。

如下这些是假值

null
undefined
false
+0、-0和NaN
""
复制代码

从逻辑上理解,假值之外的都应该是真值。

3.显式强制类型转换

所谓的显式强制类型转换就是显而易见的类型转换。

var a = 12;
var b = String(a);
console.log(b)  //"12"

var c = "66.88"
var d = Number(c);
console.log(d);  //66.88
复制代码

以上是字符串和数字之间的转换,经过String()和Number()这两个内建函数实现。String()遵循ToString()规则,Number()遵循ToNumber()规则,将值转换为基本类型。

除此以外还有其余方法能够实现数字和字符串之间的显式转换:

var a = 12;
var b = a.toString();
console.log(b);  //"12"

var c = "66.88";
var d = +c;
console.log(d); //66.88
复制代码

a.toString()是显式的,可是其中包含了隐式转换,由于基本类型值12是没有toString()方法的,因此须要先对数字12进行封装,而后再调用toString()方法,而+c则是利用了+一元运算符能够将c强制转换为数字。在项目中使用+一元运算符将日期显式转换为数字时间戳也是很常见的一种使用方式。 例如:

var d = new Date("Sat Apr 27 2019 18:46:56 GMT+0800 (中国标准时间)")
+d;   //1556362016000
复制代码

固然将日期显式转换为时间戳还有其余方式:

var d = new Date().getTime(); //1556362150329
var d1 = Date.now();  //1556362186672
复制代码

固然这几种方法中是有一些细微区别的,+d是将时间戳的毫秒都改成了000的数值,不是很精确,而new Date().getTime()能够得到指定时间的时间戳,Date.now()能够得到当前时间的时间戳,能够根据须要快速的获取所须要的时间戳。

在不少时候咱们须要将"100px"转换为数字100,这个时候咱们使用转换就不行了,须要用到解析,虽然解析和转换都是将数字字符串转换为数字,可是两者是有区别的,转换只能转换字符串中只包含数字的字符,可是解析能够解析字符串中包含非数字字符,解析咱们能够用parseInt()方法,例如:

var a = "100";
var b = "100px";
Number(a);  //100
parseInt(a);  //100
Number(b);  //NaN
parseInt(b); //100
复制代码

parseInt()方法只能解析字符串值,传递其余参数是无用的,例如数字,布尔值(true),对象([1,2,3])等等;

说完了字符串和数字之间的转换,下面来看一下从非布尔值转换为布尔值。

var a = [];
var b = {};
var c = "0";
var d = 0;
var e = "";
var f = null;
var g = undefined;
var h;
Boolean(a);  //true
Boolean(b);  //true
Boolean(c);  //true
Boolean(d);  //false
Boolean(e);  //false
Boolean(f);  //false
Boolean(g);  //false
Boolean(h);  //false 
!!c //true
!!d //false
复制代码

看一下是否是和咱们前边说的是同样的,少数假值以外,其余的都是真值,虽然Boolean()是显式转换,可是在平常项目中并不经常使用, 更经常使用的是!符号,而在if() {}判断语句中,若是没有使用Boolean()和!符号,那么会自动进行隐式转换,这也是为何在项目中能够直接用if判断是否符合条件的缘由。可是建议使用Boolean()和!!进行显式转换,这样可让代码具备更好的可读性。

4.隐式类型转换

隐式类型转换指的是那些隐蔽的强制类型转换,隐式强制类型转换可使代码更为简洁,减小冗余代码。隐式类型转换没有明确的转换方式,只要你本身以为不是明显的显式强制类型转换,那么你均可以认为它是隐式强制类型转换。

字符串和数字二者之间的隐式类型强制转换,例如:

var a = '100';
var b = '0';

var c = 100;
var d = 0;
a + b;   //'1000'
c + d;    //100
a + c;  //'100100'
b + d;   //'00'
复制代码

再看对象(数组)之间的隐式类型转换:

var a = [1,2];
var b = [3,4];
a + b; //'1,23,4'
复制代码

从以上例子咱们能够看出来,若是某个操做数是字符串的话,将会使用+进行拼接操做,若是二者都是数字的话,则会使用数字运算进行加法运算。而若是其中一个操做数是对象(数组)的话,则会对该操做数调用ToPrimitive抽象操做。

进行ToPrimitive抽象操做的时候,会调用valueOf()方法,若是调用valueOf()方法没法获得简单基本类型值,就会转而调用toString()方法,反之亦然。

布尔值到数字的隐式强制类型转换

通常状况下,咱们都是将其余类型向布尔值转换,可是有时候使用布尔值向数字转换也会有使用场景。

例如:

var a = true;
var b = false;
a + b;  //1
a + a; //2
b + b; //0
复制代码

经过以上例子能够看出来,在转换的时候true会转换为数字1,false会转换为数字0。这样的话,在作一些多重条件判断的时候就会用到了。例如,咱们想有且仅有一个条件为true时达成条件。

var a = true;
var b = false;
function onlyOneTrue(a,b,c) {
    return !!((a && !b && !c) ||(!a && b && !c) );
}
onlyOneTrue(a, b, b) ; //true
onlyOneTrue(a, b, a);  false
复制代码

能够看到,在条件很少的时候,咱们还能够写出来,若是须要传入5个,6个,N个参数的话,就很难处理了,在这样的业务场景下就能够将布尔值转换为数字。

var a = true;
var b = false;
function onlyOneTrue() {
    var sum = 0;
    for ( var i = 0; i < arguments.length; i++ ) {
        sum += Number(!!arguments[i])
    }
    return sum === 1;
}
onlyOneTrue(a,b,b,b,b,a,a,,b)
复制代码

这样的状况下,不管咱们传入多少参数,或者须要知足几个ture,只须要修改sum的条件判断就均可以了。

其余类型值隐式强制转换为布尔值

其余类型值隐式强制转换为布尔值,这种业务场景咱们就见的很是多了,例如:

1.if () 条件语句判断表达式

2.for(.. ; ..; .. )语句中的条件判断表达式

3.while()和do while()循环中的条件语句判断表达式

4.? : 三元运算符条件判断表达式

5.逻辑与(&&)运算符和逻辑或(||)左边的操做数

宽松相等( == )和严格相等( === )

关于这两种比较相等在社区中争论已久,有的开发者认为使用宽松相等( == )就能够了,有的开发者认为必须使用严格相等( === )。其实大可没必要争论到底使用哪一种方法是对的,只要咱们理解了这两种相等的区别,就会恍然大悟。

二者区别: 宽松相等( == )容许在相等比较中进行强制类型转换,而严格相等( === )是不容许在相等比较中进行强制类型转换的。

虽然==比===作的事情更多,工做量大一些,在强制类型转换的时候须要多花费一点时间,可是仅仅是微秒(百万分之一秒)的差异而已,因此这一点时间并不能影响咱们代码的性能。而在其余方面二者之间并无什么不一样。因此咱们在写项目中能够放心的使用宽松相等( == ),而不是设置规范必须去使用严格相等( === )。

宽松不相等 != 就是==的相反方法,!==同理。

宽松相等

1.null 和 undefined两值之间的相等比较

var a = null;
var b;

a == b; //true
b == null; //true

a == false; //false
b == fasle; //false
a == 0; //fasle
b == 0; //fasle
a == ''; //fasle
b == ''; //fasle
复制代码

由以上例子能够看出来在宽松比较( == )的时候,null和undefined是一回事的,能够进行隐式强制类型转换,可是除此以外其余值都不和他们两个相等。

因此在处理一些判断的时候,能够经过这种方式将null和undefined做为等价的方式比较,例如:

if ( a == null ) {
    
}
或
if ( a == undefined ) {
    
}
等价于
if ( a == null || a == undefined ) {
    
}
复制代码

这样写的话既保证了安全性,又能够保证了代码的简洁性。

2.字符串和数字之间的比较

var a = 100;
var b = '100';
a === b; //false
a == b; //true
复制代码

由于===不进行强制类型转换,因此a !== b,即100和'100'不相等,而 == 进行了隐式类型转换,因此100 == '100'。

字符串和数字之间宽松相等的比较规则,能够根据EAMAScript规范11.9.3.4-5来解读一下,即:

若是两边比较的值:一方值为数字,一方值为字符串,则对字符转进行ToNumber()转换。

3.布尔类型和其余类型之间的相等比较

var a = '100';
var b = 100;
var c = true;
a == c; //false
b == c; //false
复制代码

这是由于根据ESMA规范,若是两边比较的值,一方值为true,一方值为其余类型,那么会对布尔值进行ToNumber()转换,先将布尔值转换为数字,再次进行比较。

4.对象和非对象之间的相等比较

var a = 100;
var b = [100];

a == b; //true
复制代码

这是由于根据ESMA规范,若是两边比较的值,一方是字符串或者数字,一方是对象,那么会使对象进行ToPrimitive()抽象操做,而后进行比较。例如上述例子,先将[42]经过ToPrimitive抽象操做返回了'100',变成了 '100' == 100,再根据字符串和数字之间比较的规则,将'100'转换成了数字100,最后二者相等。

5.极端状况

[] == ![]  //true
复制代码

咱们知道全部的对象都强制类型转换以后都为true,上述例子看起来是真值和假值的比较,结果应该为false,可是事实结果为true,这是为何呢?

咱们知道!会进行布尔值强制类型转换,因此![]就被转换为了fasle,变成了[] == false, 而当布尔值和其余类型进行比较的时候,会进行ToNumber(false), 因此![]就被转换为了0,变成了false == 0,那么在布尔值和数字比较的时候,会进行ToNumber()转换,因此最后[] == ![];

有不对的地方,欢迎指出,一块儿讨论。

参考:

1.你不知道的js中卷

2.ESMAScript5.1规范

相关文章
相关标签/搜索