摘要:注釋空數(shù)組空對象轉換為布爾型也是坑。系統(tǒng)會在自動類型轉換的時候調用他們,所以我們通常不需要手動調用他們。嚴格相等不存在類型轉換,對于類型不同的兩個值直接返回。
Javascript 中有5種基本類型(不包括 symbol),以及對象類型,他們在不同的運算中會被系統(tǒng)轉化為不同是類型,當然我們也可以手動轉化其類型。
Javascript 類型轉換中的坑極多,就連 Douglas Crockford 在 《Javascript: The Good Parts》一書中也極力 "吐槽" 。下面我們來自習研究一下這個部分,希望不要把自己繞暈。
typeof 運算在解釋各個類型之前,我們需要理解 typeof 運算。該運算得到對象的類型:
</>復制代碼
typeof 2; //number
typeof "abc"; //string
typeof true; //boolean
typeof undefined; //undefined
typeof new Date(); //object
typeof null; //object
typeof NaN; //number, NaN 即 Not a Number,表示一個非法值。
typeof [1,2,3]: //object
typeof /^d*$/; //object
function fn(){} //定義一個函數(shù)
typeof fn; //function
通過上面例子我們可以很明顯的看到,除了基本類型以外的類型,都是對象,但是有例外:null 的 typeof 值是 "object" 【坑1】, 函數(shù)的 typeof 值是 "function" ! (函數(shù)對象的構造函數(shù)是 Function,也就繼承了 Function 的原型)【坑2】
</>復制代碼
注:JS 中 typeof 根據(jù)變量存儲單元特性判斷變量類型,其中當變量的前三位都是 0, 這個變量就是對象類型。但不幸的是,null 的所以位都是 0,結果就被識別成對象了。(摘自《你不知道的 JS 上卷》)
而且我們不難發(fā)現(xiàn),NaN 的類型也是 "number",這個地方也是矛盾十足【坑3】
注意:本文的測試在現(xiàn)在最新瀏覽器上進行,老版本瀏覽器可能有所不同。比如Safari 3.X中typeof /^d*$/;為"function"【坑4:兼容性復雜】。
不是所有對象都是返回 "object",而且還有 null 搗亂,那我們如何判斷一個值的類型呢?這個問題超過了本篇文章的知識范圍,但我會實現(xiàn)一個 typeof 函數(shù),可以更好的取代這個 typeof 運算符。為了不讓讀者和下文內容混了,我把它放在了文章末尾。
強制類型轉換(手動類型轉換)對于基本類型而言,數(shù)值、布爾和字符串具有其對應的對象類型,其構造函數(shù)在沒有new關鍵字調用的時候是類型轉換函數(shù),使用方法如下:
</>復制代碼
var num = Number("43"); //43
typeof num; //"number"
var str = String(num); //"43"
var flag = Boolean(num); //true
具體的轉換規(guī)律參看下表:
原始類型 | 目標類型(string) | 目標類型(number) | 目標類型(boolean) | 目標類型(object) |
---|---|---|---|---|
undefined | "undefined" | NaN | false | throw TypeError |
null | "null" | 0 | false | throw TypeError |
true | "true" | 1 | - | new Boolean(true) |
false | "false" | 0 | - | new Boolean(false) |
"" | - | 0 | false | new String("") |
"1.2" | - | 1.2 | true | new String("1.2") |
"1.2a" | - | NaN | true | new String("1.2a") |
"a" | - | NaN | true | new String("a") |
0 | "0" | - | false | new Number(0) |
-0 | "0" | - | false | new Number(-0) |
NaN | "NaN" | - | false | new Number(NaN) |
Infinity | "Infinity" | - | true | new Number(Infinity) |
-Infinity | "-Infinity" | - | true | new Number(-Infinity) |
1 | "1" | - | true | new Number(1) |
{} | toPrimitive | toPrimitive | true | - |
[] | "" | 0 | true | - |
[9] | "9" | 9 | true | - |
["a", "b"] | "a,b" | NaN | true | - |
function | 函數(shù)源代碼 | NaN | true | - |
注釋1: 對于 toPrimitive 會在下文詳細解釋。
注釋2:只有空字符串("")、null、undefined、+0、-0 和 NaN 轉為布爾型是 false,其他的都是 true。
注釋3:空數(shù)組、空對象轉換為布爾型也是 true【坑5】。
注釋4:null 和 undefined 轉換為數(shù)字是表現(xiàn)不一,分別為NaN和0。【坑6】
有個東西需要多帶帶說明:
字符串轉換為數(shù)字,除了 Number() 還有 parseInt() 和 parseFloat() 函數(shù)。他們是有區(qū)別的:
parseInt() 將輸入值轉化為整數(shù);parseFloat() 如果輸入的是小數(shù)字符串(或具有可轉換小數(shù)的字符串)轉換為小數(shù),如果輸入是個整數(shù)字符串依然返回整數(shù)【坑7】:
</>復制代碼
console.log(parseFloat(" 6.2 ")); //6.2
console.log(parseFloat("10")); //10
parseFloat() 可以轉換以“點 + 數(shù)字”可是開頭的字符,其默認整數(shù)部分為0;parseInt()不行,會返回NaN:
</>復制代碼
console.log(parseInt(".21")); //NaN
console.log(parseFloat(".21")); //0.21
console.log(parseFloat(".0d")); //0
parse***() 函數(shù)可以轉換以數(shù)字開頭(或開頭有正負號)的所有字符串,遇到無法轉換的字母或符號停止轉換,返回已轉換的部分。對于不能轉換的字符串返回NaN:
</>復制代碼
console.log(parseInt("10.3")); //10
console.log(parseFloat(".d1")); //NaN
console.log(parseFloat("10.11.33")); //10.11
console.log(parseFloat("4.3years")); //4.3
console.log(parseFloat("He40.3")); //NaN
parseInt()在沒有第二個參數(shù)時默認以十進制轉換數(shù)值,有第二個參數(shù)時,以第二個參數(shù)為基數(shù)轉換數(shù)值,如果基數(shù)有誤返回NaN:
</>復制代碼
console.log(parseInt("13")); //13
console.log(parseInt("11",2)); //3
console.log(parseInt("17",8)); //15
console.log(parseInt("1f",16)); //31
Number() 參數(shù)不支持參數(shù)中有不符合數(shù)字規(guī)范的任何符號,不滿足此要求返回NaN, 對于滿足此要求的參數(shù),返回十進制數(shù)值(整數(shù)或浮點數(shù))
</>復制代碼
console.log(Number("19")); //19
console.log(Number("1.2f")); //NaN
console.log(Number("-10.3")); //-10.3
console.log(Number("10.3.3")); //NaN
parseInt() 和 Number() 也支持 "0x" 或 "0X" 引導的十六進制,但不支持 "0" 引導的八進制【坑8】:
</>復制代碼
console.log(parseInt("010")); //10
console.log(parseInt("0x20")); //32
console.log(parseInt("-0x20")); //-32
console.log(Number("010")); //10
console.log(Number("0x20")); //32
但是 Number 不支持負的十六進制【坑9】:
</>復制代碼
console.log(Number("-0x20")); //NaN
parseInt() 和 Number() 都會忽略字符串首尾的空格,但parseInt() 不會忽略格式化字符,而Number() 會將格式化字符與空格一起忽略【坑10】
</>復制代碼
Number(" 34
"); //34
Number("
34 "); //34
Number(" 3
4 "); //NaN, 不和開頭結尾的空格一起的格式化字符不會被忽略
parseInt("
34 "); //NaN
他們對空字符串的處理也不一樣【坑11】
</>復制代碼
Number(" "); //0, 空格被忽略了,所以 " " 等價于 ""
parseInt(" "); //NaN, 空格被忽略了,所以 " " 等價于 ""
進制轉換不局限在十六進制,js 會利用 0=9 和 A-Z 進行最高36進制的數(shù)制轉換:
</>復制代碼
parseInt("f*ck"); // -> NaN
parseInt("f*ck", 16); // -> 15
parseInt(null, 24) // -> 23
parseInt("Infinity", 10) // -> NaN
// ...
parseInt("Infinity", 18) // -> NaN...
parseInt("Infinity", 19) // -> 18
// ...
parseInt("Infinity", 23) // -> 18...
parseInt("Infinity", 24) // -> 151176378
// ...
parseInt("Infinity", 29) // -> 385849803
parseInt("Infinity", 30) // -> 13693557269
// ...
parseInt("Infinity", 35) // -> 1201203301724
parseInt("Infinity", 36) // -> 1461559270678...
parseInt("Infinity", 37) // -> NaN
對于 Number() 而言,不傳值和傳入 undefiend 是不一樣的【坑12】:
</>復制代碼
Number() // -> 0
Number(undefined) // -> NaN
Number() 接受數(shù)值作為參數(shù),此時它既能識別負的十六進制,也能識別0開頭的八進制,返回值永遠是十進制值
</>復制代碼
Number(3); //3
Number(3.15); //3.15
Number(023); //19
Number(0x12); //18
Number(-0x12); //-18
利用自動類型轉換簡單的實現(xiàn)手動類型轉換
這個部分利用一些簡單運算會自己調用相關函數(shù),實現(xiàn)轉換可以簡化代碼。需要說明的是:_Douglas Crockford_ 在 《Javascript: The Good Parts》書中推薦使用這個方法轉換類型,而不是手寫函數(shù)調用,因為以下方法執(zhí)行效率更高。
</>復制代碼
// 任意值 => 字符串
var str = "" + 2; //"2"
// 任意值 => 數(shù)字
var num = +"2"; //2
// 任意值 => 布爾
var bool = !!2; //true
// 數(shù)值取整數(shù)
var integer = ~~3.1415926; //3,這個不涉及類型轉換
// 數(shù)值取小數(shù)
var decimals = 3.1415926 % 1; //0.14159260000000007,這個不涉及類型轉換
對象類型和基本類型的關系
剛才我們解釋了基本變量的類型轉換,但沒有舉例一個基本變量和對象之間的轉換關系。在研究其關系之前,我們需要知道 new 關鍵字可以生成一個對象,new 后面的函數(shù)成為構造函數(shù)。
</>復制代碼
var str = new String(32); //String{...}
var num = new Number("22"); //Number{...}
var flag = new Boolean("hello"); //Boolean{...}
// 這里的參數(shù)也是會發(fā)生對應類型轉換的,但得到的是對象
typeof str; //object
typeof num; //object
typeof flag; //object
js中每一個對象,都是繼承自 Object 原型的(除非你手動實現(xiàn)一個不繼承自 Object.prototype 的對象),這里我們暫不討論原型。對于 String(), Number() 和 Boolean() 得到的對象都具有一個名為`[[PrimitiveValue]]
`的屬性,改屬性是對象對應的原始值,即基本類型變量。
默認地,每個對象都有一個toString()方法和一個valueOf()方法,當需要獲取對象原始值([[PrimitiveValue]])時候,調用valueOf()方法,需要獲取字符串時調用toString()方法。系統(tǒng)會在自動類型轉換的時候調用他們,所以我們通常不需要手動調用他們。
隱式類型轉換不僅僅使用 toString() 和 valueOf(),比如基本類型轉換為對象依然是使用 new 關鍵字;而基本類型直接互相轉換使用其類型對應函數(shù),比如字符串轉換為數(shù)字,使用 Number()。
隱式類型轉換(自動類型轉換)由于 js 是個弱類型語言,所以不是所有運算都要求類型一致,Js 為了一些運算可以執(zhí)行,使用了隱式類型轉換。也就是說,在一些計算中,系統(tǒng)會悄悄的完成類型轉換,比如以下情況:
</>復制代碼
(3.1415926).toFixed(2); //3.14, 由于數(shù)字是基本類型不具備方法,所以自動將其轉換為對象類型
3 + "23"; //"323" 數(shù)值和字符串類型不同,運算時將3轉換為字符串
5 == "5"; //比較雙方類型不同,發(fā)生類型轉換。
"a" < "b"; //這個更不一樣,因為字符串比較實際上是比較其 ASCII 碼的大小
數(shù)值加法和字符串連接
為什么 3 + "23"; 不把字符串轉成數(shù)字呢?只能說這是規(guī)定!!也可能是考慮到了字符串不一定都能轉成數(shù)字,而數(shù)字一定可以轉成字符串吧。其實廣義來講,只要不是兩個數(shù)字相加,都會吧不是字符串的那一個(或2個)轉換為字符串然后連接,所以這個部分比較簡單,我們只看2個有特點的例子就好:
</>復制代碼
console.log({o:1} + "88"); //[object Object]88
console.log([5,9] + "88"); //5,988
console.log(function(e){return;} + "88"); //function(e){return;}88
默認的對象轉換為字符串使用了 toString 方法(實際上沒這么簡單,詳細見下文),而 toString 對于一般對象而言得到 [object 構造函數(shù)名稱] 這樣的一個字符串。而數(shù)組和函數(shù)重寫了對象的 toString 方法,所以數(shù)組得到用逗號鏈接的元素序列字符串;函數(shù)得到其源代碼字符串。
不過要注意到,除了加號(+),其他符號都是默認轉換為數(shù)值型:
</>復制代碼
"3" - 1 // -> 2
"3" == 3 //轉換后比較 3 == 3,而不是 "3" == "3"
但是,不巧的是這里又有例外了:就是 null 和 undefined!!
null 和 undefined這里面首先需要解釋的一個坑就是 null 和 undefined 相關的比較問題:
1、 null/undefined 和字符串相加是轉換為字符串"null"/"undefined",和數(shù)字相加是,null 轉化為0,而 undefined 轉換為 NaN(NaN 和任何數(shù)值相加得到的都是 NaN)【坑13】
</>復制代碼
console.log(null + 20); //20
console.log(undefined + 20); //NaN
console.log(null + "20"); //null20
console.log(undefined + "20"); //undefined20
2、 null 和 undefined 除了和自己以及彼此以外和誰都不相等,比如下面這個例子,雖然 null 和 undefined 類型轉換都是 false,但它們誰都不等于 false【坑14】
</>復制代碼
console.log(false == undefined); // false
console.log(false == null); // false
console.log(true == undefined); // false
console.log(true == null); // false
console.log(null == undefined); // true
雖然它們彼此是相等的,但不嚴格相等
</>復制代碼
console.log(null === undefined); // false
那么我們就有必要區(qū)分一下相等和嚴格相等。簡單來說:
相等:對于類型不同的兩個值而言,通過類型轉換可以相等的依然返回 true。
嚴格相等:不存在類型轉換,對于類型不同的兩個值直接返回 false。
這樣的解釋,簡單但不明了,因為你會遇到下面這個坑【坑15】:
</>復制代碼
if("0") {
console.log("yes");
}
由于之前我們總結過,只有空字符串("")、null、undefined、0 和 NaN 的布爾型是 false,其他的都是 true,所以上述代碼是可以輸出 ‘yes’ 的。但是我們執(zhí)行以下代碼:
</>復制代碼
console.log(false == "0"); // true
console.log(true == "0"); // false
到這里一臉懵逼!這簡直不能更坑!沒辦法,想搞明白這個事還得去看規(guī)范(7.2.13-7.2.14):
關于 == 和 !=</>復制代碼
The comparison x == y, where x and y are values, produces true or __false__. Such a comparison is performed as follows:
If Type(x) is the same as Type(y), then
Return the result of performing Strict Equality Comparison x === y.
If x is null and y is __undefined__, return __true__.
If x is undefined and y is __null__, return __true__.
If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.
Return __false__.
翻譯如下:
</>復制代碼
比較表達式 x == y (x 和 y 為值) 返回 true 或 __false__,執(zhí)行過程如下:
如果 Type(x) 和 Type(y) 相同,則
返回 x === y 的結果;
如果 x 是 null 并且 y 是 __undefined__,返回 __true__;
如果 x 是 undefined 并且 y 是 __null__,返回 __true__;
如果 Type(x) 是數(shù)值并且 Type(y) 是字符串,返回 x == ToNumber(y) 的結果;
如果 Type(x) 是字符串并且 Type(y) 是數(shù)值,返回 ToNumber(x) == y 的結果;
如果 Type(x) 是布爾型,返回 ToNumber(x) == y 的結果;
如果 Type(y) 是布爾型,返回 x == ToNumber(y) 的結果;
如果 Type(x) 是字符串、數(shù)值或 Symbol 并且 Type(y) 是對象, 返回 x == ToPrimitive(y) 的結果;
如果 Type(x) 是對象并且 Type(y) 是字符串、數(shù)值或 Symbol , 返回 ToPrimitive(x) == y 的結果;
返回 __false__;
關于規(guī)范中的 ToPrimitive() 用來將對象轉換為 原始值 或 字符串 ,在規(guī)范7.1.1節(jié)中也有解釋,簡單來說:
ToPrimitive() 默認將類型轉為原始值,但是對象可以通過@@toPrimitive 方法重新定義其行為。規(guī)范中只有 Date 對象和 Symbol 重新定義了該行為,Date 和 Symbol 的 ToPrimitive() 默認得到 String 類型;
其次,ToPrimitive() 是依賴對象的 toString() 和 valueOf() 方法的。對象轉換基本類型時,先調用 valueOf(),如果 valueOf() 返回的不是基本類型,才調用 toString()。
如果toString() 和 valueOf()都不是函數(shù)或是返回對象的函數(shù),則拋出 TypeError 異常。
詳見規(guī)范第7.1.1 節(jié) OrdinaryToPrimitive
關于 === 和 !==</>復制代碼
The comparison x === y, where x and y are values, produces true or __false__. Such a comparison is performed as follows:
If Type(x) is different from Type(y), return __false__.
If Type(x) is Number, then
If x is __NaN__, return __false__.
If y is __NaN__, return __false__.
If x is the same Number value as y, return __true__.
If x is +0 and y is __-0__, return __true__.
If x is -0 and y is __+0__, return __true__.
Return __false__.
Return SameValueNonNumber(x, y).
</>復制代碼
NOTE: This algorithm differs from the SameValue Algorithm in its treatment of signed zeroes and NaNs.
翻譯如下:
</>復制代碼
比較表達式 x === y (x 和 y 為值) 返回 true 或 __false__,執(zhí)行過程如下:
如果 Type(x) 和 Type(y) 不同, 返回 __false__;
如果 Type(x) 是數(shù)值, 則
如果 x 是 __NaN__, 返回 __false__;
如果 y 是 __NaN__, 返回 __false__;
如果 x 和 y 值相等, 返回 __true__;
如果 x 是 +0 并且 y 是 __-0__, 返回 __true__;
如果 x 是 -0 并且 y 是 __+0__, 返回 __true__;
返回 __false__;
返回 SameValueNonNumber(x, y);
</>復制代碼
注意: SameValue 算法在對待 0 和 NaN 存在差別
感覺上面這個注意又是個坑呀,博主趕緊去繼續(xù)查手冊,發(fā)現(xiàn)這個函數(shù)的操作方法:
</>復制代碼
The internal comparison abstract operation SameValueNonNumber(x, y), where neither x nor y are Number values, produces true or __false__. Such a comparison is performed as follows:
Assert: Type(x) is not Number.
Assert: Type(x) is the same as Type(y).
If Type(x) is Undefined, return __true__.
If Type(x) is Null, return __true__.
If Type(x) is String, then
If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return __true__; otherwise, return __false__.
If Type(x) is Boolean, then
If x and y are both true or both __false__, return __true__; otherwise, return __false__.
If Type(x) is Symbol, then
If x and y are both the same Symbol value, return __true__; otherwise, return __false__.
If x and y are the same Object value, return __true__. Otherwise, return __false__.
翻譯如下:
</>復制代碼
內部的抽象比較操作 SameValueNonNumber(x, y) (x 和 y 為值) 返回 true 或 __false__,執(zhí)行過程如下::
斷言: Type(x) 不是數(shù)值;(譯注: 不符合直接拋出異常)
斷言: Type(x) 和 Type(y) 類型一樣;(譯注: 不符合直接拋出異常)
如果 Type(x) 是 undefined,返回 __true__;
如果 Type(x) 是 null,返回 __true__;
如果 Type(x) 是字符串, 則
如果 x 和 y 是嚴格相同的字符序列 (相同長度并且對應下標的字符編碼一致),返回 __true__; 否則,返回 __false__;
如果 Type(x) 是布爾型, 則
如果 x 和 y 都是 true 或者都是 __false__,返回 __true__; 否則,返回 __false__;
如果 Type(x) 是 symbol, 則
如果 x 和 y 是同一個 Symbol,返回 __true__; 否則,返回 __false__;
如果 x 和 y 是同一個對象,返回 __true__; 否則,返回 __false__;
一下翻譯了這么多,至少不會感到暈了。js 就是這樣比較兩個值的,讀完這些內容,是不是理解什么:
</>復制代碼
只要 === 為 __true__,== 一定為__true__;
只要 != 為__false__,!== 一定為__false__
比如下面再看一些奇怪的東西:
數(shù)組、對象比較
</>復制代碼
var a = [1];
var b = [2];
var c = a;
console.log(a == b); //false, 因為不是同一個對象
console.log(a == c); //true, 因為是同一個對象
// 所以
console.log([] == []); //false
console.log({} == {}); //false
比如這樣的代碼:
</>復制代碼
!![] // -> true, 和 ==, ===, !=, !== 無關的類型轉換不會調用內置的 toPrimitive, 這里調用 Boolean([]) 得到 true
[] == true // -> false, 這個通過轉換得到的是 0 == 1, 返回 false
以下兩個同理:
</>復制代碼
!!null // -> false
null == false // -> false
關于 toString() 和 valueOf()
</>復制代碼
"J" + { toString: function() { return "S"; } }; // "JS"
2 * { valueOf: function() { return 3; } }; // 6
上面這個例子不深究的話,看上去似乎若合符節(jié),一個轉為字符串,調用了 toString,第二個轉換為數(shù)字,調用了 valueOf。實際上并不是這么簡單【坑16】:
根據(jù)之前那個表格,這里使用 toPrimitive 而再看 toPrimitive 的定義,除了 Date 和 Symbol 類型轉化為字符串,其余的對象都默認轉化為數(shù)字,所以這里都是先調用 valueOf ,而對象的 valueOf 默認返回對象本身(this),這個不符合規(guī)范,因為規(guī)范要求不能返回對象,所以第一個表達式繼續(xù)調用toString 得到了 "S",而第二個 valueOf 直接返回 3,沒有調用 toString。 為了說明這個邏輯,我們再看一個例子,這次我做過多解釋了:
</>復制代碼
var oriObj = {}
var myObj = {
toString: function() {
return "myObj";
},
valueOf: function() {
return 17;
}
};
"object: " + myObj; // "object: 17"
+0 和 -0 是一致的
</>復制代碼
console.log(+0 === -0); //true
console.log(+0 == -0); //true
</>復制代碼
補充
即便如此,我們也可以用如下方法區(qū)別 +0 和 -0
</>復制代碼
function isNegativeZero(num) {
return num === 0 && (1 / num < 0);
}
NaN 是唯一一個不等于自己的值【坑17】
</>復制代碼
var x = NaN;
console.log(x == x); //false
這里有一個容易記混的地方
對于 + 運算,字符串和數(shù)字相加是將數(shù)字轉換為字符串;而 == 運算中是將字符串轉換為數(shù)字【坑18】
</>復制代碼
// 結合之前的【坑10】,就得到這么一讓人想罵娘的結果
console.log("
" == 0); //true
toLocaleString 和 toString
toLocaleString 和 toString 方法同時存在,它定義了個性化的字符串轉換功能,對于對象而言 toLocaleString 和 toString 是一樣的。不過Array, Number, Date 和TypedArray(ES6中的類型,這里不討論)都重寫了 toLocaleString。比如說數(shù)值類型:
</>復制代碼
console.log((1234).toLocaleString()); //1,234
console.log((1234567).toLocaleString("zh-Hans-CN-u-nu-hanidec", {useGrouping: false})); //一二三四五六七
console.log((1234567).toLocaleString("zh-Hans-CN-u-nu-hanidec", {useGrouping: true})); //一,二三四,五六七
日期類型:
得到一些地域性的時間表示
</>復制代碼
var date = new Date();
console.log(date.toString()); //Tue Apr 15 2014 11:50:51 GMT+0800 (中國標準時間)
console.log(date.toLocaleString()); //2014-4-15 11:50:51
console.log(date.toLocaleDateString()); //2014-4-15
console.log(date.toLocaleTimeString()); //上午11:50:51
數(shù)組類型的 toLocaleString 就是將數(shù)組中的數(shù)值類型和日期類型分別按 toLocaleString 轉換為字符串,再形成整體字符串。
關于 toLocaleString 的定義官方也是故意沒給出具體的實現(xiàn)細節(jié)【坑19】,這一點完全不能理解,所以這個方法用的場合也比較有限,這里不再贅述了。
Infinity關于 Infinity 的數(shù)學運算也比較簡單,如果學過數(shù)學中的極限的話很好理解,對于不定式運算(0 / 0, ∞ / ∞, ∞ - ∞),返回 NaN:
</>復制代碼
console.log(Infinity + Infinity); //Infinity
console.log(Infinity - Infinity); //NaN
console.log(Infinity * Infinity); //Infinity
console.log(Infinity / Infinity); //NaN
console.log(0 / 0); //NaN
javascript精度
javascript的小數(shù)精度范圍是$-1.79e308至1.79e308$,同時可以認為大數(shù)在-9e15~9e15之間的計算可以認為是沒有誤差的,即 MIN_SAFE_INTEGER 和 MAX_SAFE_INTEGER。我們可以用Number.MAX_VALUE和Number.MIN_VALUE獲得js中可表示的最大數(shù)和最小數(shù)。
</>復制代碼
console.log(Number.MIN_VALUE); //5e-324
console.log(Number.MAX_VALUE); //1.7976931348623157e+308
console.log(Number.MAX_SAFE_INTEGER); //9007199254740991
console.log(Number.MIN_SAFE_INTEGER); //-9007199254740991
對于計算值超過該范圍的數(shù)會被轉換為 Infinity 或 0,而且這個轉換不屬于類型轉換,而是編程語言處理了內存溢出后的結果:
</>復制代碼
console.log(2e200 * 73.987e150); //Infinity
console.log(-1e309); //-Infinity
console.log(4.18e-1000); //0
而且數(shù)值會在浮點計數(shù)和科學技術法間自動轉換,自動轉換臨界是1e-6
</>復制代碼
console.log(0.000006); //0.000006
console.log(0.0000006); //6e-7
但在精度范圍邊界,總會有一些問題【坑20】,姑且認為這也是個坑吧,不過這樣的問題在其他編程語言中也普遍存在
</>復制代碼
console.log(1e200 + 1 === 1e200); //true
console.log(0.1 + 0.2); //0.30000000000000004
console.log(0.3 === 0.1 + 0.2); //false
在比如下面這個
</>復制代碼
999999999999999 // -> 999999999999999
9999999999999999 // -> 10000000000000000
10000000000000000 // -> 10000000000000000
10000000000000000 + 1 // -> 10000000000000000
10000000000000000 + 1.1 // -> 10000000000000002
[] 和 {}
有了上面的基礎,這個最坑的部分來了
</>復制代碼
console.log(+{}); //NaN
console.log(+[]); //0
以上這兩個屬于轉換為數(shù)值,所以其值會調用 valueOf()(返回了對象),而后調用 toString(),前者得到 [object Object],后者得到 "", 再調用
Number() 得到結果,前者為 NaN,后者為 0。
理解了上面這個下面這個就不難了,都是轉換到字符串以后進行字符串鏈接
</>復制代碼
console.log({} + []); //[object Object]
console.log({} + {}); //[object Object][object Object]
console.log([] + []); //""
console.log([] + {}); //[object Object]
但如果像下面這樣使用呢,我們如何理解?
</>復制代碼
console.log({}[]); //[]
console.log([]{}); //"SyntaxError"(語法錯誤)
首先我們需要明白這2個表達式是從左到右執(zhí)行的。這個地方我們可以很簡單的證明第一個表達式中的{},不是對象:
</>復制代碼
var obj = {};
console.log(obj[]); //SyntaxError: Unexpected token ]
所以這里他是個表示代碼段的括號(注意塊級作用域是 ES6 提出了,在 ES5 中 {} 僅僅表示一個代碼段,如 if(exp){...} 中的 {}) ,這里這個代碼段里面什么也沒有,執(zhí)行完以后這個 {} 就沒了,剩下一個數(shù)組 []。第二個表達式 []{} 從左到右先遇到一個數(shù)組,數(shù)組后面定義代碼段或者對象都是不符合語法的。
我們再看幾個賦值相關的,這里又是一個坑,居然 js 敢不限制賦值表達式的左值是標識符或 Symbol【坑21】:
</>復制代碼
var [] = 1; //"TypeError"(類型錯誤)
var [] = "1" ; //(正常執(zhí)行,由于字符串對象本身就是類數(shù)組對象)
var [] = {}; //"TypeError"(類型錯誤)
var {} = [] ; //(正常執(zhí)行,僅僅是指針指向從對象改變到了數(shù)組)
以上的2個錯誤,都是 “TypeError: undefined is not a function”,很明顯,由于表達式不規(guī)范導致被js誤認為是一個函數(shù),從而報錯。
如果你理解了這些,不妨研究一下下面兩個表達式的值吧:
</>復制代碼
(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]] //"fail"
(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]] //"sb"
當然還有更奇怪的,原因還是在于數(shù)組對象重寫了對象的 toString 方法【坑】:
</>復制代碼
[] == ![] //true
{} == !{} //false
下面這個輸入,博主一直很疑惑。把2行代碼分別輸入到 chrome 控制臺,得到對應結果。按規(guī)范的邏輯應該輸出[object Object],但這個是為什么呢?
</>復制代碼
console.log({} + []); //[object Object]
{}+[]; //0
原因是第二行中的{}被當做了快作用域,而不是一個對象。
數(shù)組中的 null 和 undefined數(shù)組中的 null 和 undefined 會在轉換為字符串時被看做空,也就是可以直接忽略。
</>復制代碼
"" == [null]; //true
"1,,3" == [1,undefined,3] //true
大于號和小于號
大于和小于運算的兩邊都會被轉化為數(shù)字,但字符串會安其 ASCII 碼或 UNICODE 碼把每個字符一次比較,得到 Boolean 值。比如:
</>復制代碼
"abc" > "abd"; //false
"aBc" > "abc"; //false
"093" < "15"; //true
但這里有一個奇怪的例子:
</>復制代碼
var a = {pro: 29};
var b = {pro: 43};
a < b; //false
a == b; //false
a > b; //false
a <= b; //true
a >= b; //true
對于大于(等于)和小于(等于)號,兩個對象 a 和 b 都被轉換成了字符串 "[object Object]",所以他們應該是相等的,所以 a < b 和 a > b 都是 false,而 a <= b 和 a > = b 都是 true。但是 a == b 為 false。有了上面的知識,就很好理解這個問題,a, b都是對象,所以不發(fā)生類型轉換,而兩個對象引用不同,結果為 false。
ES6 中的類型轉換和坑ES6 中同樣帶入了許多坑,當然這些坑不一定都是類型轉換導致的。
label 和 塊作用域比如下面這段代碼,看似像定義對象屬性,但實際上是個塊級作用域,foo: 是一個的標簽,用來給 break 指定跳轉的地方。
</>復制代碼
foo: {
console.log("first"); //first
break foo;
console.log("second"); //不輸出
}
再看下面這個:
由于前面的 a-g 都是標簽,而后面的逗號表達式會返回最后一個表達式的值
</>復制代碼
a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5
解構賦值
比如這樣定義變量,并且結構賦值
</>復制代碼
let x, { x: y = 1 } = { x }; //由于 x 是 undefined 所以 y 取了默認值 1
console.log(y); //1
模板字符串和對象中的類型轉換
對象在類似 EL 表達式中會被自動轉換為字符串, 而對象的鍵值也會被默認轉換為字符串(除了 Symbol 類型)
</>復制代碼
`${{Object}}` //"[object Object]"
{ [{}]: {} } // -> { "[object Object]": {} }
展開運算符
由于字符串具有 iterator 就被展開了:
</>復制代碼
[...[..."..."]].length //3 實際上得到的是[".", ".", "."]
try catch 語句
這個不算是 es6 的問題,不過我們也看一看:
try 中的 return 和 throw 會在有 finally 語句是中的 return 或 throw 覆蓋(這里的確是覆蓋,而不是前一個 return 未執(zhí)行,詳細可以參看規(guī)范第13.15.8節(jié)。
</>復制代碼
(() => {
var i = 0;
try {
return ++i;
} finally {
return ++i;
}
})(); // 2
可見上面兩個 return 都執(zhí)行了,但后一個把前一個覆蓋了。如果你認為第一個 return 沒執(zhí)行,而是執(zhí)行了自加,那你一定忘了程序執(zhí)行的最小單元是語句,而這里的 ++i 并不是一個完整的語句。
class 類</>復制代碼
//這個代碼是不會報錯的,系統(tǒng)會直接將 "class" 字符串作為對象的屬性名
const foo = {
class: function() {}
};
var obj = new class {
class() {}
};
console.log(obj); //{}, 和 var obj = new class{} 一樣
Symbol
這個類型轉換為字符串必須是顯示的,隱式轉換會出錯
</>復制代碼
var s = Symbol("aabb");
String(s); //"Symbol(aabb)"
s + ""; //TypeError: Cannot convert a Symbol value to a string
另一個更好的 typeOf 函數(shù)
</>復制代碼
function typeOf(val){
return Object.prototype.toString.call(val).slice(8, -1); //同樣可以很好的處理 null 和 undefined
}
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97607.html
摘要:本文主要介紹數(shù)據(jù)類型強制轉換和自動轉換,自動轉換是基于強制轉換之上。強制轉換主要指使用和三個函數(shù),手動將各種類型的值,分布轉換成數(shù)字字符串或者布爾值。 前言 JavaScript是一門動態(tài)語言,所謂的動態(tài)語言可以暫時理解為在語言中的一切內容都是不確定的。比如一個變量,這一時刻是個整型,下一時刻可能會變成字符串了。雖然變量的數(shù)據(jù)類型是不確定的,但是各種運算符對數(shù)據(jù)類型是有要求的。如果運算...
摘要:本文主要介紹數(shù)據(jù)類型強制轉換和自動轉換,自動轉換是基于強制轉換之上。強制轉換主要指使用和三個函數(shù),手動將各種類型的值,分布轉換成數(shù)字字符串或者布爾值。 前言 JavaScript是一門動態(tài)語言,所謂的動態(tài)語言可以暫時理解為在語言中的一切內容都是不確定的。比如一個變量,這一時刻是個整型,下一時刻可能會變成字符串了。雖然變量的數(shù)據(jù)類型是不確定的,但是各種運算符對數(shù)據(jù)類型是有要求的。如果運算...
摘要:本文主要介紹數(shù)據(jù)類型強制轉換和自動轉換,自動轉換是基于強制轉換之上。強制轉換主要指使用和三個函數(shù),手動將各種類型的值,分布轉換成數(shù)字字符串或者布爾值。 前言 JavaScript是一門動態(tài)語言,所謂的動態(tài)語言可以暫時理解為在語言中的一切內容都是不確定的。比如一個變量,這一時刻是個整型,下一時刻可能會變成字符串了。雖然變量的數(shù)據(jù)類型是不確定的,但是各種運算符對數(shù)據(jù)類型是有要求的。如果運算...
摘要:扎實基礎幸好自己之前花了大力氣去給自己打基礎,讓自己現(xiàn)在的基礎還算不錯。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Vue源碼閱讀總結大會 - 序 閱讀源碼是需...
閱讀 1726·2021-10-18 13:34
閱讀 3917·2021-09-08 10:42
閱讀 1560·2021-09-02 09:56
閱讀 1612·2019-08-30 15:54
閱讀 3135·2019-08-29 18:44
閱讀 3305·2019-08-26 18:37
閱讀 2221·2019-08-26 12:13
閱讀 461·2019-08-26 10:20