JavaScript強制類型轉換的背後操做

引子

強制類型轉換是JavaScript開發人員最頭疼的問題之一, 它常被詬病爲語言設計上的一個缺陷, 太危險, 應該束之高閣.函數

做爲開發人員, 每每會遇到或寫過涉及到類型轉換的代碼, 只是咱們歷來沒有意識到.ui

猜猜看😏:spa

  1. 做爲基本類型值, 爲何咱們可使用相關的屬性或方法? eg: 'hello'.charAt(0) (參見(#內置類型和內建函數的關係))
  2. a && (b || c) 這波操做咱們知道, 那麼 if (a && (b || c)), 這裏又作了哪些操做? (參見(#條件判斷))
  3. if (a == 1 && a== 2) { dosomething }, dosomething 居然執行了, 什麼鬼? (參見(#ToPrimitive))
  4. [] == ![] => true ?; false == [] => true ?; "0" == false => true ?(參見(#抽象相等))
  5. if (~indexOf('a')), 這波操做熟悉不? (參見(#隱式強制類型轉換))
  6. String , Number , Boolean 類型之間比較時, 進行的強制類型轉換又遵循了哪些規則? (參見(#抽象操做))

掌握了JavaScript強制類型轉換的抽象操做, 以上的問題都是小兒科.prototype

滑至文章底部可直接看栗子🌰. 掘金Markdown部分語法不支持, 可點此閱讀原文.設計

(如下內容是以《你不知道的JavaScript》強制類型轉換部分爲大綱, 並參照 ecma 規範 撰寫.)code


類型

內置類型

JavaScript 有七種內置類型. 空值: null, 未定義: undefined, 布爾值: boolean, 數字: number, 字符串: string, 對象: object, 符號: symbol. 除 對象:object, 爲複雜數據類型, 其它均爲基本數據類型.對象

內建函數

經常使用的內建函數: String(), Number(), Boolean(), Array(), Object(), Function(), RegExp(), Date(), Error(), Symbol().ip

內置類型和內建函數的關係

爲了便於操做基本類型值, JavaScript提供了封裝對象(內建函數), 它們具備各自的基本類型相應的特殊行爲. 當讀取一個基本類型值的時候, JavaScript引擎會自動對該值進行封裝(建立一個相應類型的對象包裝它)從而可以調用一些方法和屬性操做數據. 這就解釋了 問題 1 .開發

類型檢測

typeof => 基本類型的檢測均有同名的與之對應. null 除外, null是假值, 也是惟一一個typeof檢測會返回 'object' 的基本數據類型值.字符串

typeof null // "object"

let a = null;
(!a && typeof a === 'object') // true
複製代碼

複雜數據類型typeof檢測返回 'object', function(函數)除外. 函數因內部屬性[[Call]]使其可被調用, 其實屬於可調用對象.

typeof function(){} // "function"
複製代碼

Object.prototype.toString => 經過typeof檢測返回'object'的對象中還能夠細分爲好多種, 從內建函數就能夠知道.它們都包含一個內部屬性[[Class]], 通常經過Object.prototype.toString(...)來查看.

const str = new String('hello');
const num = new Number(123);
const arr = new Array(1, 2, 3);

console.log(Object.prototype.toString.call(str))
console.log(Object.prototype.toString.call(num))
console.log(Object.prototype.toString.call(arr))

// [object String]
// [object Number]
// [object Array]
複製代碼

抽象操做

在數據類型轉換時, 處理不一樣的數據轉換都有對應的抽象操做(僅供內部使用的操做), 在這裏用到的包括 ToPrimitive, ToString, ToNumber, ToBoolean (詳見 ecma 規範). 這些抽象操做定義了一些轉換規則, 不管是顯式強制類型轉換, 仍是隱式強制類型轉換, 無一例外都遵循了這些規則. 這裏就解釋了 問題 5問題 6 .

ToPrimitive

該抽象操做是將傳入的參數轉換爲非對象的數據. 當傳入的參數爲 Object 時, 它會調用內部方法[[DefaultValue]] 遵循必定規則返回非複雜數據類型, 規則 詳見( ecma 規範: DefaultValue). 故 ToString, ToNumber, ToBoolean在處理Object時, 會先通過ToPrimitive處理返回基本類型值.

[[DefaultValue]](hint)語法:

[[DefaultValue]]的規則會依賴於傳入的參數hint, ToString傳入的 hint 值爲 String, ToNumber傳入的 hint 值爲 Number.

  1. [[DefaultValue]](String) => 若 toString 可調用, 且 toString(Obj) 爲基本類型值, 則返回該基本類型值. 不然, 若 valueOf 可調用, 且 valueOf(Obj) 爲基本類型值, 則返回該基本類型值. 若以上處理還未獲得基本類型值, 則拋出 TypeError.
  2. [[DefaultValue]](Number) => 該規則正好和上規則調用 toString, valueOf 的順序相反. 若 valueOf 可調用, 且 valueOf(Obj) 爲基本類型值, 則返回該基本類型值. 不然, 若 toString 可調用, 且 toString(Obj) 爲基本類型值, 則返回該基本類型值. 若以上處理還未獲得基本類型值, 則拋出 TypeError.
  3. [[DefaultValue]]() => 未傳參時, 按照 hint值爲 Number 處理. Date 對象除外, 按照hint值爲 String 處理.

如今咱們就用以上的知識點來解釋 問題 3 是什麼鬼.

let i = 1;
Number.prototype.valueOf = () => {
    return i++
};
let a = new Number("0"); // 字符串強制轉換爲數字類型是不執行Toprimitive抽象操做的.
console.log('a_1:', a);
if(a == 1 && a == 2) {
    console.log('a==1 & a==2', 'i:', i);
}
// a==1 & a==2 i: 3
複製代碼

咱們改寫了內建函數 Number 原型上的 valueOf 方法, 並使得一個字符串轉換成 Number 對象, 第一次 Object 類型和 Number 類型作比較時, Object 類型將進行 ToPrimitive 處理(參見(#抽象相等)), 內部調用了 valueOf, 返回 2. 第二次一樣的處理方式, 返回 3.

ToString

該抽象操做負責處理非字符串到字符串的轉換.

type result
null "null"
undefined "undefined"
boolean true => "true"; false => "false"
string 不轉換
number (詳見 ecma 規範: ToString Applied to the Number Type)
Object 先經 ToPrimitive 返回基本類型值, 再遵循上述規則

ToNumber

該抽象操做負責處理非數字到數字的轉換.

type result
null +0
undefined NaN
boolean true => 1; false => 0
string (詳見 ecma 規範: ToNumber Applied to the String Type)
number 不轉換
Object 先經 ToPrimitive 返回基本類型值, 再遵循上述規則

常見的字符串轉換數字:

  1. 字符串是空的 => 轉換爲0.
  2. 字符串只包含數字 => 轉換爲十進制數值.
  3. 字符串包含有效的浮點格式 => 轉換爲對應的浮點數值.
  4. 字符串中包含有效的十六進制格式 => 轉換爲相同大小的十進制整數值.
  5. 字符串中包含除以上格式以外的符號 => 轉換爲 NaN.

ToBoolean

該抽象操做負責處理非布爾值到布爾值轉換.

type result
null false
undefined false
boolean 不轉換
string "" => false; 其它 => true
number +0, −0, NaN => false; 其它 => true
Object true

真值 & 假值

假值(強制類型轉換false的值) => undefined, null, false, +0, -0, NaN, "". 真值(強制類型轉換true的值) => 除了假值, 都是真值.

特殊的存在

假值對象 => documen.all 等. eg: Boolean(window.all) // false


隱式強制類型轉換

+/-/!/~

  1. +/- 一元運算符 => 運算符會將操做數進行ToNumber處理.
  2. ! => 會將操做數進行ToBoolean處理.
  3. ~ => (~x)至關於 -(x + 1) eg: ~(-1) ==> 0; ~(0) ==> 1; 在if (...)中做類型轉換時, 只有-1時, 才爲假值.
  4. +加號運算符 => 若操做數有String類型, 則都進行ToString處理, 字符串拼接. 不然進行ToNumber處理, 數字加法.

條件判斷

  1. if (...), for(;;;), while(...), do...while(...)中的條件判斷表達式.
  2. ? : 中的條件判斷表達式.
  3. ||&& 中的中的條件判斷表達式.

以上遵循 ToBoolean 規則.

||和&&

  1. 返回值是兩個操做數的中的一個(且僅一個). 首先對第一個操做數條件判斷, 若爲非布爾值則進行ToBoolean強制類型轉換.再條件判斷.
  2. || => 條件判斷爲true, 則返回第一個操做數; 不然, 返回第二個操做數. 至關於 a ? a : b;
  3. && => 條件判斷爲true, 則返回第二個操做數; 不然, 返回第一個操做數, 至關於 a ? b : a;

結合條件判斷, 解釋下 問題 2

let a = true;
let b = undefined;
let c = 'hello';
if (a && (b || c)) {
    dosomething()
}
a && (b || c) 返回 'hello', if語句中經Toboolean處理強制類型轉換爲true.
複製代碼

抽象相等

這裏的知識點是用來解釋 問題 4 的, 也是考驗人品的地方. 這下咱們要靠實力拼運氣.

  1. 同類型的比較.
    +0 == -0 // true
    null == null // true
    undefined == undefined // true
    NaN == NaN // false, 惟一一個非自反的值
    複製代碼
  2. nullundefined 的比較.
    null == undefined // true
    undefined == null // true
    複製代碼
  3. Number 類型和 String 類型的比較. => String 類型要強制類型轉換爲 Number 類型, 即 ToNumber(String) .(參見(#ToNumber))
  4. Boolean 類型和其它類型的比較. => Boolean 類型要強制類型轉換爲 Number 類型, 即 ToNumber(Boolean) .(參見(#ToNumber))
  5. Object 類型和 String 類型或 Number 類型. => Object 類型要強制轉換爲基本類型值, 即 ToPrimitive(Object) .(參見(#ToPrimitive))
  6. 其它狀況, false.

回頭看看 問題 4 中的等式. [] == ![], false == [], "0" == false. [] == ![] => ! 操做符會對操做數進行 ToBoolean 處理, [] 是真值, !true 則爲 false. 再遵循第 4 點, Boolean 類型通過 ToNumber 轉換爲 Number 類型, 則爲數值 0. 再遵循第 5 點, 對 [] 進行 ToPrimitive 操做, 前後調用 valueOf(), toString()直到返回基本類型, 直到返回 "". (先[].valueOf() => [], 非基本類型值; 再[].toString() => "", 基本類型值, 返回該基本類型值.). 再遵循第 3 點, 對 "" 進行 ToNumber 處理, 則爲數值 0. 到此, 0 == 0, 再遵循第 1 點(其實沒寫全😌, 詳見(詳見 ecma 規範: The Abstract Equality Comparison Algorithm)), return true, 完美!😏. false == [] => 同理 [] == ![]. "0" == false => 同理 [] == ![].

[] == ![]   // true
false == [] // true
"0" == false    // true
複製代碼

over!

相關文章
相關標籤/搜索