1. 程式人生 > >JavaScript自動型別轉換

JavaScript自動型別轉換

我們都知道,JavaScript是弱型別語言,在宣告一個變數時,我們無法明確宣告其型別,變數的型別根據其實際值來決定,而且在執行期間,我們可以隨時改變這個變數的值和型別,另外,變數在執行期間參與運算時,在不同的運算環境中,也會進行相應的自動型別轉換。

自動型別轉換一般是根執行環境操作符聯絡在一起的,是一種隱式轉換,看似難以捉摸,其實是有一定規律性的,大體可以劃分為:轉換為字串型別轉換為布林型別轉換為數字型別。今天我們就介紹一下這幾種轉換機制。

轉換為字串型別

加號“+”作為二元操作符(binary)並且其中一個運算元為字串型別時,另一個運算元將會被無條件轉為字串型別:

// 基礎型別

var foo = 3 + '';            // "3"

var foo = true + '';         // "true"

var foo = undefined + '';    // "undefined"

var foo = null + '';         // "null"

// 複合型別 var foo = [1, 2, 3] + ''; // "1,2,3" var foo = {} + ''; // "[object Object]" // 重寫valueOf()和toString()
var o = { valueOf: function() { return 3; }, toString: function() { return 5; } }; foo = o + ''; // "3" o = { toString: function() { return 5; } }; foo = o + ''; // "5"

  • 對於基礎型別,會直接轉為與字面量相一致的字串型別
  • 對於複合型別,會先試圖呼叫物件的valueOf()方法,如果此方法返回值是引用型別,則接著再呼叫其toString()方法,最後將返回值轉為字串型別。
  • 不同型別物件的valueOf()方法和toString()方法的返回值
    物件 valueOf()返回值 toString()返回值
    Array 返回陣列物件本身。 返回一個字串,該字串由陣列中的每個元素的 toString() 返回值經呼叫 join() 方法連線(由逗號隔開)組成。
    Boolean 布林值。 ‘true’/’false’
    Date 儲存的時間是從 1970 年 1 月 1 日午夜開始計的毫秒數 UTC。 返回一個美式英語日期格式的字串,如’Mon Sep 28 1998 14:36:22 GMT-0700‘
    Function 函式本身。 返回一個表示函式原始碼的字串
    Number 數字值。 ‘12’
    Object 物件本身。這是預設情況。 預設情況下,toString()方法被每個Object物件繼承。如果此方法在自定義物件中未被覆蓋,toString() 返回 “[object type]”,其中type是物件的型別。
    String 字串值。 字串值。
    Error 沒有 valueOf 方法。 返回一個指定的錯誤物件(Error object)的字串表示

    轉為布林型別(to boolean)

    數字轉為布林型別(from number)

    當數字在邏輯環境中執行時,會自動轉為布林型別。0和NaN會自動轉為false,其餘數字都被認為是true,程式碼如下:

    // 0和NaN為false,其餘均為true
    
    if (0) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    if (-0) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    if (NaN) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    // 其餘數字均為true
    
    if (-3) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    
    if (3) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }

    從上面的程式碼中可以看出,非0負值也會被認為是true,這一點需要注意。

    字串轉為布林型別(from string)

    和數字類似,當字串在邏輯環境中執行時,也會被轉為布林型別。空字串會被轉為false,其它字串都會轉為true

    // 空字串為false
    
    if ('') {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    // 其他字串均為true
    
    if ('0') {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    if ('false') {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }

    undefined和null轉為布林型別

    undefined和null在邏輯環境中執行時,都被認為是false,看下面程式碼:

    // undefined和null都為false
    
    if (undefined) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    
    if (null) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }

    物件轉為布林型別(from object)

    當物件在邏輯環境中執行時,只要當前引用的物件不為空,都會被認為是true。如果一個物件的引用為null,根據上面的介紹,會被轉換為false。雖然使用typeof檢測null為”object”,但它並不是嚴格意義上的物件型別,只是一個物件空引用的標識。

    另外,我們這裡的邏輯環境不包括比較操作符(==),因為它會根據valueOf()和toString()將物件轉為其他型別。

    現在我們來看一下物件型別的示例:

    // 字面量物件
    var o = {
        valueOf: function() {
            return false;
        },
        toString: function() {
            return false;
        }
    };
    
    if (o) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    // 函式
    var fn = function() {
        return false;
    };
    
    if (fn) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    // 陣列
    var ary = [];
    
    if (ary) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    // 正則表示式
    var regex = /./;
    
    if (regex) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }

    可以看到,上面的物件都被認為是true,無論內部如何定義,都不會影響最終的結果。

    正是由於物件總被認為是true,使用基礎型別的包裝類時,要特別小心:

    // 以下包裝物件都被認為是true
    
    if (new Boolean(false)) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    if (new Number(0)) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    if (new Number(NaN)) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }
    
    if (new String('')) {
        console.log('true');    // output: true
    } else {
        console.log('false');
    }

    根據們上面介紹的,它們對應的基礎型別都會被轉為false,但使用包裝類例項的時候,引擎只會判斷其引用是否存在,不會判斷內部的值,這一點初學者需要多多注意。當然我們也可以不使用new關鍵字,而是顯示的呼叫其包裝類函式,將這些值轉為布林型別:

    if (Boolean(false)) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    if (Number(0)) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    if (Number(NaN)) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }
    
    if (String('')) {
        console.log('true');
    } else {
        console.log('false');    // output: false
    }

    對於Boolean類,有一個特別需要注意的是,當傳入一個字串時,它不會去解析字串內部的值,而是做個簡單地判斷,只要不是空字串,都會被認為是true:

    if (Boolean('false')) {
        console.log('true');   // output: true
    } else {
        console.log('false');
    }
    
    if (Boolean('')) {
        console.log('true');
    } else {
        console.log('false');  // output: false
    }

    上面介紹了這麼多,還有幾個例子需要提一下,那就是邏輯非、邏輯與和邏輯或操作符,連用兩個邏輯非可以把一個值轉為布林型別,而使用邏輯與和邏輯或時,根據上面的規則,參與運算的值會被轉換為相對應的布林型別:

    // 下面幾個轉為false
    
    var isFalse = !!0;            // false
    
    var isFalse = !!NaN;         // false
    
    var isFalse = !!'';           // false
    
    var isFalse = !!undefined;    // false
    
    var isFalse = !!null;         // false
    
    // 下面都轉為true
    
    var isTrue = !!3;             // true
    
    var isTrue = !!-3;            // true
    
    var isTrue = !!'0';           // true
    
    var isTrue = !!{};            // true
    
    // 邏輯與
    
    var foo = 0 && 3;             // 0
    
    var foo = -3 && 3;            // 3
    
    // 邏輯或
    
    var foo = 0 || 3;             // 3
    
    var foo = -3 || 3;            // -3

    轉為數字型別

    運算元在數字環境中參與運算時,會被轉為相對應的數字型別值,其中的轉換規則如下:

  • 字串型別轉為數字: 空字串被轉為0,非空字串中,符合數字規則的會被轉換為對應的數字,否則視為NaN
  • 布林型別轉為數字: true被轉為1,false被轉為0
  • null被轉為0,undefined被轉為NaN
  • 物件型別轉為數字: valueOf()方法先試圖被呼叫,如果呼叫返回的結果為基礎型別,則再將其轉為數字,如果返回結果不是基礎型別,則會再試圖呼叫toString()方法,最後試圖將返回結果轉為數字,如果這個返回結果是基礎型別,則會得到一個數字或NaN,如果不是基礎型別,則會丟擲一個異常
  • 一個其他型別的值被轉換為數字,跟其參與運算的操作符有很密切的聯絡,下面我們就來詳細介紹:

    加號“+”作為一元操作符時,引擎會試圖將運算元轉換為數字型別,如果轉型失敗,則會返回NaN,程式碼如下所示:

    var foo = +'';            // 0
    
    var foo = +'3';           // 3
    
    var foo = +'3px';         // NaN
    
    var foo = +false;         // 0
    
    var foo = +true;          // 1
    
    var foo = +null;          // 0
    
    var foo = +undefined;     // NaN

    上面程式碼中,對於不符合數字規則的字串,和直接呼叫Number()函式效果相同,但和parseInt()有些出入:

    var foo = Number('3px');      // NaN
    
    var foo = parseInt('3px');    // 3

    可以看出,parseInt對字串引數比較寬容,只要起始位置符合數字型別標準,就逐個解析,直到遇見非數字字元為止,最後返回已解析的數字部分,轉為數字型別。

    加號“+”作為二元操作符時,我們上面也提到過,如果一個運算元為字串,則加號“+”作為字串連線符,但如果兩個運算元都不是字串型別,則會作為加法操作符,執行加法操作,這個時候,其他資料型別也會被轉為數字型別:

    var foo = true + 1;          // 2
    
    var foo = true + false;      // 1
    
    var foo = true + null;       // 1
    
    var foo = null + 1;          // 1
    
    var foo = null + undefined;  // NaN
    
    var foo = null + NaN;        // NaN

    上面加法運算過程中都出現了型別轉換,true轉為1,false轉為0,null轉為0,undefined轉為NaN,最後一個例子中,null和NaN運算時,是先轉為0,然後參與運算,NaN和任何其他數字型別運算時都會返回NaN,所以最終這個結果還是NaN。

    對於undefined轉為NaN似乎很好理解,但為什麼null會轉為0呢?這裡也有些歷史淵源的,熟悉C的朋友都知道,空指標其實是設計為0值的:

    // 空指標的值為0
    
    int *p = NULL;
    
    if (p == 0) {
        printf("NULL is 0");    // output: NULL is 0
    }

    程式語言的發展是有規律的,語言之間也存在著密切的關聯,新的語言總是會沿用老的傳統,繼而新增一些新的特性。從上面的例子中,我們發現,null被轉為0其實很好理解,一點也不奇怪。

    另外,我們可別忘了減號“-”操作符,當減號“-”作為一元操作符(unary negation)時,也會將運算元轉換為數字,只不過轉換的結果與上面相反,合法的數字都被轉為負值

    除加號“+”以外的其他二元操作符,都會將運算元轉為數字,字串也不例外(如果轉型失敗,則返回NaN繼續參與運算):

    var foo = '5' - '2';          // 3
    
    var foo = '5' * '2';          // 10
    
    var foo = '5' / '2';           // 2.5
    
    var foo = '5' % '2';          // 1
    
    var foo = '5' << '1';          // 10
    
    var foo = '5' >> '1';          // 2
    
    var foo = '5' ** '2';          // 25
    
    
    var foo = '5' * true;          // 5
    
    var foo = '5' * null;          // 0
    
    var foo = '5' * undefined;     // NaN
    
    var foo = '5' * NaN;          // NaN

    上面的操作符中,位移和求冪操作符平時用的不多,不過在某些場景下(比如演算法中)還是挺實用的。我們都知道,JavaScript中的數字型別都以浮點型儲存,這就意味著我們不能想C和Java那樣直接求整除結果,而是通過相關的函式進一步處理實現的,如果通過位移可以簡化不少,而求冪操作也可以直接通過求冪運算子算出結果,看下面程式碼:

    // 浮點型運算
    var foo = 5 / 2;                // 2.5
    
    // 整除操作
    var foo = Math.floor(5 / 2);    // 2
    
    // 向右移一位實現整除
    var foo = 5 >> 1;              // 2
    
    // 求冪函式
    var foo = Math.pow(5, 2);       // 25
    
    // 求冪運算
    var foo = 5 ** 2;              // 25

    除了上面的操作符之外,遞增和遞減操作符也會將運算元轉為數字,下面以字首遞增操作符為例:

    var foo = '';
    
    ++foo;    // foo: 1
    
    
    var foo = '3';
    
    ++foo;    // foo: 4
    
    
    var foo = true;
    
    ++foo;    // foo: 2
    
    
    var foo = null;
    
    ++foo;    // foo: 1
    
    
    var foo = undefined;
    
    ++foo;    // foo: NaN
    
    
    var foo = '3px';
    
    ++foo;    // foo: NaN

    上面就是基本資料型別在數字環境下的轉換規則。對於物件型別,同樣有一套轉換機制,我們上面也提到了,valueOf()方法和toString()方法會在不同的時機被呼叫,進而得到相應的返回值,最後根據返回值再進行型別轉換,將其轉為目標型別。由於篇幅限制,關於自動型別轉換的後續內容,博主安排在下一篇中講解,敬請期待。

     

    轉載自:

    https://www.cnblogs.com/liuhe688/p/5918589.html