33 個 js 核心概念(四):顯式 (名義) 與 隱式 (鴨子)類型轉換

顯式與隱式類型轉換

前言

說實話,JavaScript 的類型轉換是個至關頭疼的問題,不管是對於初學者仍是有經驗的老司機。它的難處並不在於概念多難理解,而是狀況多且雜,看似相同的狀況結果卻又出人意料,不多有人能保證時刻都能作出正確的判斷。數組

所以,這篇文章但願能講的足夠細緻和明確,讓你們可以在平常使用中,可以儘快的搞清楚類型轉換的順序和結果。函數

長文預警,建議先 mark, 分屢次查看。spa

1、類型轉換

1. 什麼叫類型轉換?

咱們知道,JavaScript 中存在七種數據類型,在必要的時候,咱們會對不一樣類型的值進行相互間的轉換。好比說,在進行條件判斷時,咱們須要將其餘類型的值轉爲布爾類型值,在使用 console.log() 打印內容時,須要將其轉爲字符串輸出。prototype

2. JavaScript 中的類型轉換方式有哪些?

在 JavaScript 中,分爲顯式類型轉換和隱式類型轉換。code

其中,顯式類型轉換是咱們爲了功能須要,人爲的將一種類型的值轉換爲另外一中類型,轉換的時機和結果都是咱們預期的;而隱式類型轉換則是 JavaScript 在代碼運行時,未經咱們容許而進行的強制類型轉換。對象

2、顯式類型轉換

1. 其餘類型轉換爲字符串( ToString )

值類型 例子 轉換後 調用法則
number 34 '34' String(34)
boolean true 'true' String(true)
boolean false 'false' String(false)
undefiend undefined 'undefined' String(undefined)
null null 'null' String(null)
object { a: 'fa' } "[object Object]" String({a: 'fa'})
object new String(45) '45' String(new String(45))
object [1, 2] '1,2' String([1,2])
object function() {var d;} "function() { var d; }" String(function() {var d;})

其餘類型的值轉換爲字符串,是經過調用原生函數String()實現,但不一樣類型值的實現卻有明顯的差別。ip

對於基本類型的值,直接將其轉化爲值的字符串形式。而對於對象類型來講,便有些複雜了。rem

首先,每一個對象內部都有一個 [[Class]] 屬性,咱們經過Object.prototype.toString() 方法能夠獲得這個屬性的字符串值。字符串

對於對象(如{ a: 'ff'; })而言,除非本身定義 toString() 方法,不然,調用 String() 方法將返回和調用 Object.prototype.toString() 相同的值。(如 : "[object Object]")。博客

const obj_1 = {
  b: 'lalala'
};
const obj_2 = {
  toString() {
    return "fasfa";
  }
};
String(obj_1); // '[object Object]'
String(obj_2); // 'fasfa'

其次, JavaScript 中,除了普通對象,還有如下幾種:

  1. 封裝對象

    對於基本類型值 stringnumberboolean 是沒有 .lengthtoString() 方法的,所以,JavaScript 提供了內建函數 String()Number()Boolean() ,經過 new 調用後會將基本類型值封裝爲一個對象。

    若是想要取到封裝對象中的基本類型值,可使用 valueOf() 方法。

    // string 類型
    const a = 'i am string';
    typeof a; //  'string'
    // string 封裝對象
    const b = new String('i am sringObject');
    typeof b; // 'object'
    // 拆封
    b.valueOf(); // i am sringObject

    那對於封裝對象,String() 會返回什麼值呢?

    事實上,封裝對象對於 toString() 方法進行了封裝,所以,對封裝對象調用 String() 方法,將會返回封裝對象調用toString() 方法返回的值。

    const numObj = new Number(false); // Number {0}
    numObj.toString(); // '0'
    String(numObj); // '0'
  2. 函數

    對於函數來講,它也包裝了本身的 toString()方法,所以,調用 String() 方法時將返回函數字符串化後的值。

    function bar() {
      console.log('bar');
    }
    String(bar); // "function bar() {↵  console.log('bar');↵}"
    bar.toString(); // "function bar() {↵  console.log('bar');↵}"
    Object.prototype.toString.call(bar); // "[object Function]"

    從上例能夠看到,String()toString() 方法調用的是函數本身封裝的toSring(),若是調用對象的 toString() 方法,則函數與普通對象同樣,返回的是函數對象內部的 [[Class]] 屬性。

  3. 數組

    數組同函數同樣,一樣包裝了本身的 toString() 方法。此方法會將數組中的每一項用逗號鏈接成一個字符串。

    const arr = [1,4,6];
    String(arr); // "1,4,6"
    arr.toString(); // "1,4,6"
    Object.prototype.toString.call(arr); // "[object Array]"

2. 其餘類型值轉爲數字( ToNumber )

一樣,先感覺一下什麼叫絕望?~~

值類型 例子 轉換後 調用法則
string '34' 34 Number('34')
string '' 0 Number('')
string '34fad' NaN Number('34fad')
string '34fad'、'34.24'、'34' 34 parseInt('34fad')
string '34fad'、'34' 34 parseFloat(值)
string '34.34' 34.34 parseFloat(值)
boolean true 1 Number(true)
boolean false 0 Number(false)
undefiend undefined NaN Number(undefined)
null null 0 Number(null)
object { a: 'fa' } NaN Number({a: 'fa'})
object new String('fff') NaN Number(new String('fff'))
object [] 0 Number([])
object [1, 2] NaN Number([1,2])
object function() {var d;} NaN Number(function() {var d;})

看完一臉懵逼有沒有?!哈哈,不用懼怕,乍看上去,大概會以爲異常混亂,其實稍加整理,不外乎如下幾種狀況:

  1. 轉換後值爲 NaN

    數字與字符串不一樣,並非任何類型值都能轉爲數字,所以,就會有 NaN,意思就是 not a number

    諸如包含非數字的字符串、undefined、非空數組,部分對象,都是咱們知道沒法轉化爲一個數字的。

  2. boolean 類型值

    對於 truefalsetrue 轉換爲 1,false 轉爲 0。

  3. 帶有數字的字符串

    從上面咱們能夠看到,對於帶有數字的字符串,有三種方法進行轉換,但規則不一樣。

    • Number() 方法會對字符串總體進行轉換, 它會先判斷這個字符串是不是個正確的數字字符串,若是不是,則會返回 NaN
    • parseInt() 方法則會對字符串從左往右依次解析,直到遇到第一個非數字字符(包括小數點),若是最左邊的字符是非數字字符,則返回 NaN
    • parseFloat() 方法解析順序同 parseInt() 相同,不一樣的是它遇到第一個小數點時會正常往右繼續解析,直至遇到非數字字符中止。
其實嚴格來說,只有 `Number()` 方法是進行轉換操做,然後二者屬於將字符串**解析** 爲數字,但爲了講解方便,我將它們放在一塊兒講述。
  1. 對象

    對於對象而言,會先將對象轉爲基本類型值( ToPrimitive ),再對基本類型值調用 Number() 方法。

    那如何將對象轉爲基本類型值?首先會調用對象的 valueOf() 方法,若是沒有此方法或者此方法返回值不是基本類型值,則會調用toString() 方法,若是 toString() 方法不存在或者返回值也不是基本類型值,會產生 TypeError 錯誤。

    // 普通對象
    const nomalObj = {
      a: '56'
    };
    nomalObj .valueOf(); // { a: '56'}
    nomalObj.toString(); // "[object Object]"
    // Number(nomalObj) 至關於Number("[object Object]") 
    Number("[object Object]"); // NaN
    Number(nomalObj); // NaN
    
    // valueOf() 返回基本類型值的對象
    const obj_1 = {
      a: '56',
      valueOf: function() {
        return '23';
      }  
    };
    obj_1.valueOf(); // '23'
    // Number(obj_1) 至關於 Number('23');
    Number('23'); // 23
    Number(obj_1); // 23
    
    // valueOf() 返回非基本類型值,toString() 返回基本類型值的對象
    const obj_2 = {
      a: '56',
      valueOf: function() {
        return {b: 34}
      },
      toString: function() {
        return false;
      }
    };
    obj_2.valueOf(); // {b: 34}
    obj_2.toString(); // false
    // Number(obj_2) 至關於 Number(false)
    Number(obj_2); // 0
    Number(false); // 0

    上面的規則,適用於咱們所說的全部對象,好比數組,封裝對象和函數。

3. 其餘類型轉換爲 boolean 值( ToBoolean )

咱們能夠經過 Boolean()方法 或!!運算符來顯式的將一個值轉換爲布爾值。

相對來講,判斷一個值是 true 仍是 false 則比較容易,咱們只須要記住如下幾種值會轉換爲 false,而其餘值,均爲 true

  • undefined
  • null
  • false
  • +0、-0 和 NaN
  • ""

當咱們看到 []、{} 甚至是 "''" 時,也必定要記住,它們是真值。

Boolean(false); // fasle
Boolean([]); //true
Boolean({}); //true
Boolean(''); // false
Boolean('""'); // true
Boolean('false'); // true

3、隱式強制類型轉換

除了進行強制類型轉換,JavaScript 會在運行時根據須要,自動進行類型的轉換,儘管這個特色飽受爭議,但不得不認可,某些狀況下咱們仍舊更喜歡使用某些隱式轉換規則。

一旦某些隱式的規則被接受並普遍使用,從某種意義上來說,這些規則便同顯式轉換同樣。

1. 奇怪的 +

先看一一個最多見的例子:

const a = 5;
const b = '6';
console.log(a+a); // 10
console.log(a+b); // '56'
console.log(b+b); // '66'

之因此會產生上例中的情況,緣由就在於在JavaScript 中,+ 運算符既能夠做用於number 類型值,也能夠做用於 string 類型值。前者進行數字相加,後者則進行字符串的拼接。

這就是爲何5 + 5 = 10'6' + '6' = '66'。而當 + 號兩邊既有數字也有字符串時,則會隱式的將數字轉換爲字符串,而後進行字符串的拼接。

那兩邊沒有字符串的狀況呢?好比:

const a = [1,4];
const b = [2,3];
const c = 4;
console.log(a+c); // '1,44'
console.log(a+b); // '1,42,3'

爲何會這樣?原來只要 + 的其中一個操做數能夠經過某種方式(toPrimitive)轉換爲字符串,就會進行字符串的拼接。

咱們知道,數組[1,4] 能夠經過 toString() 方法返回字符串 '1,4',所以,[1,4] + 4 就至關於 '1,4' + 4

由於這個特性,咱們在想將一個數字 a 轉換爲字符串時,即可以直接使用 a + '' 的形式便可。相對於顯式使用String(a),隱式轉換則更加簡潔。

從數組的例子咱們能夠看到,除了數字,其餘類型的值也能夠經過 + ' ' 的形式轉化爲字符串。

const a = {b: '2'}
console.log( a+ ''); // "[object Object]"

但有一點須要注意,對於對象而言,使用 String() 方法是直接取這個對象 toString() 方法的返回值,而 + ' ' ,則會對這個對象調用 valueOf 反法,而後對 valueOf 的返回值調用 toString(),將其轉換爲字符串。

const a = {
  toString: function() { return 45 },
  valueOf: function() { return 4}
 };
String(a); // '45'
a + ' '; //  // '4'

好在除非咱們特地去改變一個對象的 valueOf 及 'toString()' 方法,經過上述兩個方式的轉換後的結果都是一致的。

2. 有用的 -

+ 號不一樣的是,- 號只能用於數字的相減,對於它兩邊的操做數,都會通過隱式類型轉換轉爲數字。

const a = '34';
const b = '4';
console.log(a - b); // 30
const c = 'dd';
console.log(a - c); // NaN
const d = [4];
console.log(a - d); // 30

根據上例,咱們可看到,若是 - 號兩邊是字符串,則會將他們強制轉換爲數字,若是 - 兩邊不是字符串,則會先將其轉爲字符串,再將這個字符串轉爲數字。

3. 隱式轉換爲布爾值

將其餘類型值隱式轉換爲布爾值是咱們最經常使用的一種轉換。由於程序的編寫實質上就是不停的進行判斷。

在如下場景中,都是進行判斷,而只要傳入的值不是布爾值,都會經過隱式類型轉換轉爲布爾值。

  1. if (..) {} 語句中的條件判斷表達式。
  2. for ( .. ; .. ; ..) 語句中的條件判斷表達式。
  3. while (..) do ... while ( ..) 中的條件判斷表達式。
  4. ? : 中的條件判斷表達式。
  5. 邏輯或 || 或邏輯與 && 左邊的操做數。

在這些狀況下,都將會進行其餘類型值到布爾類型值的隱式轉換,規則同顯式調用 Boolean()

上面就是不一樣數據類型直接顯式或隱式的轉換規則,咱們不須要將每一種狀況都牢記在心,但有必要對他們進行充分的瞭解,這能夠保證咱們在實際寫代碼時避免很多奇怪又難以排查的 bug 。

原文地址: 阿木木的博客 與 隱式 (鴨子)類型轉換)
相關文章
相關標籤/搜索