JavaScript 轉型觀念紀錄
原始型別(Primitive Type) - 不允許自由擴增屬性
- 但卻有屬性 & 方法可以使用,因為有原始型別包裹物件 (primititve type wrapper type) & 父元素的關係
- null, undefined 沒有原始型別包裹物件
- string, number, boolean
物件型別(Object Type) - 可以自由擴增屬性
原始型別包裹物件 (primititve type wrapper type)
- 主要用途
- 透過包裹物件的 toPrimitive 中的
valueOf()
&toString()
將原始型別產生物件型別的特性,自由擴增屬性與方法
- 透過包裹物件的 toPrimitive 中的
toPrimitive
- toPrimitive(input [, PreferredType])
- input
- 代入的值
- PreferredType
- 會依照設定的首選的類型,決定要先後呼叫
valueOf
與toString
的順序- valueOf()
- 取得物件內部的原始型別的值 (Primitive Value)
- toString()
- 取得物件內部的原始型別的值 (Primitive Value) 並轉換成字串型別
- 沒有提供這個值也就是預設情況,則會設定轉換的 hint 值為 “default”
- valueOf()
- input
- 轉換方式
- 如果 input 是原始資料類型,則直接回傳 input
- PreferredType 為 Number 首選類型時,優先使用
valueOf
,然後再呼叫toString
- PreferredType 為 String 首選類型時,優先使用
toString
,然後再呼叫valueOf
- 預設呼叫方式則是先呼叫
valueOf
再呼叫toString
,否則,拋出TypeError錯誤 - 兩個例外,一個是
Date
物件預設首選類型是字串(String),另一是Symbol物件
,它們覆蓋了原來的 PreferredType 行為
- 簡略規則
- undefined -> undefined(基本型別值,不轉換)
- null -> null(基本型別值,不轉換)
- boolean -> boolean(基本型別值,不轉換)
- number -> number(基本型別值,不轉換)
- string -> string(基本型別值,不轉換)
- object:使用 [[DefaultValue]] 內部方法,依照傳入的參數決定要使用 toString 或 valueOf 取得原始型別值
Date
- 首選類型為 String,它優先使用 toString 來進行轉換,最後字串連接運算
1 | 1 + (new Date()) // "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)" |
- Date 物件中的 valueOf 回傳值,需要使用一元加號(+),來強制轉換它為數字類
1 | +new Date() // 1480180751492 |
Object
- valueOf() 回傳值: 物件本身
- toString() 回傳值: “[object Object]” 字串值,不同的內建物件的回傳值是 “[object type]”字串,”type” 指的是物件本身的類型識別
1 | var a = {age:20} |
Array
- valueOf() 回傳值: 物件本身
- toString() 回傳值: 相當於用陣列值呼叫 join(‘,’) 所回傳的字串,也就是 [1,2,3].toString() 會是 “1,2,3”,這點要特別注意
1 | var b = ['hot','cold’] |
Function
- valueOf() 回傳值: 物件本身
- toString () 回傳值: 函式中包含的程式碼轉為字串值
1 | function Person (name,age){ |
轉型過程的抽象值運算
- toPrimitive 賦予 String, Number, Boolean 有 valueOf() & toString() 方法
JSON.stringify
- JSON 的字串化
- 無法轉為 JSON 字串的非法值有 undefined、function、symbol、具有循環參考(circular reference)的物件
1 | JSON.stringify(42) // "42" |
String
- 任何非字串的值被強制轉型為字串
1 | String([1,2,3]) // “1,2,3" |
Number
- 將非數字值當成數字來操作
1 | Number(undefined) // NaN |
Boolean
- Truthy 與 Falsy 的概念
1 | Boolean(false) // false |
自動轉型
- 物件型別來比較原始型別,所有的物件型別物件,一定會透過 toPrimitive 裡面的
valueOf()
或toString()
先轉成原始型別物件,然後才進行比較,這就是「自動型別轉換」
強制轉型(coercion)分為兩種
「明確的」強制轉型 (explicit coercion) 程式碼中刻意寫出來的型別轉換的動作
1 | String(123) // "123" |
「隱含的」強制轉型 (implicit coercion) 程式碼中沒有明確指出要轉換型別卻轉型的
1 | "0" == false; // true,字串轉數字、布林再轉數字 |
+
運算子是數字的相加,還是字串的串接?- 兩運算元的型別不同,當其中一方是字串時,
+
所代表的就是字串運算子,而會將另外一個運算元強制轉型為字串,並連接兩個字串 - [] + {} 中,[] 會轉為空字串,而 {} 會轉為字串 “[object Object]”
- {} + [] 中,{} 被當成空區塊而無作用, +[] 被當成強制轉型為數字 Number([]) (由於陣列是物件,中間會先使用 toString 轉成字空串,導致變成 Number(‘’))而得到 0
- 兩運算元的型別不同,當其中一方是字串時,
1 | const a = '1'; |
- 在什麼狀況下會隱含地將值強制轉為布林呢?
- if 述句中的條件判斷
- for 述句中的條件判斷,意即測試運算式的第二個子句
- while 與 do…while 中檢測條件是否成立的測試運算式
- 三元運算式 條件 ? 值1 : 值2 中的條件運算,意即測試運算式的第一個子句
- 邏輯運算子的 ||(or) 和 &&(and)左手邊的運算元會被當成測試運算式
1 | var a = 12345; |
比較運算
- 大家都知道
- 兩個等號 ( == ) 比對兩邊物件時,JavaScript 會自動轉型,然後才進行比對
- 怎麼自動轉型?
- 它是透過 toPrimitive 的 valueOf() 或 toString() 轉換
- 除此之外,可以自訂 valueOf() 或 toString()
- 所以結果
- 當 JavaScript 任意物件在進行 比較運算 時,都會先執行 valueOf() 或 toString() ,取回該物件相對應原始型別的值,看當下兩邊比較的是甚麼原始型別,然後再進行比較
- 若有任一值轉型後的結果不是字串,就使用 Number 的規則轉為數字,來做數字上的比較
種類
相等比較
- 可分為 ==(寬鬆相等)、===(嚴格相等)、!=(寬鬆不相等)、!==(嚴格不相等),主要差異是在做值的比較時是否會做強制轉型
- == 和 === 其實都會做型別的檢查,只是當面對型別不同時的反應是不一樣
1 | const a = '100'; |
- 簡略規則
- 型別相同,就會以同一型做比較,但要注意
- NaN 不等於自己(其實,NaN 不大於、不小於也不等於任何數字,所以當然也不等於它自己)
- +0、-0 彼此相等
- 物件(含 function 和 array)的相等是比較參考(reference),若參考相等才是相等
- 型別不同,將其中一個或兩個值先做強制轉型,再用型別相同的做比較
- 字串轉為數字
- 布林轉為數字
- null 與 undefined 在寬鬆相等下會強制轉型為彼此,因此是相等的但不等於其他值,
- 若比較的對象是物件,使用 valueOf()(優先)或 toString() 將物件取得基本型別的值,再做比較
- 而 != 和 !== 就是先分別做 == 和 === 再取否定(!)
- 型別相同,就會以同一型做比較,但要注意
1 | null === undefined // false |
大小比較
- <(小於)、 >(大於)、<=(小於等於)、>=(大於等於)
- 例如:a > b 表示比較 a 是否大於 b
簡略規則
- 若兩個運算元皆為字串時,就直接依照字典字母順序做比較
注意
- 由於規格只定義了 a < b 的演算法,因此 a > b 會以 b < a 的方式做比較
- 由於沒有「嚴格關係比較」(===),所以一定會遇到強制轉型的狀況
1 | // 由於 a 和 b 都不是字串且陣列沒有 valueOf,因此先用 toString 取得基型值,得到 a 為 '12'、b 為 '13',型別都是字串,接著做字母順序的比較 |
例外
- null 與 undefined 沒有其物件包裹形式,因此 Object(null) 與 Object(undefiend) 等同於 Object(),也就是空物件 {}
- Number(NaN) 得到 NaN,且 NaN 不等於自己
1 | var a = null; |
- object
- 在 JavaScript 裡,所有的物件都是不相等的,每一個都是獨立的物件實體,即便實作了 valueOf 或 toString 方法,還是無法對使用者定義物件進行任何相等比較運算
- 任何兩個物件相比都是 false
1 | {} = {} // {} |
- 兩個相同物件比較都是 true
1 | let a = {} |
運算子 || (or) 與 && (and)
- 在兩個運算元當中「選擇」其中一個運算元的值作為結果
- 簡略規則
- ||(or) 和 &&(and)會將第一個運算元做布林測試或強制轉型為布林以便測試
- 對 ||(or)來說,若結果為 true,則取第一個運算元為結果;若結果為 false,則取第二個運算元為結果
- 對 &&(and)來說,若結果為 true,則取第二個運算元為結果;若結果為 false,則取第一個運算元為結果
- 可應用於
- ||(or) 可用來設定變數的初始值
- &&(and)可用來執行「若特定條件成立,才做某件事情」,功能近似 if 述句
1 | const a = 'Hello World!' |
Symbol 強制轉型
- 屬性名現在可以有兩種類型,一種是原來就有的字串,另一種就是新增的 Symbol 類型
- 凡是屬性名屬於 Symbol 類型,可以保證不會與其他屬性名產生衝突
1 | let a = 33 |
- 簡略規則
- 在轉為 string 方面,將 Symbol 明確的強制轉型是允許的,但隱含的強制轉型是被禁止的,並且會丟出錯誤訊息
1 | var s1 = Symbol('Hello World'); |
- 在轉為 number 方面,無論是明確或隱含都是禁止的,並且會丟出錯誤訊息
1 | const n1 = Symbol(777); |
- 在轉為 boolean 方面,無論是明確或隱含都是可以的,並且結果都是 true
1 | const b1 = Symbol(true); |