作者: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
複製程式碼
null
和 undefined
看起來很相似,但它們是兩個截然不同的型別,以至於開發人員仍不確定要使用哪個型別。
可以使用 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 內建物件的一部分,它反映了一些基本型別:String
、Number
、Boolean
和Object
。這些內建元件可用於型別之間的轉換。轉換後,我們們可以拼接兩個變數
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 的構建塊:String
、Number
、Boolean
、Null、Undefined
、Object
和Symbol
。它們都是大寫的,這種風格甚至出現在ECMAScript規範中。但是除了這些基本型別之外,還有一些映象原語的雙胞胎:內建物件。例如,String
型別有一個等效的 String
,它以兩種方式使用。如果像函式那樣呼叫(通過傳遞引數),它會將任何值轉換成字串:
var someValue = 555;
String(someValue);
"555"
複製程式碼
如果使用 String
作為 new
的建構函式,那麼結果就是String
型別的物件
var someValue = 555;
var newString = new String(someValue);
複製程式碼
這種方式值得在控制檯中檢視物件,看看它與“普通”字串有何不同
可以使用 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 };
複製程式碼
當需要轉換某些內容時,可以像使用函式一樣使用 Boolean
、String
和 Number
,。
總結
JS 中有七個構建塊,即 String
、Number
、Boolean
、Null
、Undefined
、Object
和Symbol
,這些也稱為基本型別。
JS 開發人員可以使用算術和比較操作符操作這些型別。但是我們們需要特別注意加法運算子+
和抽象比較運算子 ==
,它們本質上傾向於在型別之間進行轉換。
這種 JS 中的隱式轉換稱為型別強制轉換,並在ECMAScript規範中定義。建議在程式碼中始終使用嚴格的比較操作符===
代替 ==
。
作為一種最佳實踐,當你打算在一種型別和另一種型別之間進行轉換時,一定要弄清楚彼此之間的型別。為此,JS 有一堆內建物件,它們基本型別的一種對映:String
、Number
、Boolean
。這些內建函式可用於顯式地在不同型別之間進行轉換。
思考題:
-
44 - "alex"
的輸出結果是? -
44 + "alex"
的輸出結果是?為啥? -
"alex" + { name: "Alex" }
的輸出結果是 ? -
["alex"] == "alex" 輸出結果是啥?為什麼呢?
-
undefined == null
輸出結果是啥?為什麼呢?
程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
交流(歡迎加入群,群工作日都會發紅包,互動討論技術)
乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。
因為篇幅的限制,今天的分享只到這裡。如果大家想了解更多的內容的話,可以去掃一掃每篇文章最下面的二維碼,然後關注我們們的微信公眾號,瞭解更多的資訊和有價值的內容。
每次整理文章,一般都到2點才睡覺,一週4次左右,挺苦的,還望支援,給點鼓勵