【精】從206個console.log()徹底弄懂數據類型轉換的前世此生(上)

前言

你盼世界,我盼望你無bug。Hello 你們好!我是霖呆呆!javascript

這一期給你們帶來的是一篇關於JS數據類型轉換的文章,原由主要是前幾天在刷類型轉換的題時忽然感受本身對它們理解的還不夠深入啊,對於什麼[] == ![]、!{} == []這類題老是隻知其一;不知其二,記了忘忘了記。前端

這讓我很苦惱,決心給本身下點猛料,完全弄懂它們的轉換機制而後出幾道魔鬼題來考考本身。java

在寫的時候也是蠻糾結的,開始寫了一版全是題目的,可是後來發現若是全是題目不講其原理的話,一些讀者可能會一臉懵逼...因此後來我又加了關於toString、valueOf、toPrimitive的詳細解析,再配合一些清晰的流程圖,力求能將轉換過程說的清楚明瞭 😁。git

不過預防針可打在前頭,因爲前面1-3節是一些基礎類型之間的轉換,並不難,因此我不會花太多的篇幅在這上面,可能從第4節開始慢慢的有點內味了吧,[陰笑~],以爲本身對基礎類型之間轉換有信心的小夥伴能夠直接跳到第4節看哦。github

但願整篇閱讀下來你的腦子裏並非 "淡黃的長裙...蓬鬆的頭髮..."面試

OK👌,來看看經過閱讀你能夠學習到:數組

  • 其它數據類型轉布爾值
  • 原始值轉字符串
  • 原始值轉數字
  • 原始值轉對象(基本類型的包裝對象)
  • toString
  • Symbol.toStringTag
  • valueOf
  • ToPrimitive的執行機制
  • 對象轉字符串
  • 對象轉數字

(寫的過程當中,看到冴羽大大也發表了一篇關於類型轉換的文章《JavaScript深刻之頭疼的類型轉換(上)》,完了,難道...我已經到了和大佬們心有靈犀的境界了嗎,膨脹膨脹了,能夠借鑑一下,哈哈)瀏覽器

另外「數據類型轉換」系列我是分爲了兩篇文章來寫,這一篇主要是講解String()、Number()這種的轉換方式,對於運算符號+、==這種的轉換以及toPrimitive的一些高級用法我會放在下一篇文章裏面。 嘻嘻,仍是那句話,按部就班嘛。markdown

系列介紹

「數據類型轉換」系列共有兩篇文章:編輯器

建議按順序閱讀,兩篇文章幫你徹底弄懂數據類型轉換 😁。

1. 其它數據類型轉布爾值

轉化爲布爾值的狀況是很簡單的。

當咱們在使用Boolean()來進行轉換時,有以下轉換規則:

參數類型 結果
false、undefined、null、+0、-0、NaN、"" false
除了上面的狀況 true

(另外須要注意的是,若是在使用Boolean()時不傳參數結果也是爲false的)

1.1 數字轉布爾值

數字轉布爾值,只須要記住:

  • 除了0, -0, NaN這三種轉換爲false,其餘的一概爲true

1.1.1 題目一

console.log(Boolean(0))
console.log(Boolean(-0))
console.log(Boolean(NaN))

console.log(Boolean(1))
console.log(Boolean(Infinity))
console.log(Boolean(-Infinity))
console.log(Boolean(100n))
console.log(Boolean(BigInt(100)))
複製代碼

記住上面👆的規律,這邊我把bigInt類型的也拿過來試了一下,發現它也是爲true

所以答案爲:

console.log(Boolean(0)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(NaN)) // false

console.log(Boolean(1)) // true
console.log(Boolean(Infinity)) // true
console.log(Boolean(-Infinity)) // true
console.log(Boolean(100n)) // true
console.log(Boolean(BigInt(100))) // true
複製代碼

1.2 字符串轉布爾值

字符串轉布爾也很簡單,只須要記住:

  • 除了空字符串""都爲true

1.2.1 題目一

console.log(Boolean(""))

console.log(Boolean("1"))
console.log(Boolean("NaN"))
console.log(Boolean("aaa"))
複製代碼

這裏特別要注意的是"NaN",它並非NaN哦,而是一個字符串。

因此答案爲:

console.log(Boolean("")) // false

console.log(Boolean("1")) // true
console.log(Boolean("NaN")) // true
console.log(Boolean("aaa")) // true
複製代碼

1.3 其它類型轉布爾值

其它類型,例如null, undefined, 引用轉布爾值,這些相信你們其實也知道:

  • null、undefinedfalse
  • 引用類型,如對象,數組,類數組,日期,正則都爲true
  • document.all是一個例外,它在非IE下用typeof檢測類型爲undefined,因此會被轉爲false。(考的很少)

(感謝掘友小茗cha提出的document.all)

1.3.1 題目一

var divs = document.getElementsByTagName('div')
console.log(Boolean(null))
console.log(Boolean(undefined))

console.log(Boolean({}))
console.log(Boolean({ name: 'obj' }))
console.log(Boolean([]))
console.log(Boolean(divs))
console.log(Boolean(new Date()))
console.log(Boolean(/(\[|\])/g))

console.log(typeof document.all)
console.log(Boolean(document.all))
複製代碼

結果爲:

var divs = document.getElementsByTagName('div')
console.log(Boolean(null)) // false
console.log(Boolean(undefined)) // false

console.log(Boolean({})) // true
console.log(Boolean({ name: 'obj' })) // true
console.log(Boolean([])) // true
console.log(Boolean(divs)) // true
console.log(Boolean(new Date())) // true
console.log(Boolean(/(\[|\])/g)) // true

console.log(typeof document.all) // undefined
console.log(Boolean(document.all)) // false
複製代碼

(document.all是文檔中全部標籤組成的一個數組變量,包括了文檔對象中全部元素,document.all[]這個數組能夠訪問文檔中全部元素。它在非IE的瀏覽器中是爲undefined的,因此能夠用其來判斷當前瀏覽器是不是IE,不過如今用的已經不多了,我就不展開了)

2. 原始值轉字符串

對於原始值轉字符串,也有如下總結:

參數類型 結果
Undefined "undefined"
Null "null"
Boolean 若是參數是 true,返回 "true"。參數爲 false,返回 "false"
Number 能夠看題目2.1
String 返回與之相等的值
Symbol "Symbol()"

來作幾道題強化一下吧。

2.1 數字轉字符串

2.1.1 題目一

console.log(String(0))
console.log(String(1))
console.log(String(100))
console.log(String(NaN))
console.log(String(10n))
console.log(String(10n) === '10')
複製代碼

bigInt類型會被當成數字來處理。

答案爲:

console.log(String(0)) // '0'
console.log(String(1)) // '1'
console.log(String(100)) // '100'
console.log(String(NaN)) // 'NaN'
console.log(String(10n)) // '10'
console.log(String(10n) === '10') // true
複製代碼

2.2 Boolean、Symbol轉爲字符串

2.2.1 題目一

這三種類型轉換爲字符串,比較簡單:

console.log(String(true))
console.log(String(false))
console.log(String(Symbol(1)))
複製代碼

答案:

console.log(String(true)) // 'true'
console.log(String(false)) // 'false'
console.log(String(Symbol(1))) // 'Symbol(1)'
複製代碼

3. 原始值轉數字

參數類型 結果
Undefined NaN
Null +0
Boolean 若是參數是 true,返回 1。參數爲 false,返回 +0
Number 返回與之相等的值
String 純數字的字符串(包括小數和負數、各進制的數),會被轉爲相應的數字,不然爲NaN
Symbol 使用Number()轉會報錯

3.1 題目一

先來看看你們都知道的string、null、undefined、Symbol轉數字

console.log(Number("1"))
console.log(Number("1.1"))
console.log(Number("-1"))
console.log(Number("0x12"))
console.log(Number("0012"))

console.log(Number(null))
console.log(Number("1a"))
console.log(Number("NaN"))
console.log(Number(undefined))
console.log(Number(Symbol(1)))
複製代碼

答案爲:

console.log(Number("1")) // 1
console.log(Number("1.1")) // 1.1
console.log(Number("-1")) // -1
console.log(Number("0x12")) // 18
console.log(Number("0012")) // 12

console.log(Number(null)) // 0
console.log(Number("1a")) // NaN
console.log(Number("NaN")) // NaN
console.log(Number(undefined)) // NaN
console.log(Number(Symbol(1))) // TypeError: Cannot convert a Symbol value to a number
複製代碼

其實很好記:

  • 純數字的字符串(包括小數和負數、各進制的數),會被轉爲相應的數字
  • null轉爲0
  • Symbol會報錯
  • 其它的基本類型,包括非純數字的字符串、NaNundefined都會被轉爲NaN

3.2 題目二

Boolean類型轉數字)

布爾值轉數字也是很是簡單的,只有兩種狀況:

console.log(Number(true)) // 1
console.log(Number(false)) // 0
複製代碼

3.3 題目三

還有一種你們可能會用到的轉數字的方式,那就是使用:

  • parsetInt,將結果轉換爲整數
  • parseFloat,將結果轉換爲整數或者浮點數

它們在轉換爲數字的時候是有這麼幾個特色的:

  • 若是字符串以0x或者0X開頭的話,parseInt會以十六進制數轉換規則將其轉換爲十進制,而parseFloat會解析爲0
  • 它們兩在解析的時候都會跳過開頭任意數量的空格,日後執行
  • 執行過程當中會盡量多的解析數值字符,若是碰到不能解析的字符則會跳出解析忽略後面的內容
  • 若是第一個不是非空格,或者開頭不是0x、-的數字字面量,將最終返回NaN

來看幾道題練習一下 😄:

console.log(parseInt('10')) // 10
console.log(parseFloat('1.23')) // 1.23

console.log(parseInt("0x11")) // 17
console.log(parseFloat("0x11")) // 0

console.log(parseInt(" 11")) // 11
console.log(parseFloat(" 11")) // 11

console.log(parseInt("1.23a12")) // 1
console.log(parseFloat("1.23a12")) // 1.23

console.log(parseInt(" 11")) // 11
console.log(parseFloat(" 11")) // 11

console.log(parseInt("1a12")) // 1
console.log(parseFloat("1.23a12")) // 1.23

console.log(parseInt("-1a12")) // -1
console.log(parseFloat(".23")) // 0.23
複製代碼

一直作到如今感受都還挺簡單的哈 😄。

4. 原始值轉對象

原始值,也就是基礎數據類型。

4.1 String對象

讓咱們先來認識一個叫作String的對象,它的原型鏈是這樣的:

能夠看到,它本質上是一個構造函數,String.__proto__指向的是Function.prototype

String其實有兩種用法,一種是配合new來當構造函數用,一種是不用new

  • 當 String() 和運算符 new 一塊兒做爲構造函數使用時,它返回一個新建立的 String 對象,存放的是字符串 ss 的字符串表示(不過自從推出了Symbol以後就不推薦使用new String這種作法了)。
  • 當不用 new 運算符調用 String() 時,它只把 s 轉換成原始的字符串,並返回轉換後的值

什麼意思呢 🤔️?通俗點來講:

typeof String(1) // 'string'
typeof new String(1) // 'object'
複製代碼

使用typeof會發現類型都是不一樣的。

4.2 基本類型的包裝對象

哈哈哈。

和它一塊兒的其實還有另外兩個"親兄弟"Number、Boolean

以及它的"表哥表妹"Symbol、BigInt。 😄

爲何說Number、Boolean就是親的,然後面兩個就是表的呢 🤔️?

這個霖呆呆是以類似程度來區分的。

也就是說Number、BooleanString同樣都有兩種用法,帶new和不帶new

Symbol、BigInt就只能不帶new使用。(由於它們是ES6以後出來的,對它們調用new會報錯)

因此你會看到這個現象:

console.log(Number(1)) // 1
console.log(new Number(1)) // Number{1}
console.log(Boolean(true)) // true
console.log(new Boolean(true)) // Boolean{true}

console.log(Symbol(1)) // Symbol(1)
console.log(BigInt(1)) // 1n
console.log(new Symbol(1)) // TypeError: Symbol is not a constructor
console.log(new BigInt(1)) // TypeError: BigInt is not a constructor
複製代碼

而上面的Number{1}、Boolean{true},它就是我要介紹的基本類型的包裝對象,也被稱爲基本類型的包裝類,也能夠叫作原始值包裝對象(有不少的叫法不過你們應該都知道它表示的就是這個意思)。

能夠看到要想產生一個基礎數據類型的包裝對象只須要使用new來調用它們各自的構造函數便可:

console.log(new Number(1)) // Number{1}
console.log(new String('1')) // String{1}
console.log(new Boolean(true)) // Boolean{true}
複製代碼

這個基本類型的包裝對象有什麼特色呢?

  • 使用typeof檢測它,結果是object,說明它是一個對象
  • 使用toString()調用的時候返回的是原始值的字符串(題6.8.3中會提到)

But!!!

前面已經說到了,目前ES6規範是不建議用new來建立基本類型的包裝類的,我想大概是爲了和Symbol、BigInt它們統一吧。

那麼如今更推薦用什麼方式來建立基本類型的包裝類呢?

唔...那就是Object這個構造函數。

4.3 Object()

Object()構造函數它能夠接收一個任意類型的變量,而後進行不一樣的轉換。

也就是說七種基本數據類型,或者引用數據類型你均可以傳入進去。

不過這裏我主要是爲了介紹基本數據類型轉對象,因此就以幾個基本數據類型來作分析:

console.log(new Object('1')) // String{'1'}
console.log(new Object(1)) // Number{1}
console.log(new Object(true)) // Boolean{true}
console.log(new Object(Symbol(1))) // Symbol{Symbol(1)}
console.log(new Object(10n)) // BigInt{10n}

console.log(new Object(null)) // {}
console.log(new Object(undefined)) // {}
複製代碼

能夠看到,你傳入的基本數據類型是什麼類型的,那麼最終的結果就會轉爲對應的包裝類,可是對於null、undefined它們會被忽略,生成的會是一個空對象。

總結-原始值轉對象

原始值轉對象主要有如下總結:

  • String、Number、Boolean有兩種用法,配合new使用和不配合new使用,可是ES6規範不建議使用new來建立基本類型的包裝類。
  • 如今更加推薦用new Object()來建立或轉換爲一個基本類型的包裝類。

基本類型的包裝對象的特色:

  • 使用typeof檢測它,結果是object,說明它是一個對象
  • 使用toString()調用的時候返回的是原始值的字符串(題6.8.3中會提到)

5. 對象轉字符串/數字前期準備

對象轉字符串和數字的過程比較複雜,會涉及到一個可能你們以前沒有聽到過的方法:toPrimitive()

它的做用其實就是輸入一個值,而後返回一個必定是基本類型的值,不然會拋出一個類型錯誤異常。

先上一張執行流程圖,讓你們感覺一下絕望、孤獨、寂寞、冷...

雖然它的功能會有些複雜,不過問題不大,待看完後面的內容以後你就能搞懂它了,在介紹toPrimitive()以前,我得先詳細介紹一下toString()valueOf()方法才行,由於弄懂了它們你才能完全吃透toPrimitive()。😄

6. toString()

6.1 toString()存在於哪裏

在此以前,我翻了不少關於toString()的資料,大多都是介紹了它的用法,可是它真正存在於哪裏呢?

可能比較常見的一種說法是它存在於Object的原型對象中,也就是Object.prototype上,那麼對於基本數據類型,Number、String、Boolean、 Symbol、BigInt呢?它們自身有這個方法嗎?或者它們的原型對象上有嗎?

本着一探到底的精神,我打印出了NumberNumber.prototype

console.log(Number)
console.log(Number.prototype)
複製代碼

而後我發現了幾件事:

  • Number只是一個構造函數,打印出來顯示的會是源代碼
  • Number.prototype上確實也有toString()
  • Number.prototype.__proto__也就是Object.prototype上也有toString()

而後我又試了一下String、Boolean、Symbol發現結果也和上面同樣。

其實不難理解,看過《💦【何不三連】作完這48道題完全弄懂JS繼承(1.7w字含辛整理-返璞歸真)》的小夥伴都知道,全部對象的原型鏈到最後都會指向Object.prototype,算是都"繼承"了Object的對象實例,所以都能使用toString()方法,可是對於不一樣的內置對象爲了能實現更適合自身的功能需求,都會重寫該方法,因此你能夠看到Number.prototype上也會有該方法。

因此咱們能夠先得出第一個結論:

  • 除了null、undefined之外的其它數據類型(基本數據類型+引用數據類型),它們構造函數的原型對象上都有toString()方法
  • 基本數據類型構造函數原型對象上的toString()會覆蓋Object原型對象上的toString()方法

(固然,等你看到6.9以後你就會發現這種說法其實並不太準確,可是大多數時候咱們都只是關心誰能夠用它,而不是它存在於哪裏)

6.2 誰能夠調用toString()

這個問題,其實在上面👆已經給出答案了,全部對象除了null、undefined之外的任何值均可以調用toString()方法,一般狀況下它的返回結果和String同樣。

其實這裏,咱們最容易搞混的就是StringtoString

以前老是爲了將某個類型轉爲字符串胡亂的用這兩個屬性。

  • String是一個相似於Function這樣的對象,它既能夠當成對象來用,用它上面的靜態方法,也能夠當成一個構造函數來用,建立一個String對象
  • toString它是除了null、undefined以外的數據類型都有的方法,一般狀況下它的返回結果和String同樣。

可是就會有小夥伴問了,那爲何'1'.toString()也能夠成功呢?那是由於代碼在運行的時候實際上是作了轉換爲包裝類的處理,相似於下面這段代碼:

var str = new Object('1');
str.toString();
str = null;
複製代碼

過程解析:

  • 建立Object實例,將s變爲了String{"1"}對象
  • 調用實例方法toString()
  • 用完以後當即銷燬這個實例

但是咱們以前不是看到了一個String的東西嗎?這裏的第一步爲何不能使用var str = new String('1')呢?

其實前面也已經說到了,因爲SymbolBigInt它們是不能使用new來調用的,會報錯,而且目前ES6的規範也不推薦使用new來建立這種基本類型的包裝類,因此這裏使用的是new Object()

可是當咱們在代碼中試圖使用1.toString(),發現編輯器已經報錯不容許咱們這樣作了。

難道數字就不能夠嗎 🤔️?最開始會有這麼奇怪的疑問是由於咱們都忽視了一件事,那就是.它也是屬於數字裏的一部分啊 😂。

好比1.21.3。因此當你想要使用1.toString()的時候,JavaScript的解釋器會把它做爲數字的一部分,這樣就至關於(1.)toString了,很顯然這是一段錯誤的代碼。

既然這樣的話,若是我還(喝唔安黃)給代碼一個.是否是就能夠了,因而我嘗試了一下:

console.log(1.1.toString())
複製代碼

發現它居然能正常打印出來:

"1.1"
複製代碼

這也就再次證實了1.toString()會將.歸給1所屬,而不是歸給toString()

固然若是你用的一個變量來承載這個數字的話也是能夠的:

var num = 1;
console.log(num.toString()) // "1"

// 或者
console.log((1).toString()) // "1"
複製代碼

因此在此咱們只須要先記住誰能夠調用toString

  • 除了null、undefined的其它基本數據類型還有對象均可以調用它
  • 在使用一個數字調用toString()的時候會報錯,除非這個數字是一個小數或者是用了一個變量來盛放這個數字而後調用。(1.1.toString()或者var a = 1; a.toString();)

6.3 toString()的call調用

可能你們看的比較多的一種用法是這樣的:

Object.prototype.toString.call({ name: 'obj' }) // '[object Object]'
複製代碼

先來點硬知識,Object.prototype.toString這個方法會根據這個對象的[[class]]內部屬性,返回由 "[object " 和 class 和 "]" 三個部分組成的字符串。

啥意思?[[class]]內部屬性是個啥 🤔️?

這裏你還真別想多,你就按字面意思來理解它就行了,想一想,class 英文單詞的意思->

那好,我就認爲它表明的是一類事物就好了。

就好比

  • 數組是一類,它的[[class]]Array
  • 字符串是一類,它的[[class]]String
  • arguments是一類,它的[[class]]Arguments

另外,關於[[class]]的種類是很是多的,你也不須要記住所有,只須要知道一些經常使用的,基本的,好理解的就能夠了。

因此回到Object.prototype.toString.call()這種調用方式來,如今你能夠理解它的做用了吧,它可以幫助咱們準確的判斷某個數據類型,也就是辨別出是數組仍是數字仍是函數,仍是NaN。😊

另外鑑於它的返回結果是"[object Object]"這樣的字符串,並且前面的"[object ]"這八個字符串都是固定的(包括"t"後面的空格),因此咱們是否是能夠封裝一個方法來只拿到"Object"這樣的字符串呢?

很簡單,上代碼:

function getClass (obj) {
    let typeString = Object.prototype.toString.call(obj); // "[object Object]"
    return typeString.slice(8, -1);
}
複製代碼

能夠看到,我給這個函數命名爲getClass,這也就呼應了它本來的做用,是爲了拿到對象的[[class]]內部屬性。

另外,在拿到了"[object Object]"字符串以後,是用了一個.slice(8, -1)的字符串截取功能,去除了前八個字符"[object ]"和最後一個"]"

如今讓咱們來看看一些常見的數據類型吧:

function getClass(obj) {
    let typeString = Object.prototype.toString.call(obj); // "[object Array]"
    return typeString.slice(8, -1);
}
console.log(getClass(new Date)) // Date
console.log(getClass(new Map)) // Map
console.log(getClass(new Set)) // Set
console.log(getClass(new String)) // String
console.log(getClass(new Number)) // Number
console.log(getClass(true)) // Boolean
console.log(getClass(NaN)) // Number
console.log(getClass(null)) // Null
console.log(getClass(undefined)) // Undefined
console.log(getClass(Symbol(42))) // Symbol
console.log(getClass({})) // Object
console.log(getClass([])) // Array
console.log(getClass(function() {})) // Function
console.log(getClass(document.getElementsByTagName('p'))) // HTMLCollection

console.log(getClass(arguments)) // Arguments
複製代碼

"霖呆呆,這麼多,這是人乾的事嗎?"

"性平氣和,記住一些經常使用的就好了..."

"啪!"

6.4 toString.call()與typeof的區別

好滴👌,經過剛剛的學習,咱們瞭解到了,toString.call這種方式是爲了獲取某個變量更加具體的數據類型。

咦~說到數據類型,咱們原來不是有一個typeof嗎?它和toString.call()又啥區別?

首先幫你們回顧一下typeof它的顯示規則:

  • 對於原始類型來講(也就是number、string這種),除了null均可以顯示正確的類型
  • null由於歷史版本的緣由被錯誤的判斷爲了"object"
  • 對於引用類型來講(也就是object、array這種),除了函數都會顯示爲"object"
  • 函數會被顯示爲function

因此呀,typeof的缺點很明顯啊,我如今有一個對象和一個數組,或者一個日期對象,我想要仔細的區分它,用typeof確定是不能實現的,由於它們獲得的都是"object"

因此,採用咱們封裝的getClass()顯然是一個很好的選擇。

(固然,瞭解instanceof的小夥伴可能也知道,用instanceof去判斷也是能夠的,不過這邊不扯遠,具體能夠看一下三元大大的《(建議收藏)原生JS靈魂之問, 請問你能接得住幾個?(上)》,裏面的第二篇有提到這個問題。或者你能夠期待一下呆呆後面的文章,那裏也會詳細講到哦,這裏先賣個關子,哈哈)

6.5 toString.call()調用和toString()的區別

剛剛咱們說到的toString()的用法是使用toString.call()的方式,那麼更多的使用確定是某個變量後面之間接着toString()呀,就好比這樣:

true.toString() // 'true'
複製代碼

請你們必定要區分清楚true.toString()Object.prototype.toString.call(true)這兩種用法啊:

  • true.toString()是將true轉爲字符串
  • toString.call(true)是獲取true 它的[[class]]內部屬性:
true.toString() // 'true'
Object.prototype.toString.call(true) // "[object Boolean]"
複製代碼

因爲toString.call()這種用法以前說的已經比較詳細了,因此下面的內容都是圍繞着true.toString()這種調用方式來說。

6.6 不一樣狀況下的toString()

那麼在不一樣的數據類型調用toString()會有什麼不一樣呢?

這裏我主要是分爲兩大塊來講:

  • 基本數據類型調用
  • 引用類型調用

6.7 基本數據類型調用toString

對於基本數據類型來調用它,超級簡單的,你就想着就是把它的原始值換成了字符串而已:

console.log('1'.toString()) // '1'
console.log(1.1.toString()) // '1.1'
console.log(true.toString()) // 'true'
console.log(Symbol(1).toString()) // 'Symbol(1)'
console.log(10n.toString()) // '10'
複製代碼

6.8 引用類型調用toString

比較難的部分是引用類型調用toString(),並且咱們知道引用類型根據[[class]]的不一樣是分了不少類的,好比有ObjectArrayDate等等。

那麼不一樣類之間的toString()是否也不一樣呢 🤔️?

沒錯,不一樣版本的toString主要是分爲:

  • 數組的toString方法是將每一項轉換爲字符串而後再用","鏈接
  • 普通的對象(好比{name: 'obj'}這種)轉爲字符串都會變爲"[object Object]"
  • 函數(class)、正則會被轉爲源代碼字符串
  • 日期會被轉爲本地時區的日期字符串
  • 原始值的包裝對象調用toString會返回原始值的字符串

好的👌,扯了這麼多知識點,終於能夠先上幾道題了 😁。

(沒有題目作我好難受~)

6.8.1 題目一

(數組的toString()用法)

先來看點簡單的:

console.log([].toString())
console.log([1].toString())
console.log([1, 2].toString())
console.log(['1', '2'].toString())

console.log(['', ''].toString())
console.log([' ', ' '].toString())
複製代碼

答案:

console.log([].toString()) // ""
console.log([1].toString()) // "1"
console.log([1, 2].toString()) // "1,2"
console.log(['1', '2'].toString()) // "1,2"

console.log(['', ''].toString()) // ","
console.log([' ', ' '].toString()) // " , "
複製代碼

沒啥難度。

須要注意的可能就是[].toString()的時候,因爲數組一項都沒有,因此獲得的確定是一個空字符串。

另外須要注意的是最後兩個,一個是徹底的空字符串,一個是帶了空格的。

6.8.2 題目二

(非數組類型的其它對象)

console.log({}.toString())
console.log({name: 'obj'}.toString())

console.log(class A {}.toString())
console.log(function () {}.toString())
console.log(/(\[|\])/g.toString())
console.log(new Date().toString())
複製代碼

依照上面👆的第2,3,4條規則,答案會爲:

'[object Object]'
'[object Object]'

'class A {}'
'function () {}'
'/(\[|\])/g'
'Fri Mar 27 2020 12:33:16 GMT+0800 (中國標準時間)'
複製代碼

6.8.3 題目三

(原始值包裝對象調用toString()

原始值包裝對象在上面👆的第四章已經講到了,也就是:

Number{1}
String{'1'}
Boolean{true}
複製代碼

這樣的對象。

當它們在調用toString()方法的時候,會返回它們原始值的字符串,就像這樣:

console.log(new Object(true).toString()) // "true"
console.log(new Object(1).toString()) // "1"
console.log(new Object('1').toString()) // "1"
console.log(new Object(Symbol(1)).toString()) // "Symbol(1)"
console.log(new Object(BigInt(10)).toString()) // "10"
複製代碼

6.8.4 題目四

Map、Set類型調用toString

在作題的時候,我又想着測試一下Map、Set類型調用toString會是什麼樣的。

console.log(new Map().toString())
console.log(new Set().toString())
console.log(new Array(['1']).toString())
複製代碼

發現結果居然是:

console.log(new Map().toString()) // "[object Map]"
console.log(new Set().toString()) // "[object Set]"
console.log(new Array(['1']).toString()) // "1"
複製代碼

這看的我有點懵了,怎麼前面兩個的結果有點像是Object.prototype.toString.call()的調用結果呢?而若是是數組的話,卻又遵循了數組轉字符串的轉換規則...

啊啊啊啊...好不容易弄懂了一些,這怎麼又跑出來個Map、Set

好奇心趨勢着我將new Map()這個實例對象打印出來看看:

console.log(new Map())
複製代碼

嘶~

我好像嗅到了一絲八卦的氣息,我發現Map.prototype和並無和Number.prototype同樣有它自身的toString()方法,而只是Object.prototype上纔有。

而且好像有一個咱們歷來沒有見過的屬性:Symbol(Symbol.toStringTag),並且它的值正好是"Map"

6.9 Symbol.toStringTag

不懂就查,搜了一波Symbol.toStringTag以後,我就恍然大悟了。

《Symbol.toStringTag》上是這樣描述它的:

Symbol.toStringTag公知的符號是在建立對象的默認字符串描述中使用的字符串值屬性。它由該Object.prototype.toString()方法在內部訪問。

看不懂不要緊,你這樣理解就能夠了,它其實就是決定了剛剛咱們提到全部數據類型中[[class]]這個內部屬性是什麼。

好比數字,咱們前面獲得的[[class]]Number,那我就能夠理解爲數字這個類它的Symbol.toStringTag返回的就是Number

只不過在以前咱們用到的Number、String、Boolean中並無Symbol.toStringTag這個內置屬性,它是在咱們使用toString.call()調用的時候纔將其辨別返回。

而剛剛咱們打印出了new Map(),能夠看到Symbol.toStringTag它是確確實實存在於Map.prototype上的,也就是說它是Map、Set內置的一個屬性,所以當咱們直接調用toString()的時候,就會返回"[object Map]"了。

額,咱們是否是就能夠這樣理解呢?

  • 沒有Symbol.toStringTag內置屬性的類型在調用toString()的時候至關因而String(obj)這樣調用轉換爲相應的字符串
  • Symbol.toStringTag內置屬性的類型在調用toString()的時候會返回相應的標籤(也就是"[object Map]"這樣的字符串)

咱們經常使用的帶有Symbol.toStringTag內置屬性的對象有:

console.log(new Map().toString()) // "[object Map]"
console.log(new Set().toString()) // "[object Set]"
console.log(Promise.resolve().toString()) // "[object Promise]"
複製代碼

並且我發現了它和Symbol.hasInstance同樣,能夠容許咱們自定義標籤。

(Symbol.hasInsance的做用是自定義instanceof的返回值)

什麼是自定義標籤呢 🤔️?

也就是說,假如咱們如今建立了一個類,而且用toString.call()調用它的實例對象是會有以下結果:

class Super {}
console.log(Object.prototype.toString.call(new Super())) // "[object Object]"
複製代碼

很好理解,由於產生的new Super()是一個對象嘛,因此打印出的會是"[object Object]"

可是如今有了Symbol.toStringTag以後,咱們能夠改後面的"Object"

好比我重寫一下:

class Super {
  get [Symbol.toStringTag] () {
    return 'Validator'
  }
}
console.log(Object.prototype.toString.call(new Super())) // "[object Validator]"
複製代碼

這就是Symbol.toStringTag的厲害之處,它可以容許咱們自定義標籤。

可是有一點要注意了,Symbol.toStringTag重寫的是new Super()這個實例對象的標籤,而不是重寫Super這個類的標籤,也就是說這裏有區別的:

class Super {
  get [Symbol.toStringTag] () {
    return 'Validator'
  }
}
console.log(Object.prototype.toString.call(Super)) // "[object Function]"
console.log(Object.prototype.toString.call(new Super())) // "[object Validator]"
複製代碼

由於Super它自己仍是一個函數,只有Super產生的實例對象纔會用到咱們的自定義標籤。

總結-toString()

內容好多啊,咱們總結着理順來,這樣纔好記。

誰能夠調用toString()?

  • 除了null、undefined的其它基本數據類型還有對象均可以調用它,一般狀況下它的返回結果和String同樣。
  • 在使用一個數字調用toString()的時候會報錯,除非這個數字是一個小數或者是用了一個變量來盛放這個數字而後調用。(1.1.toString()或者var a = 1; a.toString();)

Object.prototype.toString.call()是作什麼用的?

  • 返回某個數據的內部屬性[[class]],可以幫助咱們準確的判斷出某個數據類型
  • typeof判斷數據類型更加的準確

不一樣數據類型調用toString()

  • 原始數據類型調用時,把它的原始值換成了字符串
  • 數組的toString方法是將每一項轉換爲字符串而後再用","鏈接
  • 普通的對象(好比{name: 'obj'}這種)轉爲字符串都會變爲"[object Object]"
  • 函數(class)、正則會被轉爲源代碼字符串
  • 日期會被轉爲本地時區的日期字符串
  • 原始值的包裝對象調用toString會返回原始值的字符串
  • 擁有Symbol.toStringTag內置屬性的對象在調用時會變爲對應的標籤"[object Map]"

Symbol.toStringTag

  • 它是某些特定類型的內置屬性,好比Map、Set、Promise
  • 主要做用是能夠容許咱們自定義標籤,修改Object.prototype.toString.call()的返回結果

7. valueOf()

接下來要介紹的是toString()的孿生兄弟valueOf,爲何說是它的孿生兄弟呢 🤔️?

由於它們有不少相同的特性,好比前面咱們提到的toString()的存在位置,咱們能夠回頭看看6.1的那張圖,發現有toString() 的地方也有valueOf()

另外一個要介紹它的重要緣由是在對象轉基礎數據類型中,與toString()相輔相成的就是它了。

它的做用主要是:

把對象轉換成一個基本數據的值

因此咱們能夠看出它兩的區別:

  • toString主要是把對象轉換爲字符串
  • valueOf主要把對象轉換成一個基本數據的值

讓咱們先來看看valueOf的基本用法吧。

7.1 基本數據類型調用valueOf()

基本數據類型的調用也是很簡單的,它只要返回調用者本來的值就能夠了:

console.log('1'.valueOf()) // '1'
console.log(1.1.valueOf()) // 1.1
console.log(true.valueOf()) // true
console.log(Symbol(1).valueOf()) // Symbol(1)
console.log(10n.valueOf()) // 10n
複製代碼

看着好像沒變啊,沒錯,因此你能夠用下面👇的方式來驗證一下:

var str = '1'
console.log(str.valueOf() === str) // true
複製代碼

7.2 引用類型調用valueOf()

引用類型調用valueOf()並不難,你只須要記住:

  • 非日期對象的其它引用類型調用valueOf()默認是返回它自己
  • 而日期對象會返回一個1970 年 1 月 1 日以來的毫秒數

好比:

console.log([].valueOf()) // []
console.log({}.valueOf()) // {}
console.log(['1'].valueOf()) // ['1']
console.log(function () {}.valueOf()) // ƒ () {}
console.log(/(\[|\])/g.valueOf()) // /(\[|\])/g
console.log(new Date().valueOf()) // 1585370128307
複製代碼

總結-valueOf()

valueOf()的基本用法

  • 基本數據類型調用,返回調用者本來的值
  • 非日期對象的其它引用類型調用valueOf()默認是返回它自己
  • 而日期對象會返回一個1970 年 1 月 1 日以來的毫秒數(相似於1585370128307)。

8. toPrimitive

弄懂了難啃的toString()valueOf(),終於到了咱們的主角toPrimitive...

淚牛滿面 😢。

不過不可大意,它纔是最難啃的那塊知識點。

先讓咱們來看看它的函數語法:

ToPrimitive(input, PreferredType?)
複製代碼

參數:

  • 參數一:input,表示要處理的輸入值
  • 參數二:PerferredType,指望轉換的類型,能夠看到語法後面有個問號,表示是非必填的。它只有兩個可選值,NumberString

而它對於傳入參數的處理是比較複雜的,如今讓咱們來看看開篇的那幅流程圖:

根據流程圖,咱們得出了這麼幾個信息:

  1. 當不傳入 PreferredType 時,若是 input 是日期類型,至關於傳入 String,不然,都至關於傳入 Number。
  2. 若是是 ToPrimitive(obj, Number),處理步驟以下:
  • 若是 obj 爲 基本類型,直接返回
  • 不然,調用 valueOf 方法,若是返回一個原始值,則 JavaScript 將其返回。
  • 不然,調用 toString 方法,若是返回一個原始值,則 JavaScript 將其返回。
  • 不然,JavaScript 拋出一個類型錯誤異常。
  1. 若是是 ToPrimitive(obj, String),處理步驟以下:
  • 若是 obj爲 基本類型,直接返回
  • 不然,調用 toString 方法,若是返回一個原始值,則 JavaScript 將其返回。
  • 不然,調用 valueOf 方法,若是返回一個原始值,則 JavaScript 將其返回。
  • 不然,JavaScript 拋出一個類型錯誤異常。

(總結來源《冴羽-JavaScript深刻之頭疼的類型轉換(上)》)

上面👆的圖其實只是看着很複雜,細心的小夥伴可能會發現,在圖裏紅框裱起來的地方,只有toString()valueOf()方法的執行順序不一樣而已。

若是 PreferredType 是 String 的話,就先執行 toString()方法

若是 PreferredType 是 Number 的話,就先執行 valueOf()方法

霖呆呆建議你先本身在草稿紙上將這幅流程圖畫一遍,以後再來作題有助於記憶 😁。

9. 對象轉字符串

9.1 題目一

(最基本的轉換)

好吧,呆呆,我看你扯了這麼多toPrimitive的轉換流程,可我也沒看出有什麼實際的用處啊。

這...沒啊,其實咱們很早就用上了啊,只不過你以前可能不知道而已。

好比當咱們使用String()來轉換一個對象爲字符串的時候:

console.log(String({}))
複製代碼

你們都知道結果是:

"[object Object]"
複製代碼

但它爲何是這樣呢?看着結果和toString()調用的結果好像啊。

這裏其實就用到了toPrimitive的轉換規則呀。

你看看,咱們把上面👆的代碼換成toPrimitive的僞代碼看看:

toPrimitive({}, 'string')
複製代碼

OK👌,來回顧一下剛剛的轉換規則:

  1. input{},是一個引用類型,PerferredTypestring
  2. 因此調用toString()方法,也就是{}.toString()
  3. {}.toString()的結果爲"[object Object]",是一個字符串,爲基本數據類型,而後返回,到此結束。

哇~

是否是一切都說得通了,好像不難吧 😁。

沒錯,當使用String()方法的時候,JS引擎內部的執行順序確實是這樣的,不過有一點和剛剛提到的步驟不同,那就是最後返回結果的時候,其實會將最後的基本數據類型再轉換爲字符串返回。

也就是說上面👆的第三步咱們得拆成兩步來:

  1. {}.toString()的結果爲"[object Object]",是一個字符串,爲基本數據類型
  2. 將這個"[object Object]"字符串再作一次字符串的轉換而後返回。(由於"[object Object]"已是字符串了,因此原樣返回,這裏看不出有什麼區別)

將最後的結果再轉換爲字符串返回這一步,其實很好理解啊。你想一想,我調用String方法那就是爲了獲得一個字符串啊,你要是給我返回一個number、null啊什麼的,那不是隔壁老王乾的事嘛~

(咳咳,霖呆呆的真名姓王哈 [害羞~])

9.2 題目二

上面👆的轉換好像並不能看出來最後會轉爲字符串那一步的效果啊,那麼來看看這道題:

console.log(String(null))
console.log(String(new Object(true)))
複製代碼

想一想這裏的轉換規則。

對於String(null)

  • 傳入的input是個基礎數據類型,這簡單啊,直接返回它就能夠了
  • 返回的值是一個null,而後再把它轉爲字符串"null",因此最後返回的是"null"

對於String(new Object(true))

  • 傳入的new Object(true)是一個基本類型的包裝類Boolean{true}
  • 它也是屬於引用類型,所以會調用toString()
  • 而基本類型的包裝類咱們在題6.8.3中已經說到了,它調用toString()方法是會返回原始值的字符串,也就是"true"
  • 返回值"true"是基本數據類型,最後再進行一層字符串轉換(仍是它自己),而後返回"true"

答案:

console.log(String(null)) // "null"
console.log(String(new Object(true))) // true
複製代碼

若是你能看到這的話,怎樣?是否是有點那啥感受了。

9.3 題目三

(數組轉字符串)

數組轉字符串我總結了一下主要是這樣:

  • 空數組[]是被轉換爲空字符串""
  • 如果數組不爲空的話,則將每一項轉換爲字符串而後再用","鏈接

配合着引用類型轉字符串我畫了一張圖。

先來看點簡單的:

console.log(String([]))
console.log(String([1]))
console.log(String([1, 2]))
console.log(String(['1', '2']))
複製代碼

答案:

console.log(String([])) // ""
console.log(String([1])) // "1"
console.log(String([1, 2])) // "1,2"
console.log(String(['1', '2'])) // "1,2"
複製代碼

沒啥難度。

讓咱們用toPrimitive的轉換規則來講一下:

對於String([1, 2])

  • input爲數組[1, 2],所以使用toString()方法調用
  • [1, 2]轉爲字符串爲"1,2",字符串"1,2"爲原始數據類型,則返回(因爲返回值都是字符串我就省略還有一個字符串的轉換過程不說了)

9.4 題目四

讓咱們加上Boolean、函數、NaN看看:

console.log(String([true, false]))
console.log(String([NaN, 1]))

console.log(String([function () {}, 1]))
console.log(String([{ name: 'obj' }, { name: 'obj2' }]))
複製代碼

解析:

  • 類型全都是數組,就是將數組的每一項轉換爲字符串而後用","鏈接
  • 前兩個都沒啥問題
  • 第三個,函數轉爲字符串是其源代碼字符串,也就是"function () {}"
  • 第四個,裏面的每一項是一個對象,且轉爲字符串爲"[object, Object]",因此結果會有兩個"[object Object]"","鏈接

答案爲:

console.log(String([true, false])) // "true,false"
console.log(String([NaN, 1])) // "NaN,1"

console.log(String([function () {}, 1])) // "function () {},1"
// "[object Object],[object Object]"
console.log(String([{ name: 'obj' }, { name: 'obj2' }]))
複製代碼

因此作這類題時,你通常只要謹記這個準則:

  • 如果數組不爲空的話,則將每一項轉換爲字符串而後再用","鏈接

就能夠了,而後再看裏面具體的每一項會被轉成什麼。

9.5 題目五

(日期類型轉字符串)

console.log(String(new Date()))
console.log(String(new Date('2020/12/09')))
複製代碼

日期類型的對象轉字符串在題6.8.2中也已經說到過了,它會被轉爲本地時區的日期字符串,因此結果爲:

console.log(String(new Date())) // Sat Mar 28 2020 23:49:45 GMT+0800 (中國標準時間)
console.log(String(new Date('2020/12/09'))) // Wed Dec 09 2020 00:00:00 GMT+0800 (中國標準時間)
複製代碼

總結-對象轉字符串

對於對象轉字符串,也就是調用String()函數,總結以下:

  • 若是對象具備 toString 方法,則調用這個方法。若是他返回一個原始值,JavaScript 將這個值轉換爲字符串,並返回這個字符串結果。
  • 若是對象沒有 toString 方法,或者這個方法並不返回一個原始值,那麼 JavaScript 會調用 valueOf 方法。若是存在這個方法,則 JavaScript 調用它。若是返回值是原始值,JavaScript 將這個值轉換爲字符串,並返回這個字符串的結果。()
  • 不然,JavaScript 沒法從 toString 或者 valueOf 得到一個原始值,這時它將拋出一個類型錯誤異常。

其實也就是走的toPrimitive(object, 'string')這種狀況。

10. 對象轉數字

若是你們弄懂了對象轉字符串的話,那麼弄懂對象轉數字也不難了。

剛剛咱們說了對象轉字符串也就是toPrimitive(object, 'string')的狀況,

那麼對象轉數字就是toPrimitive(object, 'number')

區別就是轉數字會先調用valueOf()後調用toString()

10.1 題目一

(最基本的轉換)

console.log(Number({}))
console.log(Number([]))
console.log(Number([0]))
console.log(Number([1, 2]))
複製代碼

對於Number({})

  • 傳入的是一個對象{},所以調用valueOf()方法,該方法在題7.1中已經提到過了,它除了日期對象的其它引用類型調用都是返回它自己,因此這裏仍是返回了對象{}
  • valueOf()返回的值仍是對象,因此繼續調用toString()方法,而{}調用toString()的結果爲字符串"[object Object]",是一個基本數據類型
  • 獲得基礎數據類型了,該要返回了,不過在這以前還得將它在轉換爲數字才返回,那麼"[object Object]"轉爲數字爲NaN,因此結果爲NaN

對於Number([])

  • 傳入的是一個數組[],所以調用valueOf()方法,返回它自身[]
  • []繼續調用toString()方法,而空數組轉爲字符串是爲""
  • 最後再將空字符串""轉爲數字0返回

對於Number([0])

  • 由於[0]轉爲字符串是爲"0",最後在轉爲數字0 返回

對於Number([1, 2])

  • 傳入的是一個數組[1, 2],因此調用valueOf()方法返回的是數組自己[1,2]
  • 因此繼續調用toString()方法,此時被轉換爲了"1,2"字符串
  • "1,2"字符串最後被轉爲數字爲NaN,因此結果爲NaN

結果:

console.log(Number({})) // NaN
console.log(Number([])) // 0
console.log(Number([0])) // 0
console.log(Number([1, 2])) // NaN
複製代碼

10.2 題目二

(日期類型轉數字)

來看個比較特殊的日期類型轉數字

console.log(Number(new Date()))
複製代碼

過程解析:

  • 傳入的是一個日期類型的對象new Date(),所以調用valueOf(),在題目7.2中已經說了,日期類型調用valueOf()是會返回一個毫秒數
  • 毫秒數爲數字類型,也就是基本數據類型,那麼直接返回(其實還有一步轉爲數字類型的過程),因此結果爲1585413652137

答案:

console.log(Number(new Date())) // 1585413652137
複製代碼

總結-對象轉數字

因此對於對象轉數字,總結來講和對象轉字符串差很少:

  • 若是對象具備 valueOf 方法,且返回一個原始值,則 JavaScript 將這個原始值轉換爲數字並返回這個數字

  • 不然,若是對象具備 toString 方法,且返回一個原始值,則 JavaScript 將其轉換並返回。

  • 不然,JavaScript 拋出一個類型錯誤異常。

可算是給👴整完了這206console.log(),吸口氣休息一會...

後語

知識無價,支持原創。

參考文章:

你盼世界,我盼望你無bug。這篇文章就介紹到這裏。

其實我在學習數據類型轉換的的歷程是這樣的:

滿心歡喜 -> 決心弄懂 -> 眉頭緊鎖 -> 表情凝重 -> 生無可戀 -> 小徹小悟

確實有一個生無可戀的時候,哈哈哈,不過在堅持下去以後也算是"小徹小悟"吧,爲啥不是大徹大悟,這個...人仍是要謙虛點的哈。

用心創做,好好生活。若是你以爲文章對你有幫助的話來個贊👍哦,謝謝啦~ 😁。

喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號 LinDaiDai 或者掃一掃下面的二維碼👇👇👇.

我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉

你的鼓勵就是我持續創做的主要動力 😊.

相關推薦:

《全網最詳bpmn.js教材》

《【建議改爲】讀完這篇你還不懂Babel我給你寄口罩》

《【建議星星】要就來45道Promise面試題一次爽到底(1.1w字用心整理)》

《【建議👍】再來40道this面試題酸爽繼續(1.2w字用手整理)》

《【何不三連】比繼承家業還要簡單的JS繼承題-封裝篇(牛刀小試)》

《【何不三連】作完這48道題完全弄懂JS繼承(1.7w字含辛整理-返璞歸真)》

《【精】從206個console.log()徹底弄懂數據類型轉換的前世此生(下)》

相關文章
相關標籤/搜索