你盼世界,我盼望你無bug
。Hello 你們好!我是霖呆呆!javascript
這一期給你們帶來的是一篇關於JS
數據類型轉換的文章,原由主要是前幾天在刷類型轉換的題時忽然感受本身對它們理解的還不夠深入啊,對於什麼[] == ![]、!{} == []
這類題老是隻知其一;不知其二,記了忘忘了記。前端
這讓我很苦惱,決心給本身下點猛料,完全弄懂它們的轉換機制而後出幾道魔鬼題來考考本身。java
在寫的時候也是蠻糾結的,開始寫了一版全是題目的,可是後來發現若是全是題目不講其原理的話,一些讀者可能會一臉懵逼...因此後來我又加了關於toString、valueOf、toPrimitive
的詳細解析,再配合一些清晰的流程圖,力求能將轉換過程說的清楚明瞭 😁。git
不過預防針可打在前頭,因爲前面1-3
節是一些基礎類型之間的轉換,並不難,因此我不會花太多的篇幅在這上面,可能從第4
節開始慢慢的有點內味了吧,[陰笑~],以爲本身對基礎類型之間轉換有信心的小夥伴能夠直接跳到第4
節看哦。github
但願整篇閱讀下來你的腦子裏並非 "淡黃的長裙...蓬鬆的頭髮..."
面試
OK👌,來看看經過閱讀你能夠學習到:數組
(寫的過程當中,看到冴羽大大也發表了一篇關於類型轉換的文章《JavaScript深刻之頭疼的類型轉換(上)》,完了,難道...我已經到了和大佬們心有靈犀的境界了嗎,膨脹膨脹了,能夠借鑑一下,哈哈)瀏覽器
另外「數據類型轉換」系列我是分爲了兩篇文章來寫,這一篇主要是講解String()、Number()
這種的轉換方式,對於運算符號+、==
這種的轉換以及toPrimitive
的一些高級用法我會放在下一篇文章裏面。 嘻嘻,仍是那句話,按部就班嘛。markdown
「數據類型轉換」系列共有兩篇文章:編輯器
建議按順序閱讀,兩篇文章幫你徹底弄懂數據類型轉換 😁。
轉化爲布爾值的狀況是很簡單的。
當咱們在使用Boolean()
來進行轉換時,有以下轉換規則:
參數類型 | 結果 |
---|---|
false、undefined、null、+0、-0、NaN、"" | false |
除了上面的狀況 | true |
(另外須要注意的是,若是在使用Boolean()
時不傳參數結果也是爲false
的)
數字轉布爾值,只須要記住:
0, -0, NaN
這三種轉換爲false
,其餘的一概爲true
。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 複製代碼
字符串轉布爾也很簡單,只須要記住:
""
都爲true
。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 複製代碼
其它類型,例如null, undefined, 引用
轉布爾值,這些相信你們其實也知道:
null、undefined
爲false
true
document.all
是一個例外,它在非IE
下用typeof
檢測類型爲undefined
,因此會被轉爲false
。(考的很少)(感謝掘友小茗cha提出的document.all
)
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
,不過如今用的已經不多了,我就不展開了)
對於原始值轉字符串,也有如下總結:
參數類型 | 結果 |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | 若是參數是 true,返回 "true"。參數爲 false,返回 "false" |
Number | 能夠看題目2.1 |
String | 返回與之相等的值 |
Symbol | "Symbol()" |
來作幾道題強化一下吧。
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 複製代碼
這三種類型轉換爲字符串,比較簡單:
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)' 複製代碼
參數類型 | 結果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | 若是參數是 true,返回 1。參數爲 false,返回 +0 |
Number | 返回與之相等的值 |
String | 純數字的字符串(包括小數和負數、各進制的數),會被轉爲相應的數字,不然爲NaN |
Symbol | 使用Number()轉會報錯 |
先來看看你們都知道的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
會報錯NaN
,undefined
都會被轉爲NaN
(Boolean
類型轉數字)
布爾值轉數字也是很是簡單的,只有兩種狀況:
console.log(Number(true)) // 1 console.log(Number(false)) // 0 複製代碼
還有一種你們可能會用到的轉數字的方式,那就是使用:
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 複製代碼
一直作到如今感受都還挺簡單的哈 😄。
原始值,也就是基礎數據類型。
讓咱們先來認識一個叫作String
的對象,它的原型鏈是這樣的:
能夠看到,它本質上是一個構造函數,String.__proto__
指向的是Function.prototype
。
String
其實有兩種用法,一種是配合new
來當構造函數用,一種是不用new
:
Symbol
以後就不推薦使用new String
這種作法了)。什麼意思呢 🤔️?通俗點來講:
typeof String(1) // 'string' typeof new String(1) // 'object' 複製代碼
使用typeof
會發現類型都是不一樣的。
哈哈哈。
和它一塊兒的其實還有另外兩個"親兄弟"
:Number、Boolean
;
以及它的"表哥表妹"
:Symbol、BigInt
。 😄
爲何說Number、Boolean
就是親的,然後面兩個就是表的呢 🤔️?
這個霖呆呆是以類似程度來區分的。
也就是說Number、Boolean
和String
同樣都有兩種用法,帶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
這個構造函數。
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
中會提到)對象轉字符串和數字的過程比較複雜,會涉及到一個可能你們以前沒有聽到過的方法:toPrimitive()
它的做用其實就是輸入一個值,而後返回一個必定是基本類型的值,不然會拋出一個類型錯誤異常。
先上一張執行流程圖,讓你們感覺一下絕望、孤獨、寂寞、冷...
雖然它的功能會有些複雜,不過問題不大,待看完後面的內容以後你就能搞懂它了,在介紹toPrimitive()
以前,我得先詳細介紹一下toString()
和valueOf()
方法才行,由於弄懂了它們你才能完全吃透toPrimitive()
。😄
在此以前,我翻了不少關於toString()
的資料,大多都是介紹了它的用法,可是它真正存在於哪裏呢?
可能比較常見的一種說法是它存在於Object
的原型對象中,也就是Object.prototype
上,那麼對於基本數據類型,Number、String、Boolean、 Symbol、BigInt
呢?它們自身有這個方法嗎?或者它們的原型對象上有嗎?
本着一探到底的精神,我打印出了Number
和Number.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
以後你就會發現這種說法其實並不太準確,可是大多數時候咱們都只是關心誰能夠用它,而不是它存在於哪裏)
這個問題,其實在上面👆已經給出答案了,全部對象除了null、undefined
之外的任何值均可以調用toString()
方法,一般狀況下它的返回結果和String
同樣。
其實這裏,咱們最容易搞混的就是String
和toString
。
以前老是爲了將某個類型轉爲字符串胡亂的用這兩個屬性。
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')
呢?
其實前面也已經說到了,因爲Symbol
和BigInt
它們是不能使用new
來調用的,會報錯,而且目前ES6
的規範也不推薦使用new
來建立這種基本類型的包裝類,因此這裏使用的是new Object()
。
可是當咱們在代碼中試圖使用1.toString()
,發現編輯器已經報錯不容許咱們這樣作了。
難道數字就不能夠嗎 🤔️?最開始會有這麼奇怪的疑問是由於咱們都忽視了一件事,那就是.
它也是屬於數字裏的一部分啊 😂。
好比1.2
、1.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();
)可能你們看的比較多的一種用法是這樣的:
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 複製代碼
"霖呆呆,這麼多,這是人乾的事嗎?"
"性平氣和,記住一些經常使用的就好了..."
"啪!"
好滴👌,經過剛剛的學習,咱們瞭解到了,toString.call
這種方式是爲了獲取某個變量更加具體的數據類型。
咦~說到數據類型,咱們原來不是有一個typeof
嗎?它和toString.call()
又啥區別?
首先幫你們回顧一下typeof
它的顯示規則:
number、string
這種),除了null
均可以顯示正確的類型null
由於歷史版本的緣由被錯誤的判斷爲了"object"
object、array
這種),除了函數都會顯示爲"object"
function
因此呀,typeof
的缺點很明顯啊,我如今有一個對象和一個數組,或者一個日期對象,我想要仔細的區分它,用typeof
確定是不能實現的,由於它們獲得的都是"object"
。
因此,採用咱們封裝的getClass()
顯然是一個很好的選擇。
(固然,瞭解instanceof
的小夥伴可能也知道,用instanceof
去判斷也是能夠的,不過這邊不扯遠,具體能夠看一下三元大大的《(建議收藏)原生JS靈魂之問, 請問你能接得住幾個?(上)》,裏面的第二篇有提到這個問題。或者你能夠期待一下呆呆後面的文章,那裏也會詳細講到哦,這裏先賣個關子,哈哈)
剛剛咱們說到的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()
這種調用方式來說。
那麼在不一樣的數據類型調用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' 複製代碼
比較難的部分是引用類型調用toString()
,並且咱們知道引用類型根據[[class]]
的不一樣是分了不少類的,好比有Object
、Array
、Date
等等。
那麼不一樣類之間的toString()
是否也不一樣呢 🤔️?
沒錯,不一樣版本的toString
主要是分爲:
toString
方法是將每一項轉換爲字符串而後再用","
鏈接{name: 'obj'}
這種)轉爲字符串都會變爲"[object Object]"
函數(class)、正則
會被轉爲源代碼字符串日期
會被轉爲本地時區的日期字符串toString
會返回原始值的字符串好的👌,扯了這麼多知識點,終於能夠先上幾道題了 😁。
(沒有題目作我好難受~)
(數組的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()
的時候,因爲數組一項都沒有,因此獲得的確定是一個空字符串。
另外須要注意的是最後兩個,一個是徹底的空字符串,一個是帶了空格的。
(非數組類型的其它對象)
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 (中國標準時間)' 複製代碼
(原始值包裝對象調用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" 複製代碼
(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"
。
不懂就查,搜了一波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()?
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()
的返回結果接下來要介紹的是toString()
的孿生兄弟valueOf
,爲何說是它的孿生兄弟呢 🤔️?
由於它們有不少相同的特性,好比前面咱們提到的toString()
的存在位置,咱們能夠回頭看看6.1
的那張圖,發現有toString()
的地方也有valueOf()
。
另外一個要介紹它的重要緣由是在對象轉基礎數據類型
中,與toString()
相輔相成的就是它了。
它的做用主要是:
把對象轉換成一個基本數據的值。
因此咱們能夠看出它兩的區別:
toString
主要是把對象轉換爲字符串valueOf
主要把對象轉換成一個基本數據的值讓咱們先來看看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 複製代碼
引用類型調用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()
默認是返回它自己1970 年 1 月 1 日以來的毫秒數
(相似於1585370128307
)。弄懂了難啃的toString()
和valueOf()
,終於到了咱們的主角toPrimitive
...
淚牛滿面 😢。
不過不可大意,它纔是最難啃的那塊知識點。
先讓咱們來看看它的函數語法:
ToPrimitive(input, PreferredType?)
複製代碼
參數:
input
,表示要處理的輸入值PerferredType
,指望轉換的類型,能夠看到語法後面有個問號,表示是非必填的。它只有兩個可選值,Number
和String
。而它對於傳入參數的處理是比較複雜的,如今讓咱們來看看開篇的那幅流程圖:
根據流程圖,咱們得出了這麼幾個信息:
(總結來源《冴羽-JavaScript深刻之頭疼的類型轉換(上)》)
上面👆的圖其實只是看着很複雜,細心的小夥伴可能會發現,在圖裏紅框裱起來的地方,只有toString()
和valueOf()
方法的執行順序不一樣而已。
若是 PreferredType 是 String 的話,就先執行 toString()
方法
若是 PreferredType 是 Number 的話,就先執行 valueOf()
方法
霖呆呆建議你先本身在草稿紙上將這幅流程圖畫一遍,以後再來作題有助於記憶 😁。
(最基本的轉換)
好吧,呆呆,我看你扯了這麼多toPrimitive
的轉換流程,可我也沒看出有什麼實際的用處啊。
這...沒啊,其實咱們很早就用上了啊,只不過你以前可能不知道而已。
好比當咱們使用String()
來轉換一個對象爲字符串的時候:
console.log(String({})) 複製代碼
你們都知道結果是:
"[object Object]" 複製代碼
但它爲何是這樣呢?看着結果和toString()
調用的結果好像啊。
這裏其實就用到了toPrimitive
的轉換規則呀。
你看看,咱們把上面👆的代碼換成toPrimitive
的僞代碼看看:
toPrimitive({}, 'string') 複製代碼
OK👌,來回顧一下剛剛的轉換規則:
input
是{}
,是一個引用類型,PerferredType
爲string
toString()
方法,也就是{}.toString()
{}.toString()
的結果爲"[object Object]"
,是一個字符串,爲基本數據類型,而後返回,到此結束。哇~
是否是一切都說得通了,好像不難吧 😁。
沒錯,當使用String()
方法的時候,JS
引擎內部的執行順序確實是這樣的,不過有一點和剛剛提到的步驟不同,那就是最後返回結果的時候,其實會將最後的基本數據類型再轉換爲字符串返回。
也就是說上面👆的第三步咱們得拆成兩步來:
{}.toString()
的結果爲"[object Object]"
,是一個字符串,爲基本數據類型"[object Object]"
字符串再作一次字符串的轉換而後返回。(由於"[object Object]"
已是字符串了,因此原樣返回,這裏看不出有什麼區別)將最後的結果再轉換爲字符串返回這一步,其實很好理解啊。你想一想,我調用String
方法那就是爲了獲得一個字符串啊,你要是給我返回一個number、null
啊什麼的,那不是隔壁老王乾的事嘛~
(咳咳,霖呆呆的真名姓王哈 [害羞~])
上面👆的轉換好像並不能看出來最後會轉爲字符串那一步的效果啊,那麼來看看這道題:
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 複製代碼
若是你能看到這的話,怎樣?是否是有點那啥感受了。
(數組轉字符串)
數組轉字符串我總結了一下主要是這樣:
[]
是被轉換爲空字符串""
","
鏈接配合着引用類型轉字符串我畫了一張圖。
先來看點簡單的:
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"
爲原始數據類型,則返回(因爲返回值都是字符串我就省略還有一個字符串的轉換過程不說了)讓咱們加上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' }])) 複製代碼
因此作這類題時,你通常只要謹記這個準則:
","
鏈接就能夠了,而後再看裏面具體的每一項會被轉成什麼。
(日期類型轉字符串)
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()
函數,總結以下:
其實也就是走的toPrimitive(object, 'string')
這種狀況。
若是你們弄懂了對象轉字符串的話,那麼弄懂對象轉數字也不難了。
剛剛咱們說了對象轉字符串也就是toPrimitive(object, 'string')
的狀況,
那麼對象轉數字就是toPrimitive(object, 'number')
。
區別就是轉數字會先調用valueOf()
後調用toString()
。
(最基本的轉換)
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 複製代碼
(日期類型轉數字)
來看個比較特殊的日期類型轉數字
console.log(Number(new Date())) 複製代碼
過程解析:
new Date()
,所以調用valueOf()
,在題目7.2
中已經說了,日期類型調用valueOf()
是會返回一個毫秒數1585413652137
答案:
console.log(Number(new Date())) // 1585413652137 複製代碼
因此對於對象轉數字,總結來講和對象轉字符串差很少:
若是對象具備 valueOf 方法,且返回一個原始值,則 JavaScript 將這個原始值轉換爲數字並返回這個數字
不然,若是對象具備 toString 方法,且返回一個原始值,則 JavaScript 將其轉換並返回。
不然,JavaScript 拋出一個類型錯誤異常。
可算是給👴整完了這206
個console.log()
,吸口氣休息一會...
知識無價,支持原創。
參考文章:
你盼世界,我盼望你無bug
。這篇文章就介紹到這裏。
其實我在學習數據類型轉換的的歷程是這樣的:
滿心歡喜 -> 決心弄懂 -> 眉頭緊鎖 -> 表情凝重 -> 生無可戀 -> 小徹小悟
確實有一個生無可戀的時候,哈哈哈,不過在堅持下去以後也算是"小徹小悟"
吧,爲啥不是大徹大悟,這個...人仍是要謙虛點的哈。
用心創做,好好生活。若是你以爲文章對你有幫助的話來個贊👍哦,謝謝啦~ 😁。
喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號 LinDaiDai
或者掃一掃下面的二維碼👇👇👇.
我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉
你的鼓勵就是我持續創做的主要動力 😊.
相關推薦:
《【建議星星】要就來45道Promise面試題一次爽到底(1.1w字用心整理)》
《【建議👍】再來40道this面試題酸爽繼續(1.2w字用手整理)》
《【何不三連】比繼承家業還要簡單的JS繼承題-封裝篇(牛刀小試)》