隐含地:Strings <--> Numbers
Example:
结果分别为"420"(string)
与42(number)
,会导致这样的差异,一般的认知会认为,只要+
运算子的其中任一边运算元为string,那就会进行string的串接。这部分是对的,但实际运作情况可能会比我们所想的还要複杂。
Example:
以上的2个运算元是array,不是string,但运算结果却是一个string,我们来分析这种情况是怎么产生的。
依据ES5的规格,若+
运算子的其中一个运算元是object的话,首先会想办法把object转成基型值,而array是属于object,但无法使用valueOf( )
方法产生基型值,所以它会改用toString( )
方法。因此那2个array分别转成"1,2"
与"3,4"
,串接就变成"1,23,4"
。
所以结论是,若+
运算子的其中一个运算元是string(或是能转型为string)的话,那就会是进行串接动作,不然的话,就会是数值相加。
根据以上的论述,我们可以把number跟空字串(" ")相加,就能把number转型成string:
+
运算子的数值加法是具有可交换性的,2+3
与3+2
的结果是一样的。但作为string串接,就不是了,"a"+"b"
跟"b"+"a"
,结果会是不一样的string。但以上面的例子来说是具有可交换性的,a + ""
与"" + a
都是让a
变成string。
使用+
运算子来达到转型的目的是很常见的手法,但有个细节需要特别注意。
Example:a+""
会直接呼叫valueOf( )
方法,但String( )
会呼叫toString( )
方法,就会造成不同的结果。
一般来说,这种况情况不会造成困扰,但若我们自己定义某个物件的valueOf( )
方法与toString( )
方法,就要特别注意了。
string隐含地强制转型成number:
-
运算子只能用来做数值的减法,所以a - 0
中的a
势必会转成number,a * 1
、a / 1
也会产生相同的结果。
array的转型:
上面的array会先转型成string,再转成number。
b = String( a )
与b = a + ""
,这2者都是合法的语法,但b = a + ""
能见度较高。
隐含地: Boolean <--> Numbers
Example:
上面的结果,只有在「所有的引数中,只有一个true或truthy」的情况下,才会为true,这显然不是一个很好的程式码,可读性差。若我们要处理的引数更多呢?
这时,我们换另一个方式,利用boolean转型为number的特性:
function onlyOne() {var sum = 0;for (var i=0; i < arguments.length; i++) {if (arguments[i]) {sum += arguments[i];}}return sum == 1;}var a = true;var b = false;onlyOne( b, a ); // trueonlyOne( b, a, b, b, b ); // trueonlyOne( b, b ); // falseonlyOne( b, a, b, b, b, a ); // false
关于arguments[i]
可以参考这篇文章 call函式 & arguments物件
如果至少有一个引数的话,就会进入for迴圈。若arguments[i]
的值为true(1),条件成立,sum加1;若第二次(以上)成立,sum会大于1,sum == 1
的结果就会为false。换言之,只要有2个以上(含)的a
,那结果就会为false。
另一个Example:
function onlyOne() {var sum = 0;for (var i=0; i < arguments.length; i++) {sum += Number( !!arguments[i] );}return sum === 1;}onlyOne( "42", 0 ) //true
利用!!arguments[i]
会强制把值转换成true或false,再利用Number( )
会把boolean转成0或1。
只要2个以上(含)成立,sum就不会是1,自然运算结果就为false。
相信透过上述的强制转型,可读性会比一堆 && 跟 || 来的高,并提高了可扩充性。
隐含地:* --> Boolean
隐含地强制转型是由于一个值的运算过程,迫使它必须转型才发生的事情。
会发生隐含地强制转型的情况:
if( )中的条件判定。for( )迴圈中第2个子句。while( )与do...while( )中的条件判断。? : 三元运算式的第1个子句。||(or) 与 &&(and)运算子。var a = 42;var b = "abc";var c;var d = null;if (a) {console.log( "yep" );// yep}while (c) {console.log( "nope, never runs" );}c = d ? a : b;c;// "abc"if ((a && d) || c) {console.log( "yep" );// yep}
在以上的情境中,非boolean值都会隐含地强制转型成boolean值,以做出判断。
运算子 || 与 &&
||(or) 与 &&(and)运算子,实际上不会产生boolean值,而是2个运算元中的其中一个值。
换句话说,它们会选择其中一个运算元的值。
Example:
|| 与 && 运算子会在第一个运算元进行boolean(非boolean值会转型)测试。
以 || 来说,若测试结果为true,那就会回传第1个运算元的值,反之,回传第2个。
以 && 来说,若测试结果为true,那就会回传第2个运算元的值,反之,回传第1个。
|| 与 && 运算式的值,永远都会是其中一个运算元的值,而非测试结果(boolean)。
思考另一个例子:a || b
与a ? a : b
的结果虽然相等,却存在着细微差异。
在a ? a : b
中,若a是一个较複杂的运算式,且运算结果为true的话,那a有可能会被估算2次。
而a || b
,a只会被估算一次,而该值会被用于测试中,若为true,也会被当作结果输出。
此种方式有个很常见的手法:
function foo(a,b) {a = a || "hello";b = b || "world";console.log( a + " " + b );}foo();// "hello world"foo( "yeah", "yeah!" );// "yeah yeah!"
以a
来说,若没有传入值,或是falsy值,a
就会是备用的值'"hello"'。
但如果是
foo( "That's it!", "" ); // "That's it! world"
第二个值,即便我们传入"",依旧是flasy值,就会替换成"world"。
如若要更明确的测试,可以改用三元运算子。
关于&&的範例:
function foo() {console.log( a );}var a = 42;a && foo(); // 42
只有在a
为true的情况下,才会执行foo( )
方法,所以这种方式有时也会被称为「守护运算子」或是「短路」。
关于「短路」,会在第5章提到。
上面的方式,有另一种呈现,你或许看过:
if (a) { foo(); }
不过,JS还是会採用捷径的方式来处理。
来看看另一种情境:
var a = 42;var b = null;var c = "foo";if (a && (b || c)) {console.log( "yep" );}
结果会产生yep。if叙述句判断式会将a && (b || c)
的结果"foo"
,强制转型成true。
善用隐含地强制转型,可以让我们的程式码可读性与维护性更高。
或者,可以试试明确地强制转型:
if (!!a && (!!b || !!c)) {console.log( "yep" );}
哪种方式比较适合,心中自有答案。
参考来源:
此为You Don't Know JS系列的笔记。