类型转换??
对于JavaScript的类型转换,一直是为人所诟病的一点,因为它实在太奇怪了,会令人感到十分难受,虽说是拥有“灵活”的动态转换能力,但是会让人感到十分的混乱,而且别人很喜欢用这种类似“脑筋急转弯”的东西来考人。下面给个图你感受下:
是不是很难顶??想摸清楚是什么情况。所以我先说一下最常用的基本类型 (primitive)之间的转换。
基本类型转换 primitive to primitive
在静态语言中,类型转换发生在编译期间 (complie time) ,而像JS这种动态语言则发生在运行时(running time),这都是一些抽象的概念。
实际上,使用基本类型作为操作数时,类型转换最后都是 String
、Boolean
、Number
这三种类型。而且,转换还分为显式
和隐式
的强制转换。
隐式强制转换
一个典型例子:
let num = 3.14;
let toStr = num + ''; // => "3.14"
对于上面的通过 + ''
将num转换为字符串,这个行为就是隐式的。
隐式强制转换发生的一些场景:
(1) if (..)语句中的条件判断表达式。
(2) for( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。
(3) while (..) 和 do..while(..) 循环中的条件判断表达式。
(4) ? :中的条件判断表达式。
(5) 逻辑运算符 || (逻辑或)和 && (逻辑与)左边的操作数(作为条件判断表达式)。
逻辑表达式左边操作数的转换规则
&&
如果第一个操作数是对象,则返回第二个操作数。
如果第二个操作数是对象,则只有第一个操作数求值为 true 才会返回该对象。
如果两个操作数都是对象,则返回第二个操作数。
如果有一个操作数是 null,则返回 null。
如果有一个操作数是 NaN,则返回 NaN。
如果有一个操作数是 undefined,则返回 undefined。
||
如果第一个操作数是对象,则返回第一个操作数。
如果第一个操作数求值为 false,则返回第二个操作数。
如果两个操作数都是对象,则返回第一个操作数。
如果两个操作数都是 null,则返回 null。
如果两个操作数都是 NaN,则返回 NaN。
如果两个操作数都是 undefined,则返回 undefined。
直接用表格来表示一些转换规则:
Value | to String | to Number | to Boolean |
---|---|---|---|
undefined | "undefined" | NaN | flase |
null | "null" | 0 | false |
true | "true" | 1 | |
false | "false" | 0 | |
""(空字符串) | 0 | false | |
"3.14" | 3.14 | true | |
"123word" | NaN | true | |
0(+0) | "0" | false | |
-0 | "0" | false | |
-1(不为0) | "-1" | true | |
NaN | "NaN" | false |
显式强制转换
使用内建构造函数封装
// String String(123); // => "123" String(3.14); // => "3.14" String(NaN); // => "NaN" ... // Boolean Boolean(1); // => true Boolean(-0); // => false Boolean(undefined); // => false ... // Number Number(null); // => 0 Number(undefined);// => NaN Number("not a Number"); // => NaN Number(true); // => 1
对于 to Number ,还可以使用
Number
内置静态函数parseInt()
和parseFloat()
,这两个函数期待一个 String 类型的参数。Number.parseInt("3.14"); // => 3 Number.parseFloat("3.14"); // => 3.14 parseInt(NaN); // => NaN parseFloat(NaN); // => NaN parseInt(true); // => NaN parseInt("123word"); // => 123 parseInt("s1341"); // => NaN
使用运算符
对于
Boolean
使用两次取反操作符!!
可强制转换为布尔值。!!NaN; // => false !!0; // => false !!'0' // => true !!null // => flase !!'' // => false !!null // => false !!undefined // => false
对于
Number
则使用四则运算+ - * /
,一般使用+
或-
多。+NaN // => NaN +'' // => 0 +undefined // => NaN +null // => 0 +true // => 1 +false // => 0 +"3.14" // => 3.14 +"s123" // => NaN +'123s' // => NaN
当
+
为二元运算符
时比较特殊:当有一个操作数为
String
类型时,此时作为字符串连接符。当有一个操作数为
Number
类型,另一个为其他基本类型(除String),此时为数学加法,不为Number类型被转换为Number类型。当有一个操作数为
Number
类型(或者两个都是引用类型),另一个为对象(引用类型),双方都转为字符串
。
123 + "str" => // "123str" 2 + false // => 2+0=2 1 + true // => 1+1=2 1 + NaN // => NaN 1 + null // => 1+0=1 1 + undefined // => 1+NaN=NaN let obj1 = {}; let ojb2 = {}; obj1 + obj2 // => '[object Object][object Object]' 1 + [] // => '1' 1 + {} // => '1[object Object]' 1 + function(){} // => '1function(){}'
对象转换为基本类型
Boolean
由上面知道除了''
、undefined
、null
、0
、NaN
为假值,其他所有值都是真值,所以对象的布尔值都会true
。
对象转基本类型有三种变体(variant),发生在各种情况下。这些情况被称为hint
(ECMAScript规范中这样定义:当一个对象被用在需要原始值的上下文中时,例如,在 alert
或数学运算中,对象会被转换为原始值)。
Hint
"string"
对象到字符串的转换,当我们对期望一个字符串
的对象执行操作,如作为alert
的参数:
// 输出
alert(obj); // 此时hint的值为"string"
// 将对象作为属性键
anotherObj[obj] = 123;
"number"
对象到数字的转换,例如当我们进行数学运算
时:
// 显式转换
let num = Number(obj);
// 数学运算(除了二元加法)
let n = +obj; // 一元加法
let delta = date1 - date2;
// 小于/大于的比较
let greater = user1 > user2;
"default"
二元加法+
,不严格相等 ==
,<
和>
操作符。
// 二元加法使用默认 hint
let total = obj1 + obj2;
// obj == number 使用默认 hint
if (user == 1) { ... };
为了实现转换,JavaScript会遵循以下规则(按1-3
顺序):
调用
obj[Symbol.toPrimitive](hint)
—— 带有 symbol 键Symbol.toPrimitive
(系统 symbol)的方法,如果这个方法存在
的话否则,如果 hint 是
"string"
—— 尝试obj.toString()
和obj.valueOf()
,无论哪个存在。否则,如果 hint 是
"number"
或"default"
—— 尝试obj.valueOf()
和obj.toString()
,无论哪个存在。
Symbol.toPrimitive
这是个有趣的特性,来点代码~ 顺便理解下hint:
let obj = {};
obj[Symbol.toPrimitive] = function(hint){
console.log(`hint: ${hint}`);
}
+obj; // => hint: number
alert(obj) // => hint: string
if(obj == 3.14){} // => hint: default
1 + obj; // => hint: default
Symbol.toPrimitive
必须返回一个原始值,否则就按上面不存在对待。
由上面可知Symbol.toPrimitive
的优先级较高(后面的toString()和valueOf()也可),我们可以用它来实现一些特别的操作!
如:让 a==1 && a==2 && a==3
返回true
?
let a = {value: 0};
a[Symbol.toPrimitive] = function(hint) { // 此时 hint为default
return this.value++;
}
a==1 && a==2 && a==3; // => true
toString()
和valueof()
如果没有 Symbol.toPrimitive
(这也是目前默认的),那么 JavaScript 将尝试寻找 toString
和 valueOf
方法:
对于 “string” hint:
toString
,如果它不存在,则valueOf
(因此,对于字符串转换,优先toString
)。对于其他 hint:
valueOf
,如果它不存在,则toString
(因此,对于数学运算,优先valueOf
)。
这些方法必须
返回一个原始值。如果 toString
或 valueOf
返回了一个对象,那么返回值会被忽略(和这里没有方法的时候相同)。
默认情况下,普通对象具有 toString
和 valueOf
方法:
toString
方法返回一个字符串"[object Object]"
。valueOf
方法返回对象自身。
一般地,toString()
作为“全能”的方法来处理转换。
看代码:
let obj = {};
if(obj.hasOwnProperty(Symbol.toPrimitive)){
alert(obj); // => "[object Object]"
}
obj === obj.valueOf() // => true
讨论一些让人觉得怪(weird)的。
+[]; // => 此时hint明显为number, [].valueOf()返回自身(对象),直接当不存在,因此调用toString() => '' => 0
// 原始类型转换 '' => 0
alert([]); // => hint: string => [].toString() => ''
+{}; // => hint: number => {}.valueOf() => {} => {}.toString() => '[object Object]' => NaN
alert({}); // => hint: string => toString() => '[object Object]'
hint
为"default"
,更是怪异:
[] == {}; // => '' == '[object Object]' 明显为false
[] + {}; // => '' + '[object Object]' => '[object Object]'
{} + []; // => 0 ??? 这是为什么?
// 因为{}在这里是代码块(什么也不执行), 而 + 则作为显示强制转换,hint为number
有了这些知识后,我们可以将开头的打印 'nb'解出来了:
([][[]] + [])[+!![]] + ([] + {})[!+[] + !![]]; // => 'nb'
[][[]] //=> 这是一个数组访问表达式,hint为number => [].toString() => '' => 0,而[][0]明显为undefined
[][[]] + [] // => undefined + [] hint为default => "undefined" + '' => "undefined"
[+!![]] // => +true => 1 即 "undefined"[1] === 'n'
[] + {} // => '[object Object]'
!+[] + !![] // => !0 + true => true + true => 2
说实话我是挺烦这种脑筋急转弯的。。就像耍小聪明!在最后说一下不严格相等操作符 ==
和严格相等操作符 ===
:
==
等于运算符(==
)检查其两个操作数是否相等,并返回Boolean
结果。与严格相等运算符(===
)不同,它会尝试强制类型转换并且比较不同类型的操作数。
相等运算符(==
和!=
)使用抽象相等比较算法比较两个操作数。可以大致概括如下:
如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回
true
。如果一个操作数是
null
,另一个操作数是undefined
,则返回true
。如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:
当数字与字符串进行比较时,会尝试将字符串转换为数字值。
如果操作数之一是
Boolean
,则将布尔操作数转换为1或0。如果是
true
,则转换为1
。如果是
false
,则转换为0
。
如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的
valueOf()
和toString()
方法将对象转换为原始值。
如果操作数具有相同的类型,则将它们进行如下比较:
String
:true
仅当两个操作数具有相同顺序的相同字符时才返回。Number
:true
仅当两个操作数具有相同的值时才返回。+0
并被-0
视为相同的值。如果任一操作数为NaN
,则返回false
。Boolean
:true
仅当操作数为两个true
或两个false
时才返回true
。
列举一些Weird的例子:
NaN != NaN; // => true
NaN == NaN; // => false
null == undefined; // true
1 == '1'; // => true
true == 1; // => true
String('string') == 'string'; // => true
===
全等运算符(===
和 !==
)使用全等比较算法来比较两个操作数。
如果操作数的类型不同,则返回
false
。如果两个操作数都是对象,只有当它们指向同一个对象时才返回
true
。如果两个操作数都为
null
,或者两个操作数都为undefined
,返回true
。如果两个操作数有任意一个为
NaN
,返回false
。否则,比较两个操作数的值:
数字类型必须拥有相同的数值。
+0
和-0
会被认为是相同的值。字符串类型必须拥有相同顺序的相同字符。
布尔运算符必须同时为
true
或同时为false
。
代码就不带了,大同小异。
Reference参考:
JavaScript高级程序设计