【跟着犀牛書複習JS基礎】隱式類型轉換

引言javascript

惡補我慘不忍睹的js基礎,從毫無頭緒到有一點頭緒,畫個圈但願能逐漸找到適合個人方法。目前在跟着犀牛書一章一章的過,把難點和看不懂的地方拎出來網上多找些文章看一看,相關的面試題目作一作。java

第一篇先從永遠很混亂,永遠覺得清楚了但其實仍是不清楚的隱式類型轉換開始,本篇對標犀牛書第6版3.6包裝對象,3.8 類型變換 ,第4章表達式和運算符。面試

類型概述

既然要作類型轉換,首先要了解js中有6大原始類型 (ES10中新增第7種bigInt 目前絕大部分生產環境仍是ES6, 暫且忽略),包括Bool String Number Null Undefined Symbol(ES6新增的)和引用類型Object,包括ArrayFunctionRegexpDate等,這篇文章中暫沒考慮Symbol類型。數組

原始類型的判斷可使用typeof, 例如 typeof 'aabb' === 'string' 引用類型的判斷可使用instanceof, 例如 [] instanceof Array === truebash

注意如下兩個特殊狀況post

typeof null === 'object';
 typeof function(){} === 'function'
複製代碼

類型轉換規則

​ 已知js數據類型有兩大類,原始類型和引用類型,則類型轉換無非原始類型之間的轉換、原始類型轉引用類型、引用類型轉原始類型。ui

​ 犀牛書中已經列出了各類類型轉換對應的規則: this

類型轉換對應表

原始類型的互換

原始類型的轉換很簡單,對應表格就行,注意紅框中幾種看似簡單可是容易混淆的轉換:spa

  1. undefined 轉數字爲NaN;
  2. null轉數字爲0;
  3. true值轉字符串爲「true」;
  4. false值轉字符串爲「false「

引用類型和原始類型的互換

引用類型和原始類型的互換就是常說的裝箱和拆箱的概念,裝箱即把原始類型轉換爲對應的引用類型,拆箱即把引用類型轉換爲原始類型。prototype

裝箱常常發生在後面所說的常見場景中的屬性訪問表達式,好比說:

var o = {x: 1, y: 2};
var arr = [1, 2, 3];
o.x //訪問對象o的x屬性
arr[0] //訪問數組arr的索引爲0的元素
複製代碼

若是.[以前的表達式不是對象或數組,js會將其轉換爲對象,這也是爲何雖然原始類型不能擴展屬性和方法但咱們仍然能直接使用相似str.substring()調用方法。這個過程當中實際發生瞭如下幾個步驟:

  • 將字符串str經過調用new String(str)的方法建立一個實例
  • 在實例上調用substring方法
  • 銷燬實例

同字符串同樣,數字和布爾值也有對應的方法:Number()Bool()null和undefined則沒有包裝對象,訪問它們的屬性會拋出一個類型錯誤

引用類型轉化爲原始類型

引用類型到原始值的轉換則比較複雜,犀牛書說的有點繞且多是因爲翻譯的緣由表達上略有歧義,使我理解上走了很多彎路,總的來講符合如下規則:

  • 對象轉換爲字符串:

    1. 先嚐試調用對象的toString()方法,若是對象擁有這個方法且返回一個原始值,js將這個值轉換爲字符串並返回這個字符串結果;
    2. 若是對象沒有toString()方法,或這個方法並不返回一個原始值,則調用valueOf()方法,若是這個方法存在且返回值是一個原始值,js將這個值轉換爲字符串並返回這個字符串結果;
    3. 不然,拋出一個類型錯誤異常
  • 對象轉換爲數字:

    1. 先嚐試調用對象的valueOf()方法,若是對象擁有這個方法且返回一個原始值,js*將這個值轉換爲數字(須要的話)*並返回這個數字;
    2. 若是對象沒有valueOf()方法,或這個方法並不返回一個原始值,則調用toString()方法,若是這個方法存在且返回值是一個原始值,js將這個值轉換爲數字(若是須要的話)並返回;
    3. 不然,拋出一個類型錯誤異常
  • 對象轉換爲布爾值: 恆爲真值

或許你已經注意到對象轉數字規則中的,」若是須要的話「這個註釋,先不用管它,假設js是永遠須要的,後面會說到是在什麼狀況下不須要。

隱式轉換的常見場景

既然明確了轉化類型目標之後的規則已經明確了,那麼js在作隱式轉換時都是從自身類型到那種類型呢? 這纔是本文的重點,也是我常常迷惑的問題。

我整理了幾種js發生隱式轉化的常見場景,以後如有發現再作補充:

  1. 各類表達式中,包括算數表達式、比較表達式、邏輯表達式;
  2. if()語句中,若條件是單個變量,會將單個變量轉換爲bool值;
  3. 屬性訪問表達式:o.x、 arr[0];

第2種場景很簡單,若是if條件語句中只有單個變量的時候,會將變量轉換爲bool值,若是是表達式則先計算表達式結果再轉換爲bool值,第三種場景則是上文提到的裝箱,也沒必要再說,接下來只具體介紹一下第一種場景。

算術表達式

基本的算術運算符包括二元算術運算符+、-、*、/、%和一元算術運算符+、-、++、--,二元加法運算符比較複雜,隨後單獨講,其他的幾種都很簡單,即將操做數轉化爲數字,全部沒法轉化爲數字的都轉化爲NaN,且操做結果也是NaN, 這裏注意一下一元+運算符便可,平時用的不多,但一些複雜的面試題中可能會出現,只要把操做數轉化爲數字便可。

這裏有兩個額外的tips:

  • 求餘操做符的結果的符號和第一個操做數保持一致,eg:5%2 == 1 5%-2 == 1 -5%2 == -1
  • 一元自增自減運算符要求操做數爲一個左值,左值並不是是在左邊的值而是指變量、對象屬性或數組元素,不然會拋出一個錯誤
  • 後增量的計算時間
二元+運算符

二元加法運算符的複雜之處在於既能對兩個數字作加法,也能作字符串鏈接,行爲表現爲:

  1. 若是其中一個操做數是對象,則遵循上面提到的對象轉爲原始值規則轉換爲原始值,其中又遵循兩點規則:

    • 絕大多數對象遵循對象轉換爲數字的規則,即先嚐試valueOf再調用toString
    • 日期對象類型到原始值的轉換是使用對象到字符串的轉換,即先嚐試toString再調用valueOf
    • 這裏的轉換並不會真的將對象強制轉換爲數字或字符串,而是使用按轉換規則處理後獲得的原始值,例如對象轉數字規則,若是調用toString方法後返回了字符串原始類型則不會再轉爲數字類型返回
  2. 若是其中一個操做數是字符串的話,另外一個操做數也會轉化爲字符串而後進行字符串拼接;

  3. 不然兩個操做數都轉化爲數字(或NaN),而後作加法

關係表達式

關係運算符包括==、===、!=、!==、>、<、>=、<=等,首先要知道嚴格相等運算符===和不嚴格相等運算符!==是不會作類型轉換的,咱們只看列出來的其他幾種關係運算符。若是兩個操做數的類型不一樣,這幾種關係表達式的行爲表現以下:

  1. null == nullundefined == undefinednull == undefined,除此以外,undefinednull與其餘任何結果的比較值都爲false;

  2. NaN和其餘任何類型比較永遠返回false(包括和他本身),NaN == NaN //false

  3. 若是一個操做數是字符串,一個操做數是數字,先將字符串轉換爲數字;

  4. 若是其中一個操做數是bool值,先將其轉換爲數字;

  5. 若是一個操做數是對象,另外一個是數字或字符串,則遵循上面提到的對象轉爲原始值規則轉換爲原始值再進行比較,這裏的轉換和二元+運算符同樣,符合:

    • 絕大多數對象遵循對象轉換爲數字的規則,即先嚐試valueOf再調用toString
    • 日期對象類型到原始值的轉換是使用對象到字符串的轉換,即先嚐試toString再調用valueOf
    • 這裏的轉換並不會真的將對象強制轉換爲數字或字符串,而是使用按轉換規則處理後獲得的原始值,例如對象轉數字規則,若是調用toString方法後返回了字符串原始類型則不會再轉爲數字類型返回
  6. 其餘不一樣類型之間的比較均不相等

總結:關係表達式優先把操做數轉化爲數字,包括對象,只是對象轉化爲數字時直接使用返回的結果並不強制轉化爲數字,Date對象除外;

這裏也有幾個額外的tips:

  • 0 == -0 //true
  • 因此大寫的ASCII字母都小於小寫的ASCII字母,因此在進行字符串比較的時候注意作大小寫轉換,eg: 'Zore' > 'area' // false
  • null === undefined //false 但 null == undefined //true
  • <=>=運算符只是簡單的不大於和不小於,它們在判斷相等的時候並不依賴=====運算符的比較規則
  • 字符串相等比較的特殊狀況
    字符串相等比較的特殊狀況

其餘表達式

其餘表達式包括邏輯表達式、複製表達式和一些未提到的運算符等,在隱式類型轉換中咱們不用太關心,由於他們的轉換會比較明確,其中咱們只須要知道:

  1. 邏輯與&&,邏輯或||是會在特定狀況下短路的,即只計算左邊的表達式不計算右邊的表達式;
  2. 賦值表達式是從右向左結合的;
  3. 基本的運算優先級

以注意一些具備反作用的運算的考察便可,eg: var i = 1; false && i++;這裏的i++表達式並無被計算,除此以外,邏輯非運算符!會將操做數的布爾值求反,能夠用兩次邏輯非運算!!來獲得一個操做數的布爾值。

總結

js的類型轉換的表現知足如下幾點規則:

  1. null == nullundefined == undefinednull == undefined,除此以外,undefinednull與其餘任何結果的比較值都爲false;
  2. NaN和其餘任何類型比較永遠返回false(包括和他本身),NaN == NaN //false
  3. +、==、!=和關係運算中,若是其中一個操做數是對象,執行對象到原始值轉換
  4. 對象到原始值的轉換基本都使用對象到數字的轉換規則即先嚐試valueOf再調用toString;
  5. 在第二點的基礎上日期對象類型到原始值的轉換是使用對象到字符串的轉換,即先嚐試toString再調用valueOf;
  6. +、==、!=和關係運算中的轉換並不會真的將對象強制轉換爲數字或字符串,而是使用按轉換規則處理後獲得的原始值(這就是上面提到的不須要的狀況),例如對象轉數字規則,若是調用toString方法後返回了字符串原始類型則不會再轉爲數字類型返回;
  7. 加號運算符和比較運算符的行爲有所不一樣,前者更偏心字符串,後者更偏心數字。

這篇總結就到這裏了,對我來講已經比較明晰了,但願對大家來講也有所幫助。

面試題練習

先看幾道題目,能夠先作完再看後面的解析(部分來源於參考文章):

1. [1,2] + 1
2. [1,2] - 1
3. [1,2] + [3,4]
4. true + false
5. "number" + 15 + 3
6. 15 + 3 + "number"
7. "foo" + + "bar"
8. "true" == true
9. false == "false"
10. !!"false" == !!"true"
11. ["x"] == "x"
12. [] + null + 1
13. [1,2,3] == [1,2,3]
14. [] + {}
15. {} + []
16. {} + [] + {} + [1]
17. ! + [] + [] + ![]
18. 全部的假值包括哪些?
19. 將一個變量強制轉換爲字符串,你能說幾種方法?它們有什麼優缺點?
20. 什麼樣的處理可讓`a == 1 && a == 2 && a == 3`表達式爲`true`?
複製代碼

解析

  1. 」1,21「

    根據加法運算規則,對象轉換爲原始類型,[1,2]先調用valueOf()獲得它自己,仍是一個引用類型因而繼續調用toString()獲得」1,2「直接使用,再根據規則字符串與數字相加,數字轉換爲字符串作字符串相連

  2. NaN

    根據減法運算規則,操做數直接轉化爲數字,對象轉數字且將toString()的返回值強制轉化爲數字即NaNNaN - 1 仍是NaN

  3. "1,23,4"

    對象轉原始類型且toString()的返回值直接使用,字符串拼接獲得」1,23,4「

  4. 1

    沒有引用類型,沒有字符串,操做數轉數字相加

  5. 」number153「

    表達式從左向右執行

  6. 」18number「

    考察點同上

  7. 」fooNaN「

    表達式會解析成」foo「 + ( + "bar" ),根據一元+運算符,操做數轉數字獲得NaN」foo「 + NaNNaN轉字符串作字符串拼接,注意加號之間的空格,若是無空格就是自增運算符了

  8. false

    ==優先轉數字,其中一個操做符是布爾值,先轉爲數字獲得」true「 == 1,其中一個操做數是字符串,另外一個是數字,先將字符串轉換爲數字獲得 NaN == 1false

  9. false

    考點同上

  10. true

    邏輯非運算符的優先級比關係運算符的優先級高,兩個操做數會先被轉換爲布爾值即true == true,顯然表達式結果爲true

  11. true

    其中一個操做數是對象,按規則轉化爲原始值,且toString()的返回值直接使用"x" == "x"顯然成立

  12. 」null1「

    表達式從左往右執行,[] + null,對象轉原始值獲得」「,連續作字符串拼接獲得」null1「

  13. false

    引用類型比較的是內存地址

  14. "[object Object]"

    14,15題放一塊兒看,[] + {}`` []先轉原始值爲」「{}轉原始值爲"[object Object]"

  15. 0

    這裏有些意外,這涉及到JavaScript的語法解析規則,在這段代碼中,解析器遇到{}後將其解析爲了一個空的代碼塊,直接忽略,因而表達式被解析爲+ [],根據一元操做符+[]轉數字爲0

  16. 」0[object Object]1「

    是14,15的考點結合的升級版

  17. "truefalse"

    根據運算符的優先級表達式解析爲!(+[]) + [] + (![]),即!0 + [] + falsetrue + [] + falsetrue + "" + false

  18. 全部的假值包括

    undefined
    null
    NaN
    0
    -0
    ""
    false
    //還有一種特殊的假值對象
    document.all;    //輸出當前文檔下的全部標籤
    Object.prototype.toString.call(document.all);    //[object HTMLAllCollection]
    typeof document.all;      //undefined
    Boolean(document.all);    //false,意外吧?!!!
    
    複製代碼
  19. 假設變量爲str,將變量強制轉換爲字符串有如下幾種方法:

    • new String(str)

    • toString

      if(str.toString()) str.toString() 
      else Object.prototype.toString.call(str)
      複製代碼
    • str + ""

    • JSON.stringfy(str)

    第一種最爲穩妥,第二種若是對象重寫了toString方法則會影響最終的結果,第三種方法,根據對象轉原始值的規則,是先嚐試調用valueOf再調用toString(),若是對象重寫了自身的valueOf方法或toString()方法則會影響到最終的結果,第四種方法,若是傳入的值自己就是字符串會獲得雙重引號的值,eg: JSON.stringify("11")獲得」「11」「,另外若是對象內有遞歸引用,還會拋出報錯,eg:

    var a = {},b = {};
    a.param = b;
    b.param = a;
    JSON.stringify(a);
    //VM14994:4 Uncaught TypeError: Converting circular structure to JSON
    複製代碼

    除此以外

    [MDN](developer.mozilla.org/zh-CN/docs/…

    還給出瞭如下提醒:

    JSON.stringify()注意事項

  20. 根據對象轉換爲原始值的規則,能夠這樣寫:

    var a = {
      value: [3, 2, 1],
      valueOf: function() { return this.value },
      toString: function() {return this.value.pop()}
    }
    複製代碼

參考

【JS 進階】你真的掌握變量和類型了嗎

17道題完全理解 JavaScript 中的類型轉換

關於數據類型轉換的面試題總結

相關文章
相關標籤/搜索