JavaScript中的強制型別轉換

笑口常開發表於2017-10-13

將值從一種型別轉換為另一種型別通常稱為型別轉換,這是顯式的情況;隱式的情況稱為強制型別轉換
JavaScript 中的強制型別轉換總是返回標量基本型別值,如字串、數字和布林值

如何理解: 型別轉換髮生在靜態型別語言的編譯階段,而強制型別轉換則發生在動態型別語言的執行時?
1. 如果是靜態語言,比如c等,所有的型別轉換應該都是在編譯階段處理的吧?
2. 如果是動態語言,如js等, 編譯階段會處理型別轉換嗎?

ToString

  1. 它負責處理非字串到字串的強制型別轉換
  2. 數字的字串化遵循通用規則,
  3. 陣列的預設toString()方法經過了重新定義

Json字串化

說明
`JSON.stringify()`在將JSON物件序列化為字串時也用到了`ToString`,
但是, JSON字串化並非嚴格意義上的強制型別轉換:

1. 對多數簡單值來說,JSON字串話和`toString()`效果基本相同,只不過序列化的結果總是字串, 所有** 安全的SJON值 **都可以使用`JSON.stringify()`字串化, 安全的JSON值是指能夠呈現為有效JSON格式的值
    JSON.stringify(42); // "42"
    JSON.stringify("42"); // ""42""
    JSON.stringify(null); // "null"
    JSON.stringify(true); // "true"
2. 不安全的JSON值
    `undefined`, `function`, `symbol`和包含迴圈引用的物件都不符合JSON結構標準, `JSON.stringify()`在物件中遇到`undefined`, `function`, `symbol`時會自動將其忽略, 在陣列中則會返回`null`;

    JSON.stringify(undefined); undefined
    JSON.stringify(function(){}); // undefined
    JSON.stringify([1, undefined, function(){}, 4]); // "[1, null, null, 4]"
    JSON.stringify({a: 2, b: function(){}}); // "{"a": 2}""

對包含迴圈引用的物件執行JSON.stringify()會報錯
3. 如果物件中定義了toJSON方法, 那麼在呼叫JSON.stringify之前會預設的隱式呼叫該方法,然後對該方法的返回值使用stringify,
如果要對含有非法JSON值的物件做字串化,可以使用toJSON來返回一個安全的JSON值,然後在stringify中對其JSON字串化
    var o = { };

    var a = {
        b: 42,
        c: o,
        d: function(){}
    };

    // 在a中建立一個迴圈引用
    o.e = a;

    // 迴圈引用在這裡會產生錯誤
    // JSON.stringify( a );

    // 自定義的JSON序列化
    a.toJSON = function() {
        // 序列化僅包含b
        return { b: this.b };
    };
    JSON.stringify( a ); // "{"b":42}"

4. JSON.stringify()中還可以傳入第二個引數,這個引數是一個陣列或者是函式,用來說明在序列化過程中哪些屬性應該被處理
* 如果是一個陣列,那麼應該是一個包含物件中需要處理的屬性的字串陣列
* 如果是一個函式,那麼它會對物件本身處理一次,然後對物件中的各個屬性分別處理一次,

    var a = {
        b: 42,
        c: '42',
        d: [1, 2, 3]
    }
    JSON.stringify(a, ['b', 'c']); // "{"b": 42, "c": "42"}"
    JSON.stringify(a, function(k, v){
        if(k !== 'c'){
            return v;
        }
        });  // "{"b": 42, "d": [1, 2, 3]}"

這個函式中需要傳遞兩個引數:鍵k和值v, 引數k在第一次呼叫時為undefined

ToNumber

將非數字值當作數字來使用
其中 true轉換為1, false轉換為0, undefined轉換為NaN, null轉換為0, 對字串的處理遵循數字常量的相關規則, 處理失敗時返回NaN

1. 物件如何轉換為數字: 先轉換為對應的基本型別,然後再強制轉換為數字
    先檢查是否有`valueOf`方法,如果有並且返回的是**基本型別的值**, 則使用該值進行強制型別轉換,
    如果沒有`valueOf()`或者其返回的不是基本值,就使用`toString()`方法的** 返回值 **來進行強制型別轉換
    如果`valueOf()`和`toString()`,則會產生`TypeError`錯誤

所以, 使用Object.create(null)建立的物件是不能進行強制型別轉換的, eg:

var obj = Object.create(null);
obj == 1; //Uncaught TypeError: Cannot convert object to primitive value
obj.valueOf = function(){return 1;}
obj == 1; // true

ToBoolean

在這一部分意識到了之前的一個誤區:先說明一下:
我們知道''==false// true, ''==true; //false,但是如果字串非空呢?
之前的
錯誤**想法是'aa'==true返回為true, 但後來經過測試發現我錯了,aa==trueaa==false返回的都是false
後來想了一下原因: aa在進行比較時會先轉換為數字,’aa’轉為數字是NaN, 所以返回的結果是false


言歸正傳:
JS中, 1和true, 0和false可以相互轉換,但它們並不是一回事

可以被強制型別轉換為false的值:
undefined
null
false
+0, -0, NaN
* ""

假值物件
瀏覽器在某些情況下在常規的js語法之外建立的一些外來的值,這些值就是假值物件,在將它們強制型別轉換為布林值時結果就是false, eg:
document.all == false// false
document.all == true// false
真值就是假值列表之外的值
目前應該只有IE裡面下面程式碼會列印1, 在別的瀏覽器裡面會列印2

if(document.all){
    console.log(1);
}else{
    console.log(2);
}

真值
真值就是假值列表之外的值

var a = 'false'
var b = '0'
var c = '""'

var d = Boolean(a && b && c);
d; // true

顯示強制型別轉換

字串和數字之間的顯式強制轉換

字串和數字之間的強制轉換是通過StringNumber函式進行,除此之外也有別的方式:

var a = 42;
var b = a.toString();

var c = '3.14';
var d = +c; // '+'或者'-'運算子單獨使用都可以將運算元轉換為數值,區別在於'-'還會改變運算元的符號

b; //"42";
c; // 3.14

var c = '3.14';
var d = 5+ +c; // 由於'++'會被當作自加運算子處理, 所以應該在中間加一個空格'+ +'
d; // 8.14

日期轉換為數字

+運算子還有一個作用是可以將日期物件轉化為數字,返回的是Unix時間戳,以微秒為單位

js中作為建構函式的函式如果沒有引數,可以不帶括號的,eg: var a = +new Date, 此外還可以使用:

var timestamp = (new Date).getTime()
或者:
var timestamp = Date.now() //(推薦的用來獲取當前時間戳的方式)

位運算相關

字位運算只適用與32位整數,運算子會強制運算元使用32位格式。
~x大致等同於-(x+1),只有當x=-1時, -(x+1)才等於0,

字位截除
可以使用~~來截除數字的小數部分,但這不同與Math.floor, |也可以,但是考慮到優先順序,更常用的是~~;

Math.floor(-49.6); // -50
~~-49.6; // -49

顯示解析數字字串

解析字串中的數字和將字串強制型別轉換為數字是有區別的

    var a = '42';
    var b = '42px';

    Number(a); //42
    parseInt(a); //42

    Number(b); // NaN
    parseInt(b); // 42

解析,顧名思義,用的是parseInt或者parseFloat,
強制轉換, 使用的則是Number函式
解析需要傳入的引數是字串,如果是非字串的話,則會先轉換成字串,解析中還可以傳入第二個引數,代表轉換的進位制,有一個看似無厘頭,實則很正確的一個例子:
parseInt(1/0, 19); // 18

分析如下:
1/0 返回的是 Infinity, 首先,轉換成字串 “Infinity”,也就是說,實際執行的是:
parseInt("Infinity", 19), ‘I’在19進位制中代表的是18, 而’n’是沒有意義的,所以解析完I以後,就返回了,所以結果是18,其他一些神奇的例子:
parseInt(0.000008);  // 0
parseInt(0.0000008); // 8
parseInt(false, 16); // 'f'*16 + 'a'= 15 * 16 + 10 = 250
parseInt(parseInt, 16); // 'f'=15
parseInt('0x10'); // 16
parseInt('103', 2); //2
parseInt(78, 8); // 8

顯式轉換為布林值

  1. 使用Boolean函式或者!!進行ToBoolean的強制型別轉換
    var a = '0';
    var b = [];
    var c = {};
    
    var d = '';
    var e = 0;
    var f = null;
    var g;
    
    Boolean(a); //true
    Boolean(b); // true
    Boolean(c); //true
    
    Boolean(d); //false
    Boolean(e); //false
    Boolean(f); //false
    Boolean(g); //false
    
  2. 顯示ToBoolean的一個作用是可以在JSON序列化過程中將不安全的值返回對應的布林值
    var a = [1, 
            function(){},
            2,
            function(){}];
    JSON.stringify(a); // "[1, null, 2, null]"
    
    JSON.stringify(a, function(k, v){
        if(typeof v == 'function'){
            return !!v;
        }else{
            return v;
        }
        })  // "[1, true, 2, true]"
    
  3. 杜絕使用var b = a ? true : false(當a涉及了隱式轉換為布林值的時候)

隱式強制型別轉換

字串和數字之間的強制轉換

如果某個運算元是字串或者能夠通過以下步驟轉換為字串的話,+將進行拼接操作。如果其中一個運算元是物件(包括陣列),則首先對其呼叫 ToPrimitive 抽象操作,

    var a = {
        valueOf: function(){
            return 42;
        },
        toString: function(){
            return 4;
        }
    }
    a + ''; // '42' 呼叫`valueof方法` 先轉換成原始值,在將原始值轉換成字串
    String(a); // '4' //String()會呼叫toString方法將其轉換成字串

數字到字串的隱式轉換也是類似的

    var a = [1], b = [2];
    a - b; // -1

布林值和數字之間的 隱式強制轉換

function onlyOne() {
    var sum = 0;
    for(var i = 0; i < arguments.length; i++){
        if(arguments[i]){
            sum += Number(!!arguments[i]);
        }
    }
    return sum == 1;
}

var a = true;
var b = false;

onlyOne(a, b); //true
onlyOne(a, a, b); //false

下面的情況下會發生布林值的隱式強制轉換
if, for, while
? :三目運算子
* 邏輯運算子||和’&&’操作符左邊的運算元

||&&

js中,這兩個操作符返回的不一定是布林值,它們實際上是選擇兩個運算元中的一個,然後將其返回
可以用它來進行程式碼壓縮:

if(a){
    foo()
}

a&&foo()

寬鬆相等 和 嚴格相等

之前在區分=====時,一直覺得說“==運算子比較值是否相等,===運算子會檢查值和型別是否相等”很對, 但是這次看書上說,

==允許在相等比較中進行強制型別轉換,而===不允許
覺得這種說法更加準確

  1. 字串和數字之間的相等比較
    無論字串是在操作符的左邊還是右邊,所採取的操作都是將字串轉換成數字進行比較

  2. 其他型別和布林型別之間的相等比較
    會將其他型別轉換成數值,然後進行比較,
    eg:

    var x = true, y = '42';
    x == y; // false
    
  3. nullundefined之間的相等比較
    nullundefined之間的==比較也涉及隱式強制型別轉換

var a = null;
var b;

a == b; //true
a == null; //true
b == null; // true

a == false; //false
b == false; //false


4. 物件和非物件之間的相等比較
物件和標量基本型別進行相等比較是,物件會先轉換成原始值,然後進行比較
var a = 42;
var b = [42];

a == b;  //true

如果其中的物件是原始值的封裝物件,那麼在比較時,物件轉換成原始值的過程其實也就是物件的解封裝過程,但有些情況需要注意:
1. undefinednull是沒有封裝物件的
2. NaN雖然有封裝物件,但是解封回來的原始值也是NaN,而NaN是不等於NaN
eg:
var a = null;
var b = Object(a);

a == b; //false

var c = undefined;
var d = Object(c);
c == d; //false

var e = NaN;
var f = Object(e);
e == f; //false

最後,是一些比較的易錯點

'0' == false; //true
false == 0; //true
false == ''; //true
false == []; //true
'' == 0; //true
'' == []; //true
[] == ![]; //true, 首先進行![]的隱式轉換,先將`[]`轉換成布林值,為`true`,然後取反是`false`,而`[] == false`是true

'' == [null]; //true, 注意`[null]`字串話後是空字串`''`
0 == '\n'; // true; 原因在於: ''或'\n'等空字串在`ToNumber`的過程中被轉換成`0`

抽象關係比較:

  1. 如果比較雙方都是字串,那麼進行字串間的比較
  2. 否則,會將比較雙方都轉換成原始值,如果轉換結果中有非字串,那麼就都轉換成數字進行比較

然後裡面有一些比較詭異的事情:

var a = {b: 42};
var b = {b: 43};

a < b; //false a == b; //false a > b; //false

a <= b; //true a >= b; //true
分析如下:
首先比較雙方都不是字串,先將其都轉換成原始值:
a == ‘[object Object]’, b == ‘[object Object]’;
那麼不應該是 a == b嗎?
因為根據規範,js中的比較是這麼處理的:
a <= b; 被處理為 !(b<a), 因為 b < a的結果是false, 所以a<=b返回true

相關文章