必考知識點-JavaScript型別轉換(講原理)

蝸牛的北極星之旅發表於2019-11-08

一、型別轉換先說型別

型別轉換指將一種型別轉換為另一種型別,那我們首先來說說JavaScript中的型別。

1.1原始(Primitive)資料型別

  • Null
  • Undefined
  • Boolean
  • String
  • Number
  • Symbol
  • BigInt

BigInt是一種新的資料型別,用於當整數值大於Number資料型別支援的範圍時。這種資料型別允許我們安全地對大整數執行算術操作,表示高解析度的時間戳,使用大整數id,等等,而不需要使用庫。 重要的是要記住,不能使用Number和BigInt運算元的混合執行算術運算,需要通過顯式轉換其中的一種型別。 此外,出於相容性原因,不允許在BigInt上使用一元加號(+)運算子。

1.2引用(Object)資料型別

javaScript中內建了很多物件。

  • Array
  • Array
  • ArrayBuffer
  • AsyncFunction
  • Atomics
  • BigInt
  • BigInt64Array
  • BigUint64Array
  • Boolean
  • DataView
  • Date
  • Error
  • EvalError
  • Float32Array
  • Float64Array
  • Function
  • Generator
  • GeneratorFunction
  • Infinity
  • Int16Array
  • Int32Array
  • Int8Array
  • InternalError
  • Intl
  • Intl.Collator
  • Intl.DateTimeFormat
  • Intl.ListFormat
  • Intl.Locale
  • Intl.NumberFormat
  • Intl.PluralRules
  • Intl.RelativeTimeFormat
  • JSON
  • Map
  • Math
  • NaN
  • Number
  • Object
  • Promise
  • Proxy
  • RangeError
  • ReferenceError
  • Reflect
  • RegExp
  • Set
  • SharedArrayBuffer
  • String
  • Symbol
  • SyntaxError
  • TypeError
  • TypedArray
  • URIError
  • Uint16Array
  • Uint32Array
  • Uint8Array
  • Uint8ClampedArray
  • WeakMap
  • WeakSet
  • WebAssembly
  • decodeURI()
  • decodeURIComponent()
  • encodeURI()
  • encodeURIComponent()
  • escape()
  • eval()
  • globalThis
  • isFinite()
  • isNaN()
  • null
  • parseFloat
  • parseInt
  • undefined
  • unescape()
  • uneval()

詳情請參考MDN

大家不要看javaScript的內建物件這麼多,轉換時只需要把這麼統統當做一個型別引用型別進行轉換就行,在javaScript內部中轉換也不會考慮這麼多。

二、自動裝箱

為了方便操作基本資料型別, ECMAScript還提供了三個特殊的引用型別,基本包裝型別,String、Boolean、Number。有了這三個型別,在需要的時候,原始型別會自動轉換成相應的包裝物件(這個過程叫自動裝箱)。自動裝箱就是臨時建立一個包裝物件,將原始型別的值封裝起來,以便呼叫包裝物件的函式。但是原來那個變數的值不會有任何變化!

var s1 = "some text";
var s2 = s1.substring(2);
複製程式碼

字串是基本資料型別,為撒能呼叫方法了,這其實在後臺進行了一系列的操作

  1. 建立String型別的一個例項
  2. 在例項上呼叫指定的方法。
  3. 銷燬這個例項。
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;
複製程式碼

當然,你可以將Boolean 、Number 、String 這三個函式當作建構函式來使用,通過手動new包裝類來裝箱(得到包裝物件):

// 手動裝箱
var s1 = new String("some text");  
s1.toUpperCase();
typeof s1;  
// "object"
複製程式碼

三、型別轉換的規則

必考知識點-JavaScript型別轉換(講原理)

必考知識點-JavaScript型別轉換(講原理)

四、內部用於實現型別轉換的4個函式

4.1 ToPrimitive ( input [ , PreferredType ] )

// ECMA-262, section 9.1, page 30. Use null/undefined for no hint,
// (1) for number hint, and (2) for string hint.
function ToPrimitive(x, hint) {
    // Fast case check.
    if (IS_STRING(x)) return x;
    // Normal behavior.
    if (!IS_SPEC_OBJECT(x)) return x;
    if (IS_SYMBOL_WRAPPER(x)) throw MakeTypeError(kSymbolToPrimitive);
    if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT;
    return (hint == NUMBER_HINT) ? DefaultNumber(x) : DefaultString(x);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultNumber(x) {
    if (!IS_SYMBOL_WRAPPER(x)) {
        var valueOf = x.valueOf;
        if (IS_SPEC_FUNCTION(valueOf)) {
            var v = % _CallFunction(x, valueOf);
            if (IsPrimitive(v)) return v;
        }
        var toString = x.toString;
        if (IS_SPEC_FUNCTION(toString)) {
            var s = % _CallFunction(x, toString);
            if (IsPrimitive(s)) return s;
        }
    }
    throw MakeTypeError(kCannotConvertToPrimitive);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultString(x) {
    if (!IS_SYMBOL_WRAPPER(x)) {
        var toString = x.toString;
        if (IS_SPEC_FUNCTION(toString)) {
            var s = % _CallFunction(x, toString);
            if (IsPrimitive(s)) return s;
        }
        var valueOf = x.valueOf;
        if (IS_SPEC_FUNCTION(valueOf)) {
            var v = % _CallFunction(x, valueOf);
            if (IsPrimitive(v)) return v;
        }
    }
    throw MakeTypeError(kCannotConvertToPrimitive);
}
複製程式碼

ToPrimitive將input裝換為基本資料型別,PreferredType要麼不傳,要麼是number、string。

4.1.1 PreferredType為number

  1. 如果input本身就是原始型別,直接返回input。
  2. 呼叫input.valueOf(),如果結果是原始型別,則返回這個結果。
  3. 呼叫input.toString(),如果結果是原始型別,則返回這個結果。
  4. 丟擲TypeError異常。

4.1.2 PreferredType為string

  1. 如果input本身就是原始型別,直接返回input。
  2. 呼叫input.toString(),如果結果是原始型別,則返回這個結果。
  3. 呼叫input.valueOf(),如果結果是原始型別,則返回這個結果。
  4. 丟擲TypeError異常。

4.1.3 PreferredType不傳入

  1. 如果input是內建的Date型別,PreferredType 視為String
  2. 否則PreferredType 視為 Number。

來看看這道網上的面試題

({}) + 1
複製程式碼

+號操作符,只有當左右兩邊的型別相同(都為string或者number)是才進行操作。所以會經歷如下步驟:

  1. {}和1都會呼叫ToPrimitive,1原始型別直接返回。
  2. {}內部呼叫DefaultNumber,使用valueOf方法,返回object。
  3. 在呼叫toString方法,返回[object, object]。
  4. 所以最後的結果就是[object, object]1。

這一類轉換換湯不換藥,轉換規則都是這樣的。

4.2 ToBoolean ( argument )

必考知識點-JavaScript型別轉換(講原理)

4.3 ToNumber( argument )

必考知識點-JavaScript型別轉換(講原理)

4.4 ToString( argument )

必考知識點-JavaScript型別轉換(講原理)

來源:ECMA-262草案/ 2019年11月7日 ECMAScript®2020語言規範

五、隱式型別裝換

在執行過程中當js內部期望得到某種型別的值,而實際在那裡的值是其他的型別,就會發生隱式型別轉換。系統內部會自動呼叫我們前面說ToBoolean ( argument )、ToNumber ( argument )、ToString ( argument ),嘗試轉換成期望的資料型別。

5.1 期望得到boolean的值

if ( !undefined && !null && !0 && !NaN && !'') {
  // xxxx
} 
複製程式碼

因為在if的括號中,js期望得到boolean的值,所以對括號中每一個值都使用ToBoolean ( argument ),將它們轉化成boolean。

5.2 期望得到number的值

3 * { valueOf: function () { return 5 } }; 
複製程式碼

因為在乘號的兩端,js期望得到number型別的值,所以對右邊的那個物件使用ToNumber ( argument ),得到結果5,再與乘號左邊的3相乘。

5.3 加號有別於其他運算子

  • 如果有一邊是字串,就把另外一邊也轉換為字串
  • 如果一方不是字串或者資料,就轉換為資料或者字串

處了加號運算子,其他運算子,只要其中一方資料,那麼另一方就被轉換為數字

六、顯示型別裝換

手動呼叫Boolean(value)、Number(value)、String(value)完成的型別轉換。

Boolean('some text');  //  true
Number("2019");  //  2019
String({a: 1});  //  "[object Object]"
複製程式碼

前面兩個型別轉換沒有什麼好解釋的,我們看看最後一個String({a: 1});在內部發生的時候

  1. 執行轉換String({a: 1})。
  2. 執行內部的ToString({a: 1})。
  3. {a: 1}不是原始型別,執行ToPrimitive({a: 1}, hint string)。
  4. 呼叫toString方法,返回"[object, object]"。
  5. 執行ToString("[object, object]"),返回"[object, object]"。

參考文章:

相關文章