這篇隨筆記錄一下js中數據的各種類型轉換的規則,雖然很基礎,但是重新過一遍會發現有些規范還是挺意想不到的
首先介紹一下ToString, ToNumber, ToBoolean 的轉換規則
1、ToString
規則1:null 轉換為 “null” , undefined 轉換為 “undefined” , true 轉換為 “true” ;
規則2:普通對象,除非自定義,否則用 toString();
數組的toString()方法,先把所有單元字符串化,然后再用“,”連接;[1,2,3,4] // “1,2,3,4”;
2、ToNumber
規則1:布爾值 true 轉換為 1, false 轉換為 0; undefined 轉換為 NaN; null 轉換為 0;字符串處理失敗時返回NaN;
規則2:以 0開頭地十六進制數會被當成十進制;
規則3:對象會先被處理成相應地基本類型值,再按照值類型做相應處理;
對象做ToPrimitive操作規則:先valueOf(), 如果valueOf的結果不是基本類型,再進行 toString();如果均不返回基本類型,則報TypeError;
使用Object.create(null) 創建的對象,無valueOf 跟 toString 方法,故不能被強制類型轉換
規則4:空數組和空字符串,都會被轉換為0:
Number("") // 0; Number([]) // 0;
3、ToBoolean
規則1:undefined,null,false,+0,-0,NaN,“” 會被轉換為false;但是對應的封裝對象為true
規則2:js中 document.all 如果用在 if 語句中,會被轉換為false;此條規則需要注意的是,在ie<= 10 的瀏覽器中,會返回true;
規則3:除規則1和規則2,其他都返回true
Boolean(undefined) // false; Boolean(null); // false; Boolean(false); // false; Boolean(+0); // false; Boolean(-0); // false; Boolean(""); // false; const a = new Boolean( false) ; // true const b = new Number(0); // true const c = new String("") ; // true if(document.all){
// ie10 及 以下,會被打印; console.log('document.all 在if語句中被強制類型轉換,但轉換的結果為false,這條語句不會被打印'); }
JSON.stringify 的規則與 強制類型轉換關聯,故也總結到這里:
JSON字符串化簡單數值時,規則與toString大致相同;但是序列化的結果總是字符串:JSON.stringify("42") // ""42"";
undefined、function、symbol 和包含對象間循環引用的,都屬於不安全的JSON值,這些值會被忽略,數組種則會返回 null,儀保證單元位置不變
例如:JSON.stringify([1,undefine,function(){},2]) // [1, null, null, 2];
JSON.stringify( {a:1, b: undefine, c: function(){} } ) // {a:1}; 此特性可用於發起請求時的無效參數過濾;
可自定義toJSON 函數,在JSON序列化時會被調用;該函數應該返回一個能夠被字符串化的安全的JSON值,而不是直接返回一個字符串,因為處理邏輯是對toJSON返回的字符串做字符化;
JSON.stringify 支持傳入可選參數replacer,用來指定對象序列化過程中屬性的處理邏輯:
a、當replacer為數組時, JSON.stringify 只序列化數組中包含的屬性,例如:
var a = { name: "Lily", age: 12 , hobby: ["basketball", "football"] } JSON.stringify(a, ["name", "age"]); // "{"name": "Lily" , "age": 12}"
// 這個特性可以用來提取對象中指定屬性
b、當replacer為函數時, 函數會對將要被序列化的對象做一個遍歷,入參為key 和 value,可在函數中寫處理邏輯:
var a = { name: "Lily", age: 12 , hobby: ["basketball", "football"] } JSON.stringify(a, function(key, value){ // "{"name":"Lily","hobby":["basketball","football"]}"
if(key !== "age") return value;
});
這里有一個細節: 當replacer函數第一次被調用時,是對對象本身的調用,key 為 undefined;
屬性的值為數組時,數組中的值也將被遍歷,此時key為數組的索引,value為數組的單元值;
接下來,我們看下一些操作符在強制類型轉換中的影響:
1、+ 操作符
+String : 會把字符串轉換為數字;
+Date:會把時間對象轉換為時間戳;同樣使用 Date.now(); new Date().getTime()也可以得到時間戳;
String + other :首先 other 會被轉換為字符串,再進行拼接操作;下面看一個例子:
{} + [] // 0 此處 {} 出現在語句的開始,會被解析為代碼塊,[]被轉換為數字0 [] + {} // "[Object Object]" 此處遵循字符串相加規則,[] 為 “”, {}為 "[Object Object]"
Number + Boolean:Boolean 會先被轉換為Number類型,再進行相加操作
- x / 等操作符,只針對Number類型有效,故這三個操作符會把操作數轉換為數字類型;
2、位運算符 ~ 和 | 位運算符只適用於32位整數,故操作數會被ToInt32強制轉換為32位格式;非數字類型的值會先通過ToNumber轉換為 數字類型;
~ 運算(字位操作“非”)返回2的補碼 ~x 大致等於 -(x+1);在~運算中,只有 x 為 -1 時,才會返回 0;這個特性可以用在將-1做為哨位值的運算中,避免暴露底層實現細節(抽象滲漏)
var a = "Hello world"; if(~a.indexOf("H")){ // 找到匹配 }
| 運算符(字位操作“或”)
0 | -0 // 0 0 | NaN // 0 0 | Infinity // 0 0 | -Infinity // 0
!運算符 :非運算符常出現在布爾值的隱式轉換中;
3、&& 和 || 運算 : 該邏輯運算(更形象地可以說是操作數選擇器運算)會返回其中一個操作數:
a && b : 首先對第一個操作數做條件判斷,結果為 true, 則返回 第二個操作數 b;如果為 false, 則返回第一個操作數a;
a || b : 首先對第一個操作數做條件判斷,結果為true,返回第一個操作數,條件判斷結果為 false, 返回第二個操作數;
這里有一個細節,a || b 的條件判斷語句中,如果 a為true,則不會再繼續執行 b;在這個特性下,可以做個小小的優化:
a ? a : b 可被優化為: a || b;
另外一個優化點就是我們熟知的短路寫法: b && b(); var a = b || 'default'; 等。
4、== 和 ===
寬松相等(==)和嚴格相等(===)的區別在於,寬松相等允許在相等比較中進行強制類型轉換。他們都會對值類型進行檢查,區別在於類型不同時他們的處理方式。
以下是 == 操作的一些規則:
規則1:NaN 不等於 NaN;+0 等於 -0;
規則2:對象的比較方式為:兩個對象指向同一個值時,即為相等,不發生強制類型轉換
強制轉換規則:
a、字符串和數字之間:字符串先被轉換成數字,再進行比較
b、其他類型和布爾值之間:布爾值要先被轉換為數字,再進行比較
c、null 和 undefined之間的相等比較:null == undefined // 返回true
d、對象和非對象之間的比較:對象會被先轉換為標量基本類型(字符串/數字/布爾值),然后再進行比較;
下面看一些容易被迷惑的例子:
var a = null; var b = Object(null); a == b // false; null 沒有對應的對象類型,故 d 相當於Object()創建的一個普通對象 var c = undefined; var d = Object(d); c == d // false undefined 沒有對應的對象類型,故 d 相當於Object()創建的一個普通對象
var e = NaN; var f = Object(NaN); e == f // false f被包裝為Number類型,拆封后為NaN,NaN == NaN // false
"0" == false // true 根據規則 b , 布爾值跟其他值比較,要先把布爾值轉換為數字,false -> 0; "0" == 0 為true
false == "" // true 根據規則b, false 被轉換為 0; 根據規則a,字符串 "" 也被轉換為 0
false == [] // true [] 先調用 valueOf,返回數組本身,繼續調用 toString, 返回空字符串 ""; false == "" 為 true
false == {} // false {} 先調用 valueOf,返回 {} ; 繼續調用 toString, 返回字符串 "[object Object]" , false == "[object Object]" 為 false
[] == ![] // true 首先計算 ![] 根據布爾值的轉換規則,[] 為 true , 那么 ![] 則為false 即 [] == false 結果為 true
2 == [2] // true [2] 調用 valueOf 返回數組對象[2], 繼續調用toString, 返回 "2" , 2 == "2" 結果為 true
"" == [null] // true 同樣valueOf 返回數組對象 [null], toString 返回 "" ;
0 == "\n" //true 空字符串,或者空格,都會被ToNumber轉換為0
"true" == true // false 這個簡單, 布爾值會先被轉換為Number, "true" == 1 結果為 false
5、關於 <= 和 >=
這次最讓人意外的就是這倆操作符,我們來看個例子:
var a = {b: 42}; var b = {b: 43}; a < b // false; a > b // false; a == b // false; a <= b // true; a >= b // true;
所以 <= 和 >= 為什么都為true?
首先,a 和 b都會先被處理成字符串 "[object Object]", 故 > 和 < 比較都為 false;根據對象的 == 運算規則,地址一致為相等, 故 == 比較也為false;
根據 規范 a <= b 會被處理成 b < a, 然后將結果反轉。因為 b < a 為 false, 故 a <= b 為 true;也就是說,js中的 <= 其實是 “不大於” 的意思。。。
另外,還有一些對數據類型做了轉換的函數,也值得關注
1、parseInt() 顯示解析數字字符串
和 Number的顯示轉換 不同,解析允許字符串中含有非數字字符,而Number顯示轉換則不允許:
Number("42px"); // NaN; 顯示轉換 parseInt("42px"); // 42 顯示解析
Number(""); // 0
parseInt("") // NaN
parseInt針對的是字符串值,如果向parseInt傳入了一些奇奇怪怪的參數,那轉換結果也是會出乎意料的:
parseInt先將參數強制類型轉換為字符串再進行解析
parseInt(0.000008) // 0 首先被轉換為字符串“0.000008”;然后被解析出 0; parseInt(0.0000008) // 8 首先被轉換為字符串“8e-7”, 然后被解析出 8 parseInt(false, 16) // 250 "false" 進行 16進制轉換,前面的兩個字母 “fa” 在16進制的字母內,被轉換為250 parseInt(parseInt, 16) // 15 首先被解析為字符串“function (){}...” 首字母 “f” 以十六進制轉換規則,被轉換為十進制 15 parseInt("0x10") // 16 0x開頭為16進制,所以只看后兩位0*16^0+1*16^1=16,所以0x10=16 parseInt("103", 2) // 2 字符串103 以2進制規則轉換,前兩位 "10" 符合二進制數,被轉換為 2, "3" 不在二進制表達范圍內,故被舍棄,最終轉換為十進制的結果為2;
最后,提一下es6中符號類型,它的強制轉換都必須是顯式的,但轉換為布爾值除外;並且符號不能夠被強制類型轉換為數字:
var s1 = Symbol("cool"); String(s1) // "Symbol(cool)"; var s2 = Symbol("not cool"); s2 + "" ; // TypeError Boolean(s1) ; // true !!s1 // true
if(s1){
console.log('這條語句可以被執行');
}