【JS 口袋書】第 7 章:JS 中的型別轉換與比較

前端小智發表於2019-10-17

作者:valentinogagliardi

譯者:前端小智

來源:github


為了保證的可讀性,本文采用意譯而非直譯。

JS 中的基本型別

JS 有 7 種基本型別,分別如下:

  • String
  • Number
  • Boolean
  • Null
  • Undefined
  • Object
  • Symbol (ES6)

布林值表示的值可以是 true ,也可以是 false。另一方面,null 是故意缺少一個值。null 通常被賦值給一個變數,用來表示變數過後會被賦予值。

var maybe = null;
複製程式碼

然後是 undefined,表示是一個變數沒有任何附加項:

var name;
console.log(name)
undefined
複製程式碼

nullundefined 看起來很相似,但它們是兩個截然不同的型別,以至於開發人員仍不確定要使用哪個型別。

可以使用 typeof 操作符來檢視變數的型別:

typeof "alex"
"string"
複製程式碼

number 型別:

typeof 9
"number"
複製程式碼

boolean 型別:

typeof false
"boolean"
複製程式碼

undefined 型別:

typeof undefined
"undefined"
複製程式碼

null 型別

typeof null
"object"
複製程式碼

這個結果有點奇怪。null 看起來像一個物件,但實際上它是JS的一個歷史錯誤,自該語言誕生以來就一直存在。由於這些原因,JS 一直名聲不佳。null 只是其中的一例。另外,一種型別和另一種型別之間的轉換有一些奇怪的規則。

先給大家介紹一下背景。各位先用 Python 做一個例子。Python 中的以下指令

'hello' + 89
複製程式碼

這樣會得到一個明確的錯誤:

TypeError: can only concatenate str (not "int") to str
複製程式碼

在 JS 中完全沒有問題:

'hello' + 89
複製程式碼

結果:

"hello89"
複製程式碼

更加奇怪是直接加一個陣列:

'hello' + []
複製程式碼

結果:

'hello'
複製程式碼

再來:

'hello' + [89]
複製程式碼

結果:

"hello89"
複製程式碼

看起來這種轉換背後有某種邏輯,甚至還可以有更加複雜的陣列結構:

'hello' + [89, 150.156, 'mike']
複製程式碼

結果:

"hello89,150.156,mike"
複製程式碼

這兩行 JS 足以讓 Java 開發人員望而卻步。但是 JS 中的這種行為是100%故意的。因此,有必要研究一下 JS 中隱式轉換(也稱為型別強制轉換)。

當數字變成字串

一些程式語言有一個稱為型別轉換的概念,這意味著:如果我們們想將一個型別轉換成另一種型別,那麼必須使轉換明確。在 JS 中也有提供這種方法。考慮以下示例

var greet = "Hello";
var year = 89;
複製程式碼

如果想進行顯式轉換,可以在程式碼中用 toString() 方法:

var greet = "Hello";
var year = 89;

var yearString = year.toString()
複製程式碼

或者使用 String

var greet = "Hello";
var year = 89;

var yearString = String(year)
複製程式碼

String 是JS 內建物件的一部分,它反映了一些基本型別:StringNumberBooleanObject。這些內建元件可用於型別之間的轉換。轉換後,我們們可以拼接兩個變數

greet + yearString;
複製程式碼

但是除了這種顯式轉換之外,在 JS 中還有一種微妙的機制,稱為隱式轉換,由 JS 引擎提供。

'hello' + 89
複製程式碼

結果:

"hello89"
複製程式碼

但是這種轉換背後的邏輯是什麼? 你可能會驚訝地發現,如果 JS 中的加法運算子+中的一個是字串,則會自動將兩個運算元中的任何一個轉換為字串!

更令人驚訝的是,這個規則在ECMAScript規範中已經固定下來了。第11.6.1節定義了加法運算子的行為,在這裡總結一下:

加法運算子(+) 如果 x 是字串或者 y 是字串那麼返回 ToString(x) 後面跟 ToString(y)

這種把戲只對數字有效嗎? 不是,陣列和物件一樣的,跑不掉:

'hello' + [89, 150.156, 'mike']
複製程式碼

結果:

"hello89,150.156,mike"
複製程式碼

物件怎樣:

'hello' + { name: "Jacopo" }
複製程式碼

結果:

"hello[object Object]"
複製程式碼

為了弄清,咋肥事,可以通過將物件轉換為字串來進行快速測試:

String({ name: "Jacopo" })
複製程式碼

結果:

"[object, Object]"
複製程式碼

但還有另一個問題:乘法、除法和減法的情況又是腫麼樣的?

我不是一個數字!

我們們看到加法運算子在至少一個運算元是字串時,是如何將運算元轉換成字串。但是其他的算術運算子呢?

操作符 描述
+
++ 自增
*
** 指數 (es6)
-
-- 自減
/
% 取餘

如果對不是數字的型別使用其中一個操作符(+除外),那麼就得到了一種特殊型別的 : NaN

89 ** "alex"
複製程式碼

結果:

NaN
複製程式碼

NaN 表示不是數字,任何失敗的算術運算,如下面的程式碼所示:

var obj = { name: "Jacopo" } % 508897
複製程式碼

結果:

console.log(obj)
NaN
複製程式碼

注意與NaN結合的 typeof。這個程式碼看起來沒問題:

typeof 9 / "alex"
NaN
複製程式碼

那下面呢?

var strange = 9 / "alex"
複製程式碼

再使用 typeof:

typeof strange
"number"
複製程式碼

NaN 被分配給一個變數時,它就變成了number,這就引出了一個新問題。我如何檢查一些變數是否是 NaN? ES6 中有一個名為 isNaN() 的新方法:

var strange = 9 / "alex"
isNaN(strange)
true
複製程式碼

接著來看看 JS 中的比較運算子,它們和算術運算子一樣奇怪。

相等還是不等

JS 中有兩大類比較運算子。首先是所說的**“弱比較”**。它是一個抽象的比較運算子(雙等號):==。然後還有一個“強比較”:===,又名 嚴格比較運算子。他倆兄弟的行為方式不一樣,來看看一些例子。

首先,如果我們們用兩個操作符比較兩個字串,倆兄弟得到一致的結果:

"hello" == "hello"
true

"hello" === "hello"
true
複製程式碼

看起來很 nice,現在來比較兩種不同的型別,數字和字串。

首先是“強比較”

"1" === 1
false
複製程式碼

很明顯,字串 1 等於數字 1。弱比較又是怎麼樣?

"1" == 1
true
複製程式碼

true表示這兩個值相等。這種行為與我們們前面看到的隱式轉換有關。原來,抽象比較操作符會在比較型別之前自動轉換型別。這是一個摘要:

抽象等式比較演算法 比較 x == y 是這樣執行的:如果 x 是字串,y 是數字,返回比較的結果ToNumber(x) == y

說白了就是:如果第一個運算元是字串,第二個運算元是數字,那麼將第一個運算元轉換為數字。

有趣的是,JS 規範中充滿了這些瘋狂的規則,我強烈建議對此進行更深入的研究。當然現在都建議使用強比較。

嚴格相等比較的規範指出,在將值與===進行比較之前,不會進行自動轉換。在程式碼中使用嚴格的相等比較可以避免愚蠢的錯誤。

基本資料型別與物件

我們們已經看到了 JS 的構建塊:StringNumberBoolean、Null、UndefinedObjectSymbol。它們都是大寫的,這種風格甚至出現在ECMAScript規範中。但是除了這些基本型別之外,還有一些映象原語的雙胞胎:內建物件。例如,String 型別有一個等效的 String ,它以兩種方式使用。如果像函式那樣呼叫(通過傳遞引數),它會將任何值轉換成字串:

var someValue = 555;

String(someValue);

"555"
複製程式碼

如果使用 String 作為 new 的建構函式,那麼結果就是String型別的物件

var someValue = 555;

var newString = new String(someValue);
複製程式碼

這種方式值得在控制檯中檢視物件,看看它與“普通”字串有何不同

【JS 口袋書】第 7 章:JS 中的型別轉換與比較

可以使用 typeof 來確認它確實是一個物件:

typeof newString
"object"
複製程式碼

基本型別的 Number 也有一個內建物件 Number,它可以(幾乎)將任何值轉換為數字:

var someValue = "555";

Number(someValue);

555;
複製程式碼

我說的幾乎是因為在試圖轉換無效的“數字”時得到 NaN

var notValidNumber = "aa555";

Number(notValidNumber);

NaN;
複製程式碼

在建構函式形式Number中使用時,將返回number型別的新物件:

var someValue = "555";

new Number(someValue);

Number {555}
複製程式碼

內建物件 Boolean 以將任何值轉換成布林值:

var convertMe = 'alex';

Boolean(convertMe)

true
複製程式碼

用建構函式的方式會返回一個物件:

var convertMe = 'alex';

typeof new Boolean(convertMe)

"object"
複製程式碼

內建的 Object 行為也一樣:

Object('hello'); // String {"hello"}

Object(1); // Number{1}
複製程式碼

如果在沒有引數的情況下呼叫,它將返回一個空物件

Object()

{}
複製程式碼

如果作為建構函式呼叫,則返回一個新物件

new Object({
  name: "Alex",
  age: 33
});

{name: "Alex", age: 33}
複製程式碼

此時,你可能會問自己:什麼時候可以使用內建物件,什麼時候應該使用基本型別初始化值? 根據經驗,當你只需要一個簡單的型別時,應該避免建構函式呼叫:

// 不要這麼用
var bool = new Boolean("alex");
var str = new String('hi');
var num = new Number(33);
var strObj = new Object('hello')
複製程式碼

除了不實用之外,這種形式還會帶來效能損失,因為這種方式每次都會建立一個新物件。 最後但同樣重要的是,當我們們想要一個簡單的字串或數字時,建立一個物件是沒有意義的。所以下面的形式是首選的

// ok 的
var bool = true
var str = 'hi';
var num = 33;
var obj = { name: "Alex", age: 33 };
複製程式碼

當需要轉換某些內容時,可以像使用函式一樣使用 BooleanStringNumber,。

總結

JS 中有七個構建塊,即 StringNumberBooleanNullUndefinedObjectSymbol,這些也稱為基本型別。

JS 開發人員可以使用算術和比較操作符操作這些型別。但是我們們需要特別注意加法運算子+和抽象比較運算子 ==,它們本質上傾向於在型別之間進行轉換。

這種 JS 中的隱式轉換稱為型別強制轉換,並在ECMAScript規範中定義。建議在程式碼中始終使用嚴格的比較操作符===代替 ==

作為一種最佳實踐,當你打算在一種型別和另一種型別之間進行轉換時,一定要弄清楚彼此之間的型別。為此,JS 有一堆內建物件,它們基本型別的一種對映:StringNumberBoolean。這些內建函式可用於顯式地在不同型別之間進行轉換。

思考題:

  1. 44 - "alex" 的輸出結果是?

  2. 44 + "alex" 的輸出結果是?為啥?

  3. "alex" + { name: "Alex" } 的輸出結果是 ?

  4. ["alex"] == "alex" 輸出結果是啥?為什麼呢?

  5. undefined == null 輸出結果是啥?為什麼呢?

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

原文:github.com/valentinoga…

交流(歡迎加入群,群工作日都會發紅包,互動討論技術)

乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。

github.com/qq449245884…

因為篇幅的限制,今天的分享只到這裡。如果大家想了解更多的內容的話,可以去掃一掃每篇文章最下面的二維碼,然後關注我們們的微信公眾號,瞭解更多的資訊和有價值的內容。

clipboard.png

每次整理文章,一般都到2點才睡覺,一週4次左右,挺苦的,還望支援,給點鼓勵

【JS 口袋書】第 7 章:JS 中的型別轉換與比較

相關文章