关于强制类型转换是一个设计上的缺陷仍是有用的特性,这一争论从JavaScript 诞生之日起就开始了。在不少的 JavaScript 书籍中强制类型转换被说成是危险、晦涩和糟糕的设计。数组
然而在不少实际应用中都运用了大量的弱类型转换:app
alert({}); // 弹框提示:[object Object]
"0" + 1; // "01"
复制代码
因为这一特性,诞生了一系列有意思的题:函数
[] + {}; // "[object Object]"
{} + []; // 0
复制代码
if (a == 1 && a == 2) {
alert('hello world!'); // 他是可能的
}
复制代码
JavaScript的类型分为原始类型与引用类型。ui
咱们能够用 typeof 运算符来查看值的类型,它返回的是类型的字符串值。this
typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true
// ES6中新加入的类型
typeof Symbol() === "symbol"; // true
复制代码
你可能注意到 null 类型不在此列。它比较特殊, typeof 对它的处理有问题:编码
typeof null === "object"; // true
复制代码
这个 bug 由来已久,在 JavaScript 中已经存在了将近二十年,也许永远也不会修复,由于这牵涉到太多的 Web 系统,“修复”它会产生更多的bug,令许多系统没法正常工做。spa
再来看看数组。JavaScript 支持数组,那么它是否也是一个特殊类型?prototype
typeof [1,2,3] === "object"; // true
复制代码
不,数组也是对象。确切地说,它也是 object 的一个“子类型”设计
最后看一组code
typeof false; // "boolean"
typeof new Boolean(false); // "object"
typeof 1; // "number"
typeof new Number(1); // "object"
复制代码
将值从一种类型转换为另外一种类型一般称为类型转换。 类型转换又分为隐式
与显式
,如:
var a = 42;
var b = a + ""; // 隐式强制类型转换
var c = String( a ); // 显式强制类型转换
复制代码
ES5 规范第 9 节中定义了一些“抽象操做”(即“仅供内部使用的操做”)和转换规则。即:
这些操做都是由js内部实现,并不是实际的函数。接下来咱们进行解析其内部实现。
将 引用类型值 强制类型转换为 string、number、boolean 是经过 ToPrimitive 抽象操做来完成的,咱们在此略过,稍后会详细介绍。
抽象操做 ToString ,它负责处理非字符串到字符串的强制类型转换。
基本类型值的字符串化规则为: null 转换为 "null" , undefined 转换为 "undefined" , true 转换为 "true" 。数值遵循常规规则。
有时咱们须要将非数字值看成数字来使用,好比数学运算。为此 ES5 规范在 9.3 节定义了抽象操做 ToNumber 。
其中 true 转换为 1 , false 转换为 0 。 undefined 转换为 NaN , null 转换为 0 。
ToNumber操做字符串时,会判断该字符是否符合数字值格式。处理失败时返回 NaN。其处理规则与Number函数类似:
Number( "" ); // 0
Number( "a1" ); // NaN
复制代码
JavaScript 中有两个关键词 true 和 false ,分别表明布尔类型中的真和假。咱们常误觉得数值 1 和 0 分别等同于 true 和 false 。在有些语言中多是这样,但在 JavaScript 中布尔值和数字是不同的。
假值的布尔强制转换的结果均为 false 。
假值列表之外的值都是真值。即布尔强制转换后均为true。 因此空数组[]、空对象{}、字符串"null"、字符串"undefined" 等都为真值。
抽象操做 ToPrimitive ,它负责将引用类型值转化为基本类型值。其具体规则为: 一、检查该值是否有 valueOf() 方法 二、若是有而且返回基本类型值,就使用该值进行强制类型转换 三、若是没有就使用 toString() 的返回值(若是存在)来进行强制类型转换。 四、若是 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
var a = {
valueOf: function(){
return "42";
}
};
Number( a ); // a.valueOf() -> "42" -> 42
String( a ); // a.toString() -> "[object Object]"
var b = {
toString: function(){
return "42";
}
};
Number( b ); // b.valueOf() -> {}; b.toString() -> "42" -> 42
String( b ); // b.toString() -> "42"
var c = [4,2];
c.toString = function(){
return this.join( "" ); // "42"
};
Number( c ); // c.toString() -> "42" -> 42
Number( [4, 2] ); // [4,2].toString() -> "4,2" -> NaN
复制代码
a + "" (隐式)和前面的 String(a) (显式)之间有一个细微的差异须要注意:
var a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
};
a + ""; // a.valueOf() -> 42 -> "42"
String( a ); // a.toString() -> 4 -> "4"
复制代码
函数:
操做符:
隐式强制类型转换指的是那些隐蔽的强制类型转换,反作用也不是很明显。换句话说,你本身以为不够明显的强制类型转换均可以算做隐式强制类型转换。
主要分为:
这里主要讲算式与弱相等。
数字 + 字符串 number + string = ToString(number) + string
1 + '2'; //
复制代码
数字 + 布尔 number + boolean = number + ToNumber(boolean)
1 + true; // 2 + ToNumber()
复制代码
字符串 + 布尔 string + boolean = string + ToString( boolean )
'1' + true; // "1true"
复制代码
原始类型 -x÷ 原始类型 primitive -x÷ primitive = ToNumber(primitive) -x÷ ToNumber(primitive)
引用类型在参与计算时,会先通过ToPrimitive转换成原始类型,而后按照上述规则参与计算。
1 + {}; // "1[object Object]"
({toString(){return 1}}) + ({toString(){return 1}}); // 2
({toString(){return 1}}) + ({toString(){return '1'}}); // "11"
复制代码
抽象相等也称为宽松相等,它的规则正是隐式强制类型转换被诟病的缘由,很容易致使 bug,实际上他的规则很是简单。
首先,有几个很是规的状况须要注意。
== 在比较两个不一样类型的值时会发生隐式强制类型转换,会将其中之一或二者都转换为相同的类型后再进行比较。
这里用字符串和数字的例子来解释 == 中的强制类型转换:
var a = 42;
var b = "42";
a === b; // false
a == b; // true
复制代码
由于没有强制类型转换,因此 a === b 为 false , 42 和 "42" 不相等。
而 a == b 是宽松相等,即若是两个值的类型不一样,则对其中之一或二者都进行强制类型转换。
具体怎么转换?是 a 从 42 转换为字符串,仍是 b 从 "42" 转换为数字?
ES5 规范 11.9.3.4-5 这样定义: (1) 若是 Type(x) 是数字, Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。 (2) 若是 Type(x) 是字符串, Type(y) 是数字,则返回 ToNumber(x) == y 的结果。
其中ToNumber 为抽象操做的规则前面已经介绍过。
== 最容易出错的一个地方是 true 和 false 与其余类型之间的相等比较。 例如:
var a = "42";
var b = true;
a == b; // false
复制代码
咱们都知道 "42" 是一个真值(见本章前面部分),为何 == 的结果不是 true 呢?缘由既简单又复杂,让人很容易掉坑里,不少 JavaScript 开发人员对这个地方并未引发足够的重视。
规范 11.9.3.6-7 是这样说的: (1) 若是 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果 (2) 若是 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。
根据规则 "42" == true ,通过ToNumber(true) -> 1; "42" == 1,再通过ToNumber("42") -> 42; 42 == 1,因此输出false。
null 和 undefined 之间的 == 也涉及隐式强制类型转换。
ES5 规范 11.9.3.2-3 规定: (1) 若是 x 为 null , y 为 undefined ,则结果为 true 。 (2) 若是 x 为 undefined , y 为 null ,则结果为 true 。
在 == 中 null 和 undefined 相等(它们也与其自身相等),除此以外其余值都不存在这种状况。 也就是说除null和undefined外的全部类型值都不与他们抽象相等。
var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false
复制代码
关于引用类型(对象 / 函数 / 数组)和原始类型值(字符串 / 数字 / 布尔值)之间的相等比较。
ES5 规范 11.9.3.8-9 作以下规定: (1) 若是 Type(x) 是字符串或数字, Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果; (2) 若是 Type(x) 是对象, Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。
例如
var a = 42;
var b = [ 42 ];
a == b; // true
复制代码
[ 42 ] == 42, 通过ToPrimitive([ 42 ]) -> [ 42 ].toString() -> "42"; "42" == 42, 再通过ToNumber("42") -> 42; 42 == 42; 返回true
Number.prototype.valueOf = function() {
return 3;
};
new Number( 2 ) == 3; // true
复制代码
而 2 == 3 不会有这种问题,由于 2 和 3 都是数字基本类型值,不会调用 Number.prototype.valueOf() 方法。而 Number(2) 涉及 ToPrimitive 强制类型 转换,所以会调用 valueOf() 。
还有文章开头的题:
if (a == 1 && a == 2) {
alert('hello world!');
}
复制代码
你也许以为这不可能,由于 a 不会同时等于 1 和 2 。但“同时”一词并不许确,由于 a == 1 在 a == 2 以前执行。 若是让 a.valueOf() 每次调用都产生反作用,好比第一次返回 1 ,第二次返回 2 ,就会出现这样的状况。
var i = 1;
Number.prototype.valueOf = function() {
return i++;
};
var a = new Number( 42 );
if (a == 1 && a == 2) {
console.log( "Yep, this happened." );
alert('hello world!');
}
复制代码
再次强调,千万不要这样,也不要所以而抱怨强制类型转换。对一种机制的滥用并不能成为诟病它的借口。咱们应该正确合理地运用强制类型转换,避免这些极端的状况。
本文介绍了 JavaScript 的数据类型之间的转换:包括显式和隐式。
在处理强制类型转换的时候要十分当心,尤为是隐式强制类型转换。在编码的时候,要知其然,还要知其因此然,并努力让代码清晰易读。
本文大部分例子与描述来自 《你不知道的JavaScript》。