沿着平滑的曲線學會 JavaScript 中的隱式強制類型轉換(基礎篇)

文章講隱式強制類型轉換的路線是從基礎講到應用, 這條學習曲線比較平滑. 具體的路線爲:正則表達式

基本數據類型引出包裝類型的概念 ==> valueOf 和 toString 方法 ==> ToPrimitive 抽象操做和 [[DefaultValue]] 操做 ==> 各類類型之間類型轉換的規律 ==> 常見引發隱式類型轉換的狀況 ==> 多個實例練習以檢驗方法的有用性.數組

1. 從基本包裝類型講起

討論基本包裝類型的前提是瞭解基本數據類型(也能夠稱爲原始類型, 簡單數據類型等)。而後經過基本數據類型調用方法的行爲, 引出基本包裝類型的概念和做用.函數

1.1 基本數據類型

已經知道 JavaScript 中共有6種基本數據類型,分別是:string,number,boolean,null,undefined,symbol.學習

基本類型既不是對象, 也沒有方法可供調用. 然而常常見到基本類型的變量調用方法的狀況, 類如:ui

let index = 'ABCDE'.indexOf('CD');
console.log(index); // 2
複製代碼

上例中 'ABCDE' 是一個字符串的基本類型, 這個字符串直接調用了indexOf 方法, 將該方法的返回值保存在變量 index 中, 而後在第二行輸出. 能夠看到這個例子可以運行而且結果正確.spa

既然上面說原始類型沒有方法可供調用, 那麼字符串 'ABCDE' 在調用 indexOf 方法時爲何沒有出錯呢? 並且從運行結果來看, indexOf 這個方法確實被調用了, 那麼是誰調用了這個方法而且返回了正確的結果呢? 答案就是基本包裝類型. 下面引入基本包裝類型的相關內容.prototype

1.2 基本包裝類型

關鍵詞: 包裝, 基本包裝類型, 基本包裝類型的對象code

上一節討論到其實調用方法的並非字符串自己 , 而是基本包裝類型. 下面就應該具體討論基本包裝類型的相關內容了.對象

爲了基本類型能夠正常調用方法, 後臺會爲這個基本類型的值自動建立一個對應的對象, 這個對象的類型就稱爲基本包裝類型, 而後用這個對象去調用方法, 在調用完方法以後, 這個對象就會被銷燬( 用完就銷燬 )了. 這個從基本數據類型生成基本包裝類型對象的過程稱爲包裝( box ).繼承

簡單來講, 在基本類型調用方法的時候, 方法的真正調用者其實不是咱們直接定義的基本類型, 而是後臺給咱們建立的基本包裝類型的 對象 . 並且這個對象是臨時的、不會一直存在的、用完就會被銷燬的.

這個對象還有一個特色, 他是與基本類型的值相對應的. 然而:

並非每一種基本類型都有對應的包裝類型

上節中提到的六種基本類型中的四種有其對應的包裝類型, 分別是:

string(字符串) -> String, number(數值) -> Number,boolean(布爾) -> Boolean,symbol(符號) -> Symbol

其中符號 -> 表示對應. 同時: 注意首字母的大寫( 對象名的首字母習俗默認大寫 ).

注意: 本文只討論 string,number,boolean 三種基本類型及其對應的包裝類型.

還須要瞭解的是, 不只僅是系統能夠隱式的建立包裝對象, 用戶也能夠手動、顯式的建立一個基本包裝類型的對象, 方法爲: 用關鍵字 new 加上對應的包裝類型的構造函數, 參數傳入基本類型的值便可, 例如:

let n = new Number(22);             // n 是 數值22對應的包裝對象
let str = new String('example');    // str 是字符串 'example' 對應的包裝對象
let flag = new Boolean(false);      // flag 是布爾值 false 對應的包裝對象
複製代碼

至此, 再看第一節的這個例子, 能夠再次想象系統建立包裝對象的大體過程:

let index = 'ABCDE'.indexOf('CD');
console.log(index); // 2
複製代碼

能夠發現第一行代碼中後臺發生了包裝行爲: 後臺根據字符串類型的 'ABCDE' 包裝出了一個對象. 即: 在調用 indexOf 方法時, 後臺發現想調用這個方法的是一個基本類型的值, 可是這個值沒有這個方法可供調用, 因而爲它生成了對應的包裝對象( 操做 1 ), 而後經過這個包裝對象來調用了 indexOf 方法( 操做 2 ), 然後將方法的返回值賦給了變量 index. 最後, 把這個包裝對象銷燬( 操做 3 ).

根據以上思路,能夠大體模擬出上述過程的對應的代碼:

// step 1. 建立 'ABCDE' 對應的基本包裝類型的對象: 
let temp = new String('ABCDE');

// step 2. 用包裝類型的對象 temp 調用 indexOf 方法, 並將返回值賦給 index 變量: 
let index = temp.indexOf('CD');

// step 3. 將 temp 對象銷燬
temp = null;
複製代碼

1.3 總結

當一個基本數據類型想要調用方法時, 後臺會爲它生成一個臨時的包裝對象, 利用這個對象去調用方法, 再將方法執行的結果返回, 隨後這個臨時對象被銷燬.

1.4 包裝對象的"拆包裝" box <-> unbox

從上面的內容瞭解到 包裝(box) 是根據一個基本類型的值生成一個對應類型的對象的過程, 與這個過程大體相反, 存在一種根據包裝對象生成基本類型值的過程, 可稱爲 拆包裝 (unbox). 這個過程一樣便可以由後臺隱式的完成, 也能夠手動的調用方法 valueOf 來作.

下一節開始討論 valueOf 這個方法, 同時引出另一個一樣重要的方法 toString.

2. 對象的兩個重要方法 valueOftoString

2.1 基本包裝類型的拆包裝( unbox ) 用到的 valueOf 方法

基本包裝類型的拆包裝操做用到了包裝對象中的 valueOf 函數, 這個函數能夠將一個對象轉換成一個基本類型的值.

對於 Boolean, Number 和 String 三者的基本包裝對象來講, 調用 valueOf 的返回值是各自對應的基本數據類型的值:

let n = new Number(22);     // 包裝基本數值數據 22
// 拆包裝出來的結果是對應的基本數據類型的值
console.log(n.valueOf() === 22);    // true

let str = new String('example');    // 包裝基本字符串數據 'example'
// 拆包裝出來的結果是對應的基本數據類型的值
console.log(str.valueOf() === 'example');   // true, 

let flag = new Boolean(false);      // 包裝基本布爾數據 false
// 拆包裝出來的結果是對應的基本數據類型的值
console.log(flag.valueOf() === false);  // true
複製代碼

有時會發生後臺隱式拆包裝的狀況, 包裝類型的對象會在後臺調用 valueOf 方法, 例如:

let a = new Number(1);
let b = a + 1;  //---> 這一行發生了隱式拆包裝操做: let b = a.valueOf() + 1;

console.log(b);  // b 爲 2

console.log(typeof a);  // object
console.log(typeof b);  // number, b 的類型爲 基本數據類型, 而不是包裝類型
複製代碼

甚至一行代碼中會發生包裝和解包裝兩種操做, 例如:

let num = 3.14159;
console.log(num.valueOf());   // 3.14159
複製代碼

在上面代碼塊的第二行代碼中變量 num 要調用函數 valueOf , 此時 num 會被先包裝爲 基本包裝類型的對象,而這個對象在調用 valueOf 方法時就發生瞭解包裝的操做.

2.2 其餘對象的 valueOf 方法

不只僅是基本包裝類型有 valueOf 方法, 許多 JavaScript 內建(build-in)對象都有該函數, 爲使執行的結果與對象自己相符合, 大多對象都重寫了這個方法. 下面看一些其餘對象的 valueOf 方法的行爲有什麼特色.

如下列出經常使用內置對象的 valueOf 方法的返回值:

對象 返回值
Boolean, Number 和 String 三者 各自相對應的基本類型的值
Array,Function,Object 三者 其自己
Date 當前時間距 1970.01.01 午夜的毫秒數
Math 和 Error 沒有 valueOf 方法

下面是實驗結果:

// 數組調用 valueOf, 返回數組自己
let array = [1, 'hello', false];
console.log(array.valueOf() === array);   // true

// 函數調用 valueOf, 返回函數自己
function foo(){}
console.log(foo.valueOf() === foo);   // true

// 對象調用 valueOf, 返回對象自己
let obj = {
    name: 'doug',
    age : 22
};
console.log(obj.valueOf() === obj);   // true

// 當前時間距1970年1月1日午夜的毫秒數
console.log(new Date().valueOf());   // 1551684737052

複製代碼

總結: valueOf 方法能夠將一個對象轉換爲基本數據類型, 並非每一個對象都有此方法(例如: Math 和 Error 對象). 對於布爾、數值和字符串三者的基本包裝類型來講,調用此函數返回其對應的基本類型的值; 對象調用此函數的返回值是其自己 ( 因爲數組和函數本質上也是對象, 因此也返回其自身 ) .

提到 valueOf 方法就不得不想起另一個對於類型轉換十分重要的方法 toString, 下節將會討論它.

2.3 能夠將對象表示爲字符串的方法 toString()

每一個內置的對象都有此方法,是從 Object 對象繼承而來的. 爲使執行的結果與對象自己相符合, 大多數內置對象都重寫了該函數. 常見的對象調用 toString 方法的返回值以下:

  • 對於用戶建立的對象, 返回'[object object]'. (存在一個例外, 見最後部分)

  • 對於 Math 對象, 返回 "[object Math]":

// 自定義的對象
console.log({name: 'doug'}.toString());  // '[object Object]'

// Math 對象
console.log(Math.toString());  // '[object Math]'
複製代碼
  • 對於 第一部分中提到的 3 個基本包裝類型的對象:
  1. 對於布爾對象, 返回字符串 "true" 或 "false", 根據其對應的基本數據類型的值而定.
  2. 對於數值對象, 返回在指定基數下該數的字符串形式, 默認基數是 10, 即默認返回 十進制 的數用引號包裹而成的字符串.
  3. 對於字符串對象, 返回對應基本數據類型的字符串(和調用 valueOf 方法獲得的結果相同) .
// 布爾值的包裝類型的對象
console.log(new Boolean(false).toString());   // 'false'

// 數值包裝類型對象的對象
console.log(new Number(3.14159).toString());  // '3.14159'

// 字符串包裝類型對象的對象
console.log(new String('str').toString());    // 'str'
複製代碼
  • 對於數組,返回全部項組成的字符串, 各項之間用 "," 鏈接.
// 數組
console.log([1, 'hello', false].toString());  // '1,hello,false'
複製代碼
  • 對於 函數,toString方法返回一個字符串,其中包含用於定義函數的源文本段.
// 函數
function foo(){console.log('hello foo');}
console.log(foo.toString());  
// 'function foo(){console.log('hello foo');}'
複製代碼
  • 其餘對象
  1. 對於 RegExp 對象,返回該正則表達式的字符串.
  2. 對於 Date 對象, 返回表示特定時間的字符串.
  3. 對於 Error 對象,返回包含錯誤內容的字符串.
// 正則對象
console.log(new RegExp("a+b+c").toString());      // "/a+b+c/"

// 日期對象
console.log(new Date().toString()); 
// Mon Mar 04 2019 17:07:54 GMT+0800 (中國標準時間)


// Error 對象
console.log(new Error('fatal error').toString()); 
// 'Error: fatal error'
複製代碼

並不是每一個對象都有 toString() 方法, 例如經過 Object.create 函數傳入null 爲參數建立出來的對象, 因爲它的 prototypenull, 因此沒有 toStringvalueOf 方法

2.4 總結

夲節討論了兩個重要的函數 valueOftoString , 前者返回調用者的基本類型的值, 後者能夠將一個對象轉化爲字符串.

這兩個函數將在強制類型轉換過程當中起到重要的做用.

注: 包裝和拆包裝過程也有其餘名稱, 例如封裝(wrap)和解封(unwrap), 只是同一個過程的不一樣說法.

相關文章
相關標籤/搜索