腦圖學習 JavaScript 之犀牛書【三 · 二】類型轉換、變量

介紹

上一篇講了 JavaScript 中的類型和值,本篇主要關於 類型轉換變量數組

須要注意包裝對象、引用類型變動、對象到原始值的類型轉換規則等。函數

運算符相關的隱式類型轉換請查看:post

自我提問

  • 字符串、數字等原始值爲何可使用 . 或 [] 讀取屬性?
  • 引用類型、非引用類型到底有什麼區別?
  • 對象是怎麼轉變爲原始值的?
  • 如何進行顯示類型轉換?

腦圖

腦圖

關鍵知識點

包裝對象

犀牛書這裏提到了一個 包裝對象 的概念(見 3.6 ),使用包裝對象的概念解釋了 爲何字符串、數字等原始值也能夠擁有各自的屬性學習

當 JavaScript 要讀取字符串 s 的屬性時,將會經過調用 new String(s) 來 將字符串轉換爲對象(也至關於調用 Object(s) ,對原始值使用 . 操做符讀取屬性會將原始值進行隱式類型轉換,轉換爲對象),而後引用這個對象的屬性,而使用結束後這個對象就會被 銷燬(實現不必定要建立和銷燬,可是能夠用來類比這個過程)。測試

經過包裝對象的概念能夠很容易理解字符串和數字等爲何有屬性,爲何沒法給它們的屬性賦值。ui

var s = 'string';
// 0
console.log(s.indexOf('s'));
// true
console.log(s.__proto__ === String.prototype);
複製代碼

上述代碼能夠看作爲this

var s = 'string';
console.log(new String(s).indexOf('s'));
複製代碼
var s = 'string';
// 可想象爲賦值在包裝對象上,然而這段語句執行完成包裝對象就被銷燬了,因此 s 上找不到 test
s.test = 'test';
// undefined
console.log(s.test);
複製代碼

不可變的原始值

原始值都是不可變動的,當爲原始值的變量從新賦值時,修改的是新的值。spa

能夠理解爲每一個原始值都是一個獨立的地址,把一個原始值賦值給一個變量會在內存中從新開闢一個空間賦值原始值並把新的地址賦值給這個變量。prototype

var a = 1; // 開闢棧空間 0001 -> 把空間地址賦給變量 a -> 存值 1 到 0001
var b = a; // 開闢棧空間 0002 -> 把空間地址賦給變量 b -> 複製 a 的值 1 -> 0002
// 1 1
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值 得出 1 1
b = 2; // 取出 b 的地址 0002 -> 存值 2 到 0002
// 2 1
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值 得出 2 1
複製代碼

可變的對象

對象類型是可變的,將一個對象類型賦值給一個變量時,實際上 傳遞的是它的引用(指針)指針

var a = {}; // 開闢棧空間 0001 -> 將空間地址賦給變量 a -> 開闢堆空間存儲 X0001 -> 存值 {} -> 存儲 X0001 地址到 0001
var b = a; // 開闢棧空間 0002 -> 將空間地址賦給變量 b -> 賦值 0001 中的地址,也就是 X0001 到 0002
// {} {}
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值,查出是堆地址時會去堆中取值,得出 {} {}
b.c = 2; // 讀取 b 從堆中取值,修改堆中值爲 {c: 2}
// {c:2} {c:2}
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值,查處是堆地址時會去堆中取值,得出 {c: 2} {c: 2}
複製代碼

== 和 ===

== 運算符在判斷時會進行 類型轉換,=== 運算符 不會作任何轉換

顯式類型轉換

一般可使用全局函數 Object、Number、Boolean、String 來進行顯式類型轉換。

然而咱們也能夠 藉助隱式類型轉換作顯示類型轉換。(隱式類型轉換見上一篇

console.log(+'0'); // 0 經過 + 運算符的隱式轉換將字符串顯式轉換爲數字
複製代碼

null 和 undefined 的對象類型轉換

注意,使用 Object 對 null、undefined 進行顯示類型轉換時,能夠正常輸出一個空對象而不報錯,至關於沒傳入值。可是當 JavaScript 嘗試對它們轉換爲對象類型時,將會拋出類型錯誤(好比 null.toString 至關於將 null 轉爲對象而後調用他的 toString,然而這將會拋出類型錯誤)。

對象到原始值的轉換

對象到原始值的轉換相對而言比較複雜。

  • 全部的對象轉換爲布爾型 都爲 true

  • 對象轉換爲字符串時,將會通過幾個步驟:

    1. 若是對象有 toString 方法,而且調用後返回原始值跳到第 3 步,不然第 2 步。
    2. 若是對象有 valueOf 方法,而且調用後返回原始值跳到第 3 步,不然第 4 步。
    3. 將經過上述方法返回的原始值轉換爲字符串。
    4. 得不到原始值,拋出類型錯誤。

    使用代碼測試一下

    const a = {
        toString: function() {
            console.log('toString is called');
            return null;
        }
    };
    console.log(String(a));
    // 'toString is called'
    // 'null'
    
    const b = {
        toString: function() {
            console.log('toString is called');
            return {};
        },
        valueOf: function() {
            console.log('valueOf is called');
            return false;
        }
    };
    console.log(String(b));
    // 'toString is called'
    // 'valueOf is called'
    // 'false'
    
    const c = {
        toString: function() {
            console.log('toString is called');
            return {};
        },
        valueOf: function() {
            console.log('valueOf is called');
            return {};
        }
    };
    console.log(String(c));
    // 'toString is called'
    // 'valueOf is called'
    // TypeError: Cannot convert object to primitive value
    複製代碼

    使用代碼模擬一下

    const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
    const obj2Str = obj => {
        let primitiveValue;
        if ('toString' in obj) {
            primitiveValue = obj.toString();
        } else if(!('valueOf' in obj)) {
            throw new TypeError("Can't find toString and valueOf");
        }
        if ('valueOf' in obj && (!('toString' in obj) || !isPrimitive(primitiveValue))) {
            primitiveValue = obj.valueOf();
        }
        if (isPrimitive(primitiveValue)) {
            return String(primitiveValue);
        } else {
            throw new TypeError("Can't convert to primitive");
        }
    };
    複製代碼

    能夠拿上面的測試代碼驗證一下是否是同樣的輸出

  • 對象轉換爲數字時,將會通過幾個步驟:(相似字符串的轉換,可是有所差異)。

    1. 若是對象有 valueOf 方法,而且調用後返回原始值跳到第 3 步,不然第 2 步。
    2. 若是對象有 toString 方法,而且調用後返回原始值跳到第 3 步,不然第 4 步。
    3. 將經過上述方法返回的原始值轉換爲字符串。
    4. 得不到原始值,拋出類型錯誤。

    一樣使用代碼測試一下

    const a = {
        valueOf: function() {
            console.log('valueOf is called');
            return null;
        }
    };
    console.log(Number(a));
    // 'valueOf is called'
    // 0
    
    const b = {
        valueOf: function() {
            console.log('valueOf is called');
            return {};
        },
        toString: function() {
            console.log('toString is called');
            return true;
        }
    };
    console.log(Number(b));
    // 'valueOf is called'
    // 'toString is called'
    // 1
    
    const c = {
        valueOf: function() {
            console.log('valueOf is called');
            return {};
        },
        toString: function() {
            console.log('toString is called');
            return {};
        }
    };
    console.log(Number(c));
    // 'valueOf is called'
    // 'toString is called'
    // TypeError: Cannot convert object to primitive value
    複製代碼

    一樣使用代碼模擬一下

    const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
    const obj2Num = obj => {
        let primitiveValue;
        if ('valueOf' in obj) {
            primitiveValue = obj.valueOf();
        } else if(!('toString' in obj)) {
            throw new TypeError("Can't find toString and valueOf");
        }
        if ('toString' in obj && (!('valueOf' in obj) || !isPrimitive(primitiveValue))) {
            primitiveValue = obj.toString();
        }
        if (isPrimitive(primitiveValue)) {
            return Number(primitiveValue);
        } else {
            throw new TypeError("Can't convert to primitive");
        }
    };
    複製代碼
  • 在使用 +、==、關係運算符(>、< 等)操做對象時,將會將對象轉換爲原始值,對於 非日期對象,轉換套用上述的數字的轉換方式(先 valueOf 後 toString),而對於 日期對象,轉換套用上述的字符串的轉換方式(先 toString 後 valueOf),可是 不會執行最後的變爲數字或字符串的類型轉換部分

    使用代碼模擬一下非日期對象的轉換

    const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
    const obj2Primitive = obj => {
        let primitiveValue;
        if ('valueOf' in obj) {
            primitiveValue = obj.valueOf();
        } else if(!('toString' in obj)) {
            throw new TypeError("Can't find toString and valueOf");
        }
        if ('toString' in obj && (!('valueOf' in obj) || !isPrimitive(primitiveValue))) {
            primitiveValue = obj.toString();
        }
        if (isPrimitive(primitiveValue)) {
            return primitiveValue;
        } else {
            throw new TypeError("Can't convert to primitive");
        }
    };
    複製代碼

    +、== 會在進行上述轉換後再次根據操做數進行二次判斷,並再次將獲得的原始值進行轉換。

    null 和 undefined 在進行 == 運算時將不會進行類型轉換,JavaScript 將會直接判斷另外一個操做數是否爲 null 或者 undefined 而後直接返回結果

  • 剩餘的其它操做符轉換類型比較明確,如 - 會將兩個操做數都轉換爲數字(套用到轉換爲數字的轉換規則)

var 重複聲明和聲明提高

使用 var 對變量進行 重複聲明是無害的,由於全部的變量聲明都會被提高到代碼執行的頂端(聲明實質上會在代碼編譯時執行),因此聲明 1 ~ n 次的效果都是同樣的。

做用域鏈

函數在 定義時 就會綁定一個做用域鏈(詞法做用域、靜態做用域相關),如:函數定義區域的做用域 -> 上層函數定義區域的做用域 -> ... -> 全局做用域。

函數在執行時做用域鏈的頂端將會加入它自身的函數做用域(參數、局部變量)。變量的查找將會隨着做用域鏈一直查找直到找到或者到全局做用域仍未找到報錯爲止。

擴展

以前有道題目是什麼狀況下 a == 1 && a == 2 爲 true,看到上面的對象到原始值的轉換,應該就能理解了,其實很簡單,藉助 == 操做符的隱式類型轉換就能夠作到。

const a = {
    value: 1,
    valueOf: function() {
        return this.value++;
    }
};
// true
console.log(a == 1 && a == 2);
複製代碼

補充內容

類型轉換表

注意對象到原始值的轉換指的是沒有對對象的 toString 和 valueOf 作過變動時的結果。

認真看了上面的轉換規則的確定能看出來,爲啥 [] 轉換爲數字會變成 0 呢,由於先調用 valueOf 然而數組默認的 valueOf 就是它自身,不是原始值,因此只能調用 toString,獲得空字符串,而後再轉成數字,就變成 0 了。

字符串 數字 布爾 對象
undefined 'undefined' NaN false throws TypeError
null 'null' 0 false throws TypeError
true 'true' 1 new Boolean(true)
false 'false' 0 new Boolean(false)
'' 0 false new String('')
' ' 0 true new String(' ')
'1.2' 1.2 true new String('1.2')
'one' NaN true new String('one')
0 '0' false new Number(0)
-0 '0' false new Number(-0)
NaN 'NaN' false new Number(NaN)
Infinity 'Infinity' true new Number(Infinity)
-Infinity '-Infinity' true new Number(-Infinity)
1.2 '1.2' true new Number(1.2)
{} '[object Object]' NaN true
[] '' 0 true

系列文章目錄

  1. 腦圖學習 JavaScript 之犀牛書【一】
  2. 腦圖學習 JavaScript 之犀牛書【二】詞法結構
  3. 腦圖學習 JavaScript 之犀牛書【三 · 一】數據類型
  4. 腦圖學習 JavaScript 之犀牛書【三 · 二】類型轉換、變量
  5. 腦圖學習 JavaScript 之犀牛書【四 · 一】運算符、類型轉換
  6. 腦圖學習 JavaScript 之犀牛書【四 · 二】表達式
相關文章
相關標籤/搜索