true 就是 1 ,false 就是 0,可是在 JS 中,number 就是 number,boolean 就是 boolean,你可以將兩者強制轉型,但那並不相同。
JavaScript 所有值可以被分為兩類:
ToBoolean定義了一個抽象運算,說明強制轉型為布林會發生什麼事。
JavaScript 沒有明確定義 truthy 的清單,語言規格暗示:沒有明確位於 falsy 清單的任何值都是 truthy。
document.all
是一個類陣列,以前被 JS程式所使用,但因為早就被遺棄了,而因為許多網站仍需要仰賴此功能無法移除,就將此改為 falsy 物件。
除此之都是 Truthy ,連包含 false 的物件包裹器都是 Truthy。
沒有明確位於 falsy 清單的任何值都是 truthy。
var a = "false";
var b = "0";
var c = "''";
var d = Boolean( a && b && c );
d;
因為這些變數內含 false 值,但變數本身就是 Truthy。
可以明確看得出來的型別轉換。
為了在 string
和 number
之間進行強制轉型,我們使用內建的String( ... )
和 Number( ... )
函式,要注意的是不使用 new
關鍵字,而我們要做的是在兩個型別做明確的強制轉型。換句話說,如果使用 new 關鍵字就是在建立物件包裹器。
var a = 42;
var b = String( a );
var c = "3.14";
var d = Number( c );
b; // "42"
d; // 3.14
無論是 String( ... )
還是 Number( ... )
,他們會把值強制轉型成基本型別值,詳細參考前面說的 ToNumber
和 ToString
。
除了String( ... )
或 Number( ... )
這兩個方法,還有其他方法可以在這兩個型別中做明確地轉換:
var a = 42;
var b = a.toString();
//表面上這看起來就是要轉成字串型別,
//但 toString 不能在基本型別值上被呼叫,所以他會被隱含的被封裝成物件包裹器。
var c = "3.14";
var d = +c;
// 這邊展示加號運算子的形式,
// 他不會做直接的加法運算或是字串連接
// 而是 + 把 c 強制轉型成數字
//
// 如果你沒有用過這些方法的話,你可能認為這些是副作用。
b; // "42"
d; // 3.14
如果你喜歡+c
模式的話,還是會有地方讓你困擾,像是:
var c = "3.14";
var d = 5+ +c;
d; // 8.14
-
運算子也會做強制轉型,但它同時也會反轉數字的正負號,但你也不能做--
,這樣會變成遞減運算子,所以你應該在他們之間放上空格- -
,結果就會像- -"3.14"
這樣,這樣就會做強制轉型。
所以如果與其他運算子緊密相鄰的話,你應該要避免用單元運算子來進行強制轉型。
單元 + 運算子另一個常見的用途是把 Date 物件強制轉型成數字:
var d = new Date( "Mon, 18 Aug 2014 08:53:06 CDT" );
+d; // 1408369986000
或是取得當下的時戳值:
var timestamp = +new Date();
var timestamp = +new Date;
//沒有參數的狀況下,小括號可加可不加
//但為了增加可讀性,請務必加小括號。
不過如果有不用強制轉型的方法應該優先使用,因為會更明確:
var timestamp = Date.now();
建議:不要用與日期有關的強制轉型,要產生現在時間戳值。
如果要取得非現在時間的時戳值,可用new Date( .. ).getTime()
。
很多人想避免用~
運算子,但我們還是需要深入了解。
在前面有說 JS 的位元運算子只對 32 位元的作業有定義,所以我們的運算元必須要符合 32 位元值的表示法。這進行的規則是由 ToInt32 抽象運算來控制。
ToInt32 會先進行 ToNumber 的強制轉型,再套用 ToInt32 的規則。
在 ToInt32 執行過程並沒有強制轉型,位元運算子(|
或~
)與某些特殊的 number 值並用,會產生類似強制轉型的效果,得出不同 number 值。
|
為位元 OR 運算子,先將兩數做二進制表示法,進行位元 OR 運算,再轉回 32 位元的數字。位元運算子也包含-
+
%
/
*
>>
<<
。
OR 運算:轉換為二進制,如果任何一個運算式的數字有 1,結果的那個數字就會有 1。否則,結果會在該數字出現 0。
以舉例 0 | x
:
0 | -0; // 0
0 | NaN; // 0
0 | Infinity; // 0
0 | -Infinity; // 0
像 NaN
或 Infinity
這種特殊值無法以 32 位元表示,所以 ToInt32 指定 0 作為這些值轉換的結果。換句話說,無法轉成功 32 位元,就是 0 。
~
運算子會先強制轉型為每個 32 位元的 number 值,再反轉每個位元。然而為何需要反轉?這起緣於離散數學,~
進行的是二的補數。
我們能明白~x
等於-(x+1)
:
~42; // -(42+1) ==> -43
簡單的說 32 位元的有效範圍的 number 值的~
運算,會為-1
這個輸入值產生一個 falsy 的 0 值(-0),而其他值產生 truthy 的 number。
-1,常被稱為一個哨符值,已與相同型別的其他值做區隔。
以indexOf(..)
來說,如果有找到值,就回傳從零算起的索引位置,如果沒有找到索引位置就回傳-1
,另外一個常見的狀況就是indexOf
常做在一個布林的檢查,常判斷某個子字串是否有出現在另一個字串中:
var a = "Hello World";
if (a.indexOf( "lo" ) >= 0) { // true
// 找到了!
}
if (a.indexOf( "lo" ) != -1) { // true
// 找到了
}
if (a.indexOf( "ol" ) < 0) { // true
// 沒找到!
}
if (a.indexOf( "ol" ) == -1) { // true
// 沒找到!
}
像是>= 0
或== -1
的寫法是一種容易洩露資訊的抽象層,我們可以用~
搭配indexOf
來改寫:
var a = "Hello World";
~a.indexOf( "lo" ); // -4 <-- truthy!
if (~a.indexOf( "lo" )) { // true
// 找到了!
}
~a.indexOf( "ol" ); // 0 <-- falsy!
!~a.indexOf( "ol" ); // true
if (!~a.indexOf( "ol" )) { // true
// 沒找到!
// 這個相等於 includes(..);
}
~
接受indexOf
的回傳值,對於-1
,會得到 falsy 的 0,而其他值都是 truthy。~
和 >= 0
或 == -1
的寫法比起來還是比較清楚明白。
~~
截斷位元。
常用:多數開發者會用來截斷數字的小數部分。
誤解:多數人認為~~
產生的結果與Math.floor(..)
相同。
結果:就只是做 ToInt32 的強制轉型。
運作方式:
~
先套用 ToInt32 的強制轉型,讓位元反轉~
進行另一次的逐位元反轉注意:
~~
逐位元雙次反轉類似!!
的行為。Math.floor(..)
不同。比較:~~x
與x | 0
都可以把數字截斷成 32 位元的整數,但因為運算子的優先序,所以優先選用~~x
,細節在第八章說明。
var a = "42";
var b = "42px";
Number( a ); // 42
parseInt( a ); // 42
Number( b ); // NaN
parseInt( b ); // 42
parseInt 剖析數字字串:
Number 轉態字串為數字:
兩者雖然相似,但有不同用途。
如果你不知道或是不在意其他非數值字元,就應該使用parseInt
,
如果能接受的值只有數字,就應該使用Number
。
注意:parseInt
的第二個引數是設定轉數字的數字進制。如果在舊型瀏覽器的parseInt
傳入08的數值,在第二個引數沒有設置下,預設為八進制,最好永遠設置第二個引數是 10。
parseInt( 1/0, 19 ); // 18
parseInt("Infinity", 19); //18
錯誤:傳入非字串給parseInt
。
base-19 的有效數值字元:0-9 與 a-i(不分大小)。
剖析過程:I
在 base-19 的值為 18,n
不在有效數值字元中,剖析停止。
其他 parseInt 的怪異行為:
parseInt( 0.000008 ); // 0 ("0" from "0.000008")
parseInt( 0.0000008 ); // 8 ("8" from "8e-7")
parseInt( false, 16 ); // 250 ("fa" from "false")
parseInt( parseInt, 16 ); // 15 ("f" from "function..")
parseInt( "0x10" ); // 16
parseInt( "103", 2 ); // 2
任何非布林值強制轉型為布林值,強制進行 ToBoolean 轉換的明確方法:
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
Boolean( a ); // true
Boolean( b ); // true
Boolean( c ); // true
Boolean( d ); // false
Boolean( e ); // false
Boolean( f ); // false
Boolean( g ); // false
但Boolean(..)
並不常見也不慣用,原因是它也會反轉該值,將」 truthy 變成 falsy,因此最常用的是!!
雙否定運算子。
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
!!a; // true
!!b; // true
!!c; // true
!!d; // false
!!e; // false
!!f; // false
!!g; // false
如果沒有 Boolean(..)
或 !!
,其他在 if(..) 述句中都是隱含的布林轉型,但這裡的目標是明確地轉型。
假設你希望在 JSON 序列化的過程中強制進行轉布林值,可以用下面方法:
var a = [
1,
function(){ /*..*/ },
2,
function(){ /*..*/ }
];
JSON.stringify( a ); // "[1,null,2,null]"
JSON.stringify( a, function(key,val){
if (typeof val == "function") {
// 強制函式進行 `ToBoolean` 轉換
return !!val;
}
else {
return val;
}
} );
// "[1,true,2,true]"
或是
var a = 42;
var b = a ? true : false;
第二個方法,雖然看起來像是明確的強制轉型,但當中有個隱含的強制轉型,a 要先被強制轉型成布林值,才能進行真假值的測試,建議避免這種方法。