成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

透徹研究Javascript類型轉(zhuǎn)換

dailybird / 2877人閱讀

摘要:注釋空數(shù)組空對(duì)象轉(zhuǎn)換為布爾型也是坑。系統(tǒng)會(huì)在自動(dòng)類型轉(zhuǎn)換的時(shí)候調(diào)用他們,所以我們通常不需要手動(dòng)調(diào)用他們。嚴(yán)格相等不存在類型轉(zhuǎn)換,對(duì)于類型不同的兩個(gè)值直接返回。

Javascript 中有5種基本類型(不包括 symbol),以及對(duì)象類型,他們?cè)诓煌倪\(yùn)算中會(huì)被系統(tǒng)轉(zhuǎn)化為不同是類型,當(dāng)然我們也可以手動(dòng)轉(zhuǎn)化其類型。

Javascript 類型轉(zhuǎn)換中的坑極多,就連 Douglas Crockford 在 《Javascript: The Good Parts》一書中也極力 "吐槽" 。下面我們來自習(xí)研究一下這個(gè)部分,希望不要把自己繞暈。

typeof 運(yùn)算

在解釋各個(gè)類型之前,我們需要理解 typeof 運(yùn)算。該運(yùn)算得到對(duì)象的類型:

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,表示一個(gè)非法值。
typeof [1,2,3]:    //object
typeof /^d*$/;    //object
function fn(){}    //定義一個(gè)函數(shù)
typeof fn;         //function

通過上面例子我們可以很明顯的看到,除了基本類型以外的類型,都是對(duì)象,但是有例外:null 的 typeof 值是 "object" 【坑1】, 函數(shù)的 typeof 值是 "function" ! (函數(shù)對(duì)象的構(gòu)造函數(shù)是 Function,也就繼承了 Function 的原型)【坑2】

注:JS 中 typeof 根據(jù)變量存儲(chǔ)單元特性判斷變量類型,其中當(dāng)變量的前三位都是 0, 這個(gè)變量就是對(duì)象類型。但不幸的是,null 的所以位都是 0,結(jié)果就被識(shí)別成對(duì)象了。(摘自《你不知道的 JS 上卷》)

而且我們不難發(fā)現(xiàn),NaN 的類型也是 "number",這個(gè)地方也是矛盾十足【坑3】

注意:本文的測(cè)試在現(xiàn)在最新瀏覽器上進(jìn)行,老版本瀏覽器可能有所不同。比如Safari 3.X中typeof /^d*$/;"function"【坑4:兼容性復(fù)雜】。

不是所有對(duì)象都是返回 "object",而且還有 null 搗亂,那我們?nèi)绾闻袛嘁粋€(gè)值的類型呢?這個(gè)問題超過了本篇文章的知識(shí)范圍,但我會(huì)實(shí)現(xiàn)一個(gè) typeof 函數(shù),可以更好的取代這個(gè) typeof 運(yùn)算符。為了不讓讀者和下文內(nèi)容混了,我把它放在了文章末尾。

強(qiáng)制類型轉(zhuǎn)換(手動(dòng)類型轉(zhuǎn)換)

對(duì)于基本類型而言,數(shù)值、布爾和字符串具有其對(duì)應(yīng)的對(duì)象類型,其構(gòu)造函數(shù)在沒有new關(guān)鍵字調(diào)用的時(shí)候是類型轉(zhuǎn)換函數(shù),使用方法如下:

var num =  Number("43");          //43
typeof num;                       //"number"
var str = String(num);            //"43"
var flag = Boolean(num);          //true

具體的轉(zhuǎn)換規(guī)律參看下表:

原始類型 目標(biāo)類型(string) 目標(biāo)類型(number) 目標(biāo)類型(boolean) 目標(biāo)類型(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: 對(duì)于 toPrimitive 會(huì)在下文詳細(xì)解釋。

注釋2:只有空字符串("")、nullundefined、+0-0NaN 轉(zhuǎn)為布爾型是 false,其他的都是 true。

注釋3:空數(shù)組、空對(duì)象轉(zhuǎn)換為布爾型也是 true【坑5】。

注釋4:nullundefined 轉(zhuǎn)換為數(shù)字是表現(xiàn)不一,分別為NaN0?!究?】

有個(gè)東西需要多帶帶說明:
字符串轉(zhuǎn)換為數(shù)字,除了 Number() 還有 parseInt()parseFloat() 函數(shù)。他們是有區(qū)別的:

parseInt() 將輸入值轉(zhuǎn)化為整數(shù);parseFloat() 如果輸入的是小數(shù)字符串(或具有可轉(zhuǎn)換小數(shù)的字符串)轉(zhuǎn)換為小數(shù),如果輸入是個(gè)整數(shù)字符串依然返回整數(shù)【坑7】:

console.log(parseFloat(" 6.2 "));     //6.2
console.log(parseFloat("10"));        //10

parseFloat() 可以轉(zhuǎn)換以“點(diǎn) + 數(shù)字”可是開頭的字符,其默認(rèn)整數(shù)部分為0;parseInt()不行,會(huì)返回NaN

console.log(parseInt(".21"));        //NaN
console.log(parseFloat(".21"));      //0.21
console.log(parseFloat(".0d"));      //0

parse***() 函數(shù)可以轉(zhuǎn)換以數(shù)字開頭(或開頭有正負(fù)號(hào))的所有字符串,遇到無法轉(zhuǎn)換的字母或符號(hào)停止轉(zhuǎn)換,返回已轉(zhuǎn)換的部分。對(duì)于不能轉(zhuǎn)換的字符串返回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()在沒有第二個(gè)參數(shù)時(shí)默認(rèn)以十進(jìn)制轉(zhuǎn)換數(shù)值,有第二個(gè)參數(shù)時(shí),以第二個(gè)參數(shù)為基數(shù)轉(zhuǎn)換數(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ī)范的任何符號(hào),不滿足此要求返回NaN, 對(duì)于滿足此要求的參數(shù),返回十進(jìn)制數(shù)值(整數(shù)或浮點(diǎn)數(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" 引導(dǎo)的十六進(jìn)制,但不支持 "0" 引導(dǎo)的八進(jìn)制【坑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 不支持負(fù)的十六進(jìn)制【坑9】:

console.log(Number("-0x20"));    //NaN

parseInt()Number() 都會(huì)忽略字符串首尾的空格,但parseInt() 不會(huì)忽略格式化字符,而Number() 會(huì)將格式化字符與空格一起忽略【坑10】

Number("  34
	 ");    //34
Number("  	34 ");     //34
Number("  3	
4 ");    //NaN,   不和開頭結(jié)尾的空格一起的格式化字符不會(huì)被忽略
parseInt("  	34 ");     //NaN

他們對(duì)空字符串的處理也不一樣【坑11】

Number("   ");     //0,   空格被忽略了,所以 "    " 等價(jià)于 ""
parseInt("   ");     //NaN,   空格被忽略了,所以 "    " 等價(jià)于 ""

進(jìn)制轉(zhuǎn)換不局限在十六進(jìn)制,js 會(huì)利用 0=9 和 A-Z 進(jìn)行最高36進(jìn)制的數(shù)制轉(zhuǎn)換:

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

對(duì)于 Number() 而言,不傳值和傳入 undefiend 是不一樣的【坑12】:

Number()          // -> 0
Number(undefined) // -> NaN

Number() 接受數(shù)值作為參數(shù),此時(shí)它既能識(shí)別負(fù)的十六進(jìn)制,也能識(shí)別0開頭的八進(jìn)制,返回值永遠(yuǎn)是十進(jìn)制值

Number(3);       //3
Number(3.15);    //3.15
Number(023);     //19
Number(0x12);    //18
Number(-0x12);   //-18
利用自動(dòng)類型轉(zhuǎn)換簡單的實(shí)現(xiàn)手動(dòng)類型轉(zhuǎn)換

這個(gè)部分利用一些簡單運(yùn)算會(huì)自己調(diào)用相關(guān)函數(shù),實(shí)現(xiàn)轉(zhuǎn)換可以簡化代碼。需要說明的是:_Douglas Crockford_ 在 《Javascript: The Good Parts》書中推薦使用這個(gè)方法轉(zhuǎn)換類型,而不是手寫函數(shù)調(diào)用,因?yàn)橐韵路椒▓?zhí)行效率更高。

// 任意值 => 字符串
var str = "" + 2;              //"2"
// 任意值 => 數(shù)字
var num = +"2";                //2
// 任意值 => 布爾
var bool = !!2;                 //true
// 數(shù)值取整數(shù)
var integer = ~~3.1415926;     //3,這個(gè)不涉及類型轉(zhuǎn)換
// 數(shù)值取小數(shù)
var decimals = 3.1415926 % 1;  //0.14159260000000007,這個(gè)不涉及類型轉(zhuǎn)換
對(duì)象類型和基本類型的關(guān)系

剛才我們解釋了基本變量的類型轉(zhuǎn)換,但沒有舉例一個(gè)基本變量和對(duì)象之間的轉(zhuǎn)換關(guān)系。在研究其關(guān)系之前,我們需要知道 new 關(guān)鍵字可以生成一個(gè)對(duì)象,new 后面的函數(shù)成為構(gòu)造函數(shù)。

var str = new String(32);           //String{...}
var num = new Number("22");         //Number{...}
var flag = new Boolean("hello");    //Boolean{...}
// 這里的參數(shù)也是會(huì)發(fā)生對(duì)應(yīng)類型轉(zhuǎn)換的,但得到的是對(duì)象
typeof str;       //object
typeof num;       //object
typeof flag;      //object

js中每一個(gè)對(duì)象,都是繼承自 Object 原型的(除非你手動(dòng)實(shí)現(xiàn)一個(gè)不繼承自 Object.prototype 的對(duì)象),這里我們暫不討論原型。對(duì)于 String(), Number() 和 Boolean() 得到的對(duì)象都具有一個(gè)名為`[[PrimitiveValue]]
`的屬性,改屬性是對(duì)象對(duì)應(yīng)的原始值,即基本類型變量。

默認(rèn)地,每個(gè)對(duì)象都有一個(gè)toString()方法和一個(gè)valueOf()方法,當(dāng)需要獲取對(duì)象原始值([[PrimitiveValue]])時(shí)候,調(diào)用valueOf()方法,需要獲取字符串時(shí)調(diào)用toString()方法。系統(tǒng)會(huì)在自動(dòng)類型轉(zhuǎn)換的時(shí)候調(diào)用他們,所以我們通常不需要手動(dòng)調(diào)用他們。

隱式類型轉(zhuǎn)換不僅僅使用 toString()valueOf(),比如基本類型轉(zhuǎn)換為對(duì)象依然是使用 new 關(guān)鍵字;而基本類型直接互相轉(zhuǎn)換使用其類型對(duì)應(yīng)函數(shù),比如字符串轉(zhuǎn)換為數(shù)字,使用 Number()。

隱式類型轉(zhuǎn)換(自動(dòng)類型轉(zhuǎn)換)

由于 js 是個(gè)弱類型語言,所以不是所有運(yùn)算都要求類型一致,Js 為了一些運(yùn)算可以執(zhí)行,使用了隱式類型轉(zhuǎn)換。也就是說,在一些計(jì)算中,系統(tǒng)會(huì)悄悄的完成類型轉(zhuǎn)換,比如以下情況:

(3.1415926).toFixed(2);      //3.14,  由于數(shù)字是基本類型不具備方法,所以自動(dòng)將其轉(zhuǎn)換為對(duì)象類型
3 + "23";                    //"323"   數(shù)值和字符串類型不同,運(yùn)算時(shí)將3轉(zhuǎn)換為字符串
5 == "5";                    //比較雙方類型不同,發(fā)生類型轉(zhuǎn)換。
"a" < "b";                   //這個(gè)更不一樣,因?yàn)樽址容^實(shí)際上是比較其 ASCII 碼的大小
數(shù)值加法和字符串連接

為什么 3 + "23"; 不把字符串轉(zhuǎn)成數(shù)字呢?只能說這是規(guī)定??!也可能是考慮到了字符串不一定都能轉(zhuǎn)成數(shù)字,而數(shù)字一定可以轉(zhuǎn)成字符串吧。其實(shí)廣義來講,只要不是兩個(gè)數(shù)字相加,都會(huì)吧不是字符串的那一個(gè)(或2個(gè))轉(zhuǎn)換為字符串然后連接,所以這個(gè)部分比較簡單,我們只看2個(gè)有特點(diǎn)的例子就好:

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

默認(rèn)的對(duì)象轉(zhuǎn)換為字符串使用了 toString 方法(實(shí)際上沒這么簡單,詳細(xì)見下文),而 toString 對(duì)于一般對(duì)象而言得到 [object 構(gòu)造函數(shù)名稱] 這樣的一個(gè)字符串。而數(shù)組和函數(shù)重寫了對(duì)象的 toString 方法,所以數(shù)組得到用逗號(hào)鏈接的元素序列字符串;函數(shù)得到其源代碼字符串。
不過要注意到,除了加號(hào)(+),其他符號(hào)都是默認(rèn)轉(zhuǎn)換為數(shù)值型:

"3" - 1  // -> 2
"3" == 3 //轉(zhuǎn)換后比較 3 == 3,而不是 "3" == "3"

但是,不巧的是這里又有例外了:就是 null 和 undefined??!

null 和 undefined

這里面首先需要解釋的一個(gè)坑就是 null 和 undefined 相關(guān)的比較問題:
1、 null/undefined 和字符串相加是轉(zhuǎn)換為字符串"null"/"undefined",和數(shù)字相加是,null 轉(zhuǎn)化為0,而 undefined 轉(zhuǎn)換為 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 除了和自己以及彼此以外和誰都不相等,比如下面這個(gè)例子,雖然 null 和 undefined 類型轉(zhuǎn)換都是 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

雖然它們彼此是相等的,但不嚴(yán)格相等

console.log(null === undefined);    // false

那么我們就有必要區(qū)分一下相等和嚴(yán)格相等。簡單來說:

相等:對(duì)于類型不同的兩個(gè)值而言,通過類型轉(zhuǎn)換可以相等的依然返回 true。

嚴(yán)格相等:不存在類型轉(zhuǎn)換,對(duì)于類型不同的兩個(gè)值直接返回 false。

這樣的解釋,簡單但不明了,因?yàn)槟銜?huì)遇到下面這個(gè)坑【坑15】:

if("0") {
  console.log("yes");
}

由于之前我們總結(jié)過,只有空字符串("")、null、undefined、0NaN 的布爾型是 false,其他的都是 true,所以上述代碼是可以輸出 ‘yes’ 的。但是我們執(zhí)行以下代碼:

console.log(false == "0");         // true
console.log(true == "0");         // false

到這里一臉懵逼!這簡直不能更坑!沒辦法,想搞明白這個(gè)事還得去看規(guī)范(7.2.13-7.2.14):

關(guān)于 ==!=

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__.

翻譯如下:

比較表達(dá)式 x == y (x 和 y 為值) 返回 true 或 __false__,執(zhí)行過程如下:

如果 Type(x) 和 Type(y) 相同,則

返回 x === y 的結(jié)果;

如果 x 是 null 并且 y 是 __undefined__,返回 __true__;

如果 x 是 undefined 并且 y 是 __null__,返回 __true__;

如果 Type(x) 是數(shù)值并且 Type(y) 是字符串,返回 x == ToNumber(y) 的結(jié)果;

如果 Type(x) 是字符串并且 Type(y) 是數(shù)值,返回 ToNumber(x) == y 的結(jié)果;

如果 Type(x) 是布爾型,返回 ToNumber(x) == y 的結(jié)果;

如果 Type(y) 是布爾型,返回 x == ToNumber(y) 的結(jié)果;

如果 Type(x) 是字符串、數(shù)值或 Symbol 并且 Type(y) 是對(duì)象, 返回 x == ToPrimitive(y) 的結(jié)果;

如果 Type(x) 是對(duì)象并且 Type(y) 是字符串、數(shù)值或 Symbol , 返回 ToPrimitive(x) == y 的結(jié)果;

返回 __false__;

關(guān)于規(guī)范中的 ToPrimitive() 用來將對(duì)象轉(zhuǎn)換為 原始值字符串 ,在規(guī)范7.1.1節(jié)中也有解釋,簡單來說:

ToPrimitive() 默認(rèn)將類型轉(zhuǎn)為原始值,但是對(duì)象可以通過@@toPrimitive 方法重新定義其行為。規(guī)范中只有 Date 對(duì)象和 Symbol 重新定義了該行為,Date 和 Symbol 的 ToPrimitive() 默認(rèn)得到 String 類型;

其次,ToPrimitive() 是依賴對(duì)象的 toString()valueOf() 方法的。對(duì)象轉(zhuǎn)換基本類型時(shí),先調(diào)用 valueOf(),如果 valueOf() 返回的不是基本類型,才調(diào)用 toString()。

如果toString()valueOf()都不是函數(shù)或是返回對(duì)象的函數(shù),則拋出 TypeError 異常。

詳見規(guī)范第7.1.1 節(jié) OrdinaryToPrimitive

關(guān)于 ===!==

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.

翻譯如下:

比較表達(dá)式 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 算法在對(duì)待 0NaN 存在差別

感覺上面這個(gè)注意又是個(gè)坑呀,博主趕緊去繼續(xù)查手冊(cè),發(fā)現(xiàn)這個(gè)函數(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__.

翻譯如下:

內(nèi)部的抽象比較操作 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 是嚴(yán)格相同的字符序列 (相同長度并且對(duì)應(yīng)下標(biāo)的字符編碼一致),返回 __true__; 否則,返回 __false__;

如果 Type(x) 是布爾型, 則

如果 x 和 y 都是 true 或者都是 __false__,返回 __true__; 否則,返回 __false__;

如果 Type(x) 是 symbol, 則

如果 x 和 y 是同一個(gè) Symbol,返回 __true__; 否則,返回 __false__;

如果 x 和 y 是同一個(gè)對(duì)象,返回 __true__; 否則,返回 __false__;

一下翻譯了這么多,至少不會(huì)感到暈了。js 就是這樣比較兩個(gè)值的,讀完這些內(nèi)容,是不是理解什么:

只要 === 為 __true__,== 一定為__true__;
只要 != 為__false__,!== 一定為__false__

比如下面再看一些奇怪的東西:

數(shù)組、對(duì)象比較

var a = [1];
var b = [2];
var c = a;
console.log(a == b);    //false, 因?yàn)椴皇峭粋€(gè)對(duì)象
console.log(a == c);    //true, 因?yàn)槭峭粋€(gè)對(duì)象
// 所以
console.log([] == []);             //false
console.log({} == {});             //false

比如這樣的代碼:

!![]       // -> true, 和 ==, ===, !=, !== 無關(guān)的類型轉(zhuǎn)換不會(huì)調(diào)用內(nèi)置的 toPrimitive, 這里調(diào)用 Boolean([]) 得到 true
[] == true // -> false, 這個(gè)通過轉(zhuǎn)換得到的是 0 == 1, 返回 false

以下兩個(gè)同理:

!!null        // -> false
null == false // -> false

關(guān)于 toString() 和 valueOf()

"J" + { toString: function() { return "S"; } };  // "JS"
2 * { valueOf: function() { return 3; } };       // 6

上面這個(gè)例子不深究的話,看上去似乎若合符節(jié),一個(gè)轉(zhuǎn)為字符串,調(diào)用了 toString,第二個(gè)轉(zhuǎn)換為數(shù)字,調(diào)用了 valueOf。實(shí)際上并不是這么簡單【坑16】:
根據(jù)之前那個(gè)表格,這里使用 toPrimitive 而再看 toPrimitive 的定義,除了 Date 和 Symbol 類型轉(zhuǎn)化為字符串,其余的對(duì)象都默認(rèn)轉(zhuǎn)化為數(shù)字,所以這里都是先調(diào)用 valueOf ,而對(duì)象的 valueOf 默認(rèn)返回對(duì)象本身(this),這個(gè)不符合規(guī)范,因?yàn)橐?guī)范要求不能返回對(duì)象,所以第一個(gè)表達(dá)式繼續(xù)調(diào)用toString 得到了 "S",而第二個(gè) valueOf 直接返回 3,沒有調(diào)用 toString。 為了說明這個(gè)邏輯,我們?cè)倏匆粋€(gè)例子,這次我做過多解釋了:

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

補(bǔ)充

即便如此,我們也可以用如下方法區(qū)別 +0 和 -0

function isNegativeZero(num) {
    return num === 0 && (1 / num < 0);
}

NaN 是唯一一個(gè)不等于自己的值【坑17】

var x = NaN;
console.log(x == x);           //false

這里有一個(gè)容易記混的地方

對(duì)于 + 運(yùn)算,字符串和數(shù)字相加是將數(shù)字轉(zhuǎn)換為字符串;而 == 運(yùn)算中是將字符串轉(zhuǎn)換為數(shù)字【坑18】

// 結(jié)合之前的【坑10】,就得到這么一讓人想罵娘的結(jié)果
console.log(" 	
 " == 0);      //true
toLocaleString 和 toString

toLocaleString 和 toString 方法同時(shí)存在,它定義了個(gè)性化的字符串轉(zhuǎn)換功能,對(duì)于對(duì)象而言 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}));  //一,二三四,五六七

日期類型:
得到一些地域性的時(shí)間表示

var date = new Date();
console.log(date.toString());           //Tue Apr 15 2014 11:50:51 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
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 轉(zhuǎn)換為字符串,再形成整體字符串。

關(guān)于 toLocaleString 的定義官方也是故意沒給出具體的實(shí)現(xiàn)細(xì)節(jié)【坑19】,這一點(diǎn)完全不能理解,所以這個(gè)方法用的場(chǎng)合也比較有限,這里不再贅述了。

Infinity

關(guān)于 Infinity 的數(shù)學(xué)運(yùn)算也比較簡單,如果學(xué)過數(shù)學(xué)中的極限的話很好理解,對(duì)于不定式運(yùn)算(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í)可以認(rèn)為大數(shù)在-9e15~9e15之間的計(jì)算可以認(rèn)為是沒有誤差的,即 MIN_SAFE_INTEGERMAX_SAFE_INTEGER。我們可以用Number.MAX_VALUENumber.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

對(duì)于計(jì)算值超過該范圍的數(shù)會(huì)被轉(zhuǎn)換為 Infinity 或 0,而且這個(gè)轉(zhuǎn)換不屬于類型轉(zhuǎn)換,而是編程語言處理了內(nèi)存溢出后的結(jié)果:

console.log(2e200 * 73.987e150);       //Infinity
console.log(-1e309);                   //-Infinity
console.log(4.18e-1000);               //0

而且數(shù)值會(huì)在浮點(diǎn)計(jì)數(shù)和科學(xué)技術(shù)法間自動(dòng)轉(zhuǎn)換,自動(dòng)轉(zhuǎn)換臨界是1e-6

console.log(0.000006);                 //0.000006
console.log(0.0000006);                //6e-7

但在精度范圍邊界,總會(huì)有一些問題【坑20】,姑且認(rèn)為這也是個(gè)坑吧,不過這樣的問題在其他編程語言中也普遍存在

console.log(1e200 + 1 === 1e200);  //true
console.log(0.1 + 0.2);                //0.30000000000000004
console.log(0.3 === 0.1 + 0.2);        //false

在比如下面這個(gè)

999999999999999  // -> 999999999999999
9999999999999999 // -> 10000000000000000

10000000000000000       // -> 10000000000000000
10000000000000000 + 1   // -> 10000000000000000
10000000000000000 + 1.1 // -> 10000000000000002
[] 和 {}

有了上面的基礎(chǔ),這個(gè)最坑的部分來了

console.log(+{});      //NaN
console.log(+[]);      //0

以上這兩個(gè)屬于轉(zhuǎn)換為數(shù)值,所以其值會(huì)調(diào)用 valueOf()(返回了對(duì)象),而后調(diào)用 toString(),前者得到 [object Object],后者得到 "", 再調(diào)用
Number() 得到結(jié)果,前者為 NaN,后者為 0。

理解了上面這個(gè)下面這個(gè)就不難了,都是轉(zhuǎn)換到字符串以后進(jìn)行字符串鏈接

console.log({} + []);  //[object Object]
console.log({} + {});  //[object Object][object Object]
console.log([] + []);  //""
console.log([] + {});  //[object Object]

但如果像下面這樣使用呢,我們?nèi)绾卫斫猓?/p>

console.log({}[]);     //[]
console.log([]{});     //"SyntaxError"(語法錯(cuò)誤)

首先我們需要明白這2個(gè)表達(dá)式是從左到右執(zhí)行的。這個(gè)地方我們可以很簡單的證明第一個(gè)表達(dá)式中的{},不是對(duì)象:

var obj = {};
console.log(obj[]);      //SyntaxError: Unexpected token ]

所以這里他是個(gè)表示代碼段的括號(hào)(注意塊級(jí)作用域是 ES6 提出了,在 ES5 中 {} 僅僅表示一個(gè)代碼段,如 if(exp){...} 中的 {}) ,這里這個(gè)代碼段里面什么也沒有,執(zhí)行完以后這個(gè) {} 就沒了,剩下一個(gè)數(shù)組 []。第二個(gè)表達(dá)式 []{} 從左到右先遇到一個(gè)數(shù)組,數(shù)組后面定義代碼段或者對(duì)象都是不符合語法的。

我們?cè)倏磶讉€(gè)賦值相關(guān)的,這里又是一個(gè)坑,居然 js 敢不限制賦值表達(dá)式的左值是標(biāo)識(shí)符或 Symbol【坑21】:

var [] = 1;            //"TypeError"(類型錯(cuò)誤)
var [] = "1" ;         //(正常執(zhí)行,由于字符串對(duì)象本身就是類數(shù)組對(duì)象)
var [] = {};           //"TypeError"(類型錯(cuò)誤)
var {} = [] ;          //(正常執(zhí)行,僅僅是指針指向從對(duì)象改變到了數(shù)組)

以上的2個(gè)錯(cuò)誤,都是 “TypeError: undefined is not a function”,很明顯,由于表達(dá)式不規(guī)范導(dǎo)致被js誤認(rèn)為是一個(gè)函數(shù),從而報(bào)錯(cuò)。

如果你理解了這些,不妨研究一下下面兩個(gè)表達(dá)式的值吧:

(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]    //"fail"
(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]                //"sb"

當(dāng)然還有更奇怪的,原因還是在于數(shù)組對(duì)象重寫了對(duì)象的 toString 方法【坑】:

[] == ![]  //true
{} == !{}  //false

下面這個(gè)輸入,博主一直很疑惑。把2行代碼分別輸入到 chrome 控制臺(tái),得到對(duì)應(yīng)結(jié)果。按規(guī)范的邏輯應(yīng)該輸出[object Object],但這個(gè)是為什么呢?

console.log({} + []);  //[object Object]
{}+[];    //0

原因是第二行中的{}被當(dāng)做了快作用域,而不是一個(gè)對(duì)象。

數(shù)組中的 null 和 undefined

數(shù)組中的 null 和 undefined 會(huì)在轉(zhuǎn)換為字符串時(shí)被看做空,也就是可以直接忽略。

"" == [null];                //true
"1,,3" == [1,undefined,3]    //true
大于號(hào)和小于號(hào)

大于和小于運(yùn)算的兩邊都會(huì)被轉(zhuǎn)化為數(shù)字,但字符串會(huì)安其 ASCII 碼或 UNICODE 碼把每個(gè)字符一次比較,得到 Boolean 值。比如:

"abc" > "abd";     //false
"aBc" > "abc";     //false
"093" < "15";      //true

但這里有一個(gè)奇怪的例子:

var a = {pro: 29};
var b = {pro: 43};

a < b;     //false
a == b;    //false
a > b;     //false

a <= b;    //true
a >= b;    //true

對(duì)于大于(等于)和小于(等于)號(hào),兩個(gè)對(duì)象 a 和 b 都被轉(zhuǎn)換成了字符串 "[object Object]",所以他們應(yīng)該是相等的,所以 a < ba > b 都是 false,而 a <= ba > = b 都是 true。但是 a == b 為 false。有了上面的知識(shí),就很好理解這個(gè)問題,a, b都是對(duì)象,所以不發(fā)生類型轉(zhuǎn)換,而兩個(gè)對(duì)象引用不同,結(jié)果為 false。

ES6 中的類型轉(zhuǎn)換和坑

ES6 中同樣帶入了許多坑,當(dāng)然這些坑不一定都是類型轉(zhuǎn)換導(dǎo)致的。

label 和 塊作用域

比如下面這段代碼,看似像定義對(duì)象屬性,但實(shí)際上是個(gè)塊級(jí)作用域,foo: 是一個(gè)的標(biāo)簽,用來給 break 指定跳轉(zhuǎn)的地方。

foo: {
  console.log("first");   //first
  break foo;
  console.log("second");   //不輸出
}

再看下面這個(gè):

由于前面的 a-g 都是標(biāo)簽,而后面的逗號(hào)表達(dá)式會(huì)返回最后一個(gè)表達(dá)式的值

a: b: c: d: e: f: g: 1, 2, 3, 4, 5;    // -> 5
解構(gòu)賦值

比如這樣定義變量,并且結(jié)構(gòu)賦值

let x, { x: y = 1 } = { x };    //由于 x 是 undefined 所以 y 取了默認(rèn)值 1
console.log(y);                 //1
模板字符串和對(duì)象中的類型轉(zhuǎn)換

對(duì)象在類似 EL 表達(dá)式中會(huì)被自動(dòng)轉(zhuǎn)換為字符串, 而對(duì)象的鍵值也會(huì)被默認(rèn)轉(zhuǎn)換為字符串(除了 Symbol 類型)

`${{Object}}`        //"[object Object]"
{ [{}]: {} }         // -> { "[object Object]": {} }
展開運(yùn)算符

由于字符串具有 iterator 就被展開了:

[...[..."..."]].length   //3   實(shí)際上得到的是[".", ".", "."]
try catch 語句

這個(gè)不算是 es6 的問題,不過我們也看一看:

try 中的 return 和 throw 會(huì)在有 finally 語句是中的 return 或 throw 覆蓋(這里的確是覆蓋,而不是前一個(gè) return 未執(zhí)行,詳細(xì)可以參看規(guī)范第13.15.8節(jié)。

(() => {
  var i = 0;
  try {
    return ++i;
  } finally {
    return ++i;
  }
})(); // 2

可見上面兩個(gè) return 都執(zhí)行了,但后一個(gè)把前一個(gè)覆蓋了。如果你認(rèn)為第一個(gè) return 沒執(zhí)行,而是執(zhí)行了自加,那你一定忘了程序執(zhí)行的最小單元是語句,而這里的 ++i 并不是一個(gè)完整的語句。

class 類
//這個(gè)代碼是不會(huì)報(bào)錯(cuò)的,系統(tǒng)會(huì)直接將 "class" 字符串作為對(duì)象的屬性名
const foo = {
  class: function() {}
};


var obj = new class {
  class() {}
};
console.log(obj);   //{},  和 var obj = new class{} 一樣
Symbol

這個(gè)類型轉(zhuǎn)換為字符串必須是顯示的,隱式轉(zhuǎn)換會(huì)出錯(cuò)

var s = Symbol("aabb");
String(s);        //"Symbol(aabb)"
s + "";           //TypeError: Cannot convert a Symbol value to a string
另一個(gè)更好的 typeOf 函數(shù)
function typeOf(val){
  return Object.prototype.toString.call(val).slice(8, -1);  //同樣可以很好的處理 null 和 undefined
}

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/97607.html

相關(guān)文章

  • JavaScript數(shù)據(jù)類型轉(zhuǎn)換

    摘要:本文主要介紹數(shù)據(jù)類型強(qiáng)制轉(zhuǎn)換和自動(dòng)轉(zhuǎn)換,自動(dòng)轉(zhuǎn)換是基于強(qiáng)制轉(zhuǎn)換之上。強(qiáng)制轉(zhuǎn)換主要指使用和三個(gè)函數(shù),手動(dòng)將各種類型的值,分布轉(zhuǎn)換成數(shù)字字符串或者布爾值。 前言 JavaScript是一門動(dòng)態(tài)語言,所謂的動(dòng)態(tài)語言可以暫時(shí)理解為在語言中的一切內(nèi)容都是不確定的。比如一個(gè)變量,這一時(shí)刻是個(gè)整型,下一時(shí)刻可能會(huì)變成字符串了。雖然變量的數(shù)據(jù)類型是不確定的,但是各種運(yùn)算符對(duì)數(shù)據(jù)類型是有要求的。如果運(yùn)算...

    blastz 評(píng)論0 收藏0
  • JavaScript數(shù)據(jù)類型轉(zhuǎn)換

    摘要:本文主要介紹數(shù)據(jù)類型強(qiáng)制轉(zhuǎn)換和自動(dòng)轉(zhuǎn)換,自動(dòng)轉(zhuǎn)換是基于強(qiáng)制轉(zhuǎn)換之上。強(qiáng)制轉(zhuǎn)換主要指使用和三個(gè)函數(shù),手動(dòng)將各種類型的值,分布轉(zhuǎn)換成數(shù)字字符串或者布爾值。 前言 JavaScript是一門動(dòng)態(tài)語言,所謂的動(dòng)態(tài)語言可以暫時(shí)理解為在語言中的一切內(nèi)容都是不確定的。比如一個(gè)變量,這一時(shí)刻是個(gè)整型,下一時(shí)刻可能會(huì)變成字符串了。雖然變量的數(shù)據(jù)類型是不確定的,但是各種運(yùn)算符對(duì)數(shù)據(jù)類型是有要求的。如果運(yùn)算...

    chaos_G 評(píng)論0 收藏0
  • JavaScript數(shù)據(jù)類型轉(zhuǎn)換

    摘要:本文主要介紹數(shù)據(jù)類型強(qiáng)制轉(zhuǎn)換和自動(dòng)轉(zhuǎn)換,自動(dòng)轉(zhuǎn)換是基于強(qiáng)制轉(zhuǎn)換之上。強(qiáng)制轉(zhuǎn)換主要指使用和三個(gè)函數(shù),手動(dòng)將各種類型的值,分布轉(zhuǎn)換成數(shù)字字符串或者布爾值。 前言 JavaScript是一門動(dòng)態(tài)語言,所謂的動(dòng)態(tài)語言可以暫時(shí)理解為在語言中的一切內(nèi)容都是不確定的。比如一個(gè)變量,這一時(shí)刻是個(gè)整型,下一時(shí)刻可能會(huì)變成字符串了。雖然變量的數(shù)據(jù)類型是不確定的,但是各種運(yùn)算符對(duì)數(shù)據(jù)類型是有要求的。如果運(yùn)算...

    Julylovin 評(píng)論0 收藏0
  • 【Vue原理】Vue源碼閱讀總結(jié)大會(huì) - 序

    摘要:扎實(shí)基礎(chǔ)幸好自己之前花了大力氣去給自己打基礎(chǔ),讓自己現(xiàn)在的基礎(chǔ)還算不錯(cuò)。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【Vue原理】Vue源碼閱讀總結(jié)大會(huì) - 序 閱讀源碼是需...

    Edison 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<