最近腦子裏有冒出「多看點書」的想法,但我我的不是很喜歡翻閱紙質書籍,另外一方面也是由於我能抽出來看書的時間比較瑣碎,因此就乾脆用app看電子書了(若是有比較完整的閱讀時間,仍是建議看紙質書籍,排版看起來更舒服點)。考慮到平時工做遇到的大部分問題仍是javascript強相關的,因而我選擇從《Javascript權威指南第6版》開始。javascript
javascript
的數據類型分爲兩大類,一類是原始類型(primitive type),一類是對象類型(object type)。java
原始類型又稱爲基本類型,分爲Number
, String
, Boolean
, Undefined
, Null
幾類。比較特殊的是,undefined
是Undefined
類型中的惟一一個值;一樣地,null
是Null
類型中的惟一一個值。git
除此以外,ES6
引入了一個比較特殊的原始類型Symbol
,用於表示一個獨一無二的值,具體使用方法能夠看阮一峯老師的ECMAScript6入門,或者直接翻閱MDN,我平時看MDN比較多,感受比較權威,API也很完善。程序員
爲何說Symbol
是原始類型,而不是對象類型呢?由於咱們知道,大部分程序員都是沒有對象的,那麼要想找到女友,最快的辦法就是new
一個。es6
const options = { '性格': '好', '顏值': '高', '對我': '好' } const gf = new GirlFriend(options) // new一個女友
好了,不皮了,回到正題,意思就是,Symbol
是沒有構造函數constructor
的,不能經過new Symbol()
得到實例。github
可是獲取symbol
類型的值是經過調用Symbol
函數獲得的。正則表達式
const symbol1 = Symbol('Tusi')
Symbol
值是惟一的,因此下面的等式是不成立的。數組
Symbol(1) === Symbol(1) // false
對象類型也叫引用類型,簡單地理解呢,對象就是鍵值對key:value
的集合。常見的對象類型有Object
, Array
, Function
, Date
, RegExp
等。promise
除了這些,Javascript
還有蠻蠻多的全局對象,具體見JavaScript 標準內置對象。可是全局對象並不意味着它就是一種對象類型,就好比JSON
是一個全局對象,可是它不是一種類型,這一點要搞清楚。瀏覽器
前面說了,對象能夠new
出來,因此對象類型都有構造函數,Object
類型對應的構造函數是Object()
,Array
類型對應的構造函數是Array()
,再也不贅述。
var obj = new Object() // 不過咱們通常也不會這麼寫一個普通對象 var arr1 = new Array(1) // 建立一個length是1的空數組 var arr2 = new Array(1, 2) // 建立數組[1, 2]
棧內存的優點是,存取速度比堆內存要快,充分考慮這一點,實際上是能夠優化代碼性能的。
原始類型是按值訪問的,其值存儲在棧內存中,所佔內存大小是已知的或是有範圍的;
對基本類型變量的從新賦值,其本質上是進行壓棧操做,寫入新的值,並讓變量指向一塊棧頂元素(大概意思是這樣,可是v8
等引擎有沒有作這方面的優化,就要細緻去看了)
var a = 1; // 壓棧,1成爲棧頂元素,其值賦給變量a a = 2; // 壓棧,2成爲棧頂元素,並賦值給變量a(內存地址變了)
而對象類型是按引用訪問的,經過指針訪問對象。
指針是一個地址值,相似於基本類型,存儲於棧內存中,是變量訪問對象的中間媒介。
而對象自己存儲在堆內存中,其佔用內存大小是可變的,未知的。
舉例以下:
var b = { name: 'Tusi' }
運行這行代碼,會在堆內存中開闢一段內存空間,存儲對象{name: 'Tusi'}
,同時聲明一個指針,其值爲上述對象的內存地址,指針賦值給引用變量b
,意味着b
引用了上述對象。
對象能夠新增或刪除屬性,因此說對象類型佔用的內存大小通常是未知的。
b.age = 18; // 對象新增了age屬性
那麼,按引用訪問是什麼意思呢?
個人理解是:對引用變量進行對象操做,其本質上改變的是引用變量所指向的堆內存地址中的對象自己。
這就意味着,若是有兩個或兩個以上的引用變量指向同一個對象,那麼對其中一個引用變量的對象操做,會影響指向該對象的其餘引用變量。
var b = { name: 'Tusi' }; // 建立對象,變量b指向該對象 var c = b; // 聲明變量c,指向與b一致 b.age = 18; // 經過變量b修改對象 // 產生反作用,c受到影響 console.log(c); // {name: "Tusi", age: 18}
考慮到對象操做的反作用,咱們會在業務代碼中常用深拷貝來規避這個問題。
判斷數據類型是很是重要的基礎設施之一,那麼如何判斷數據類型呢?請接着往下看。
javascript
自己提供了typeof
運算符,能夠輔助咱們判斷數據類型。
typeof
操做符返回一個字符串,表示未經計算的操做數的類型。
typeof
的運算結果以下,引用自MDN typeof
數據類型 | 運算結果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
String | "string" |
Symbol | "symbol" |
Function | "function" |
其餘對象 | "object" |
宿主對象(由JS環境提供,如Nodejs有global,瀏覽器有window) | 取決於具體實現 |
能夠看到,typeof
能幫咱們判斷出大部分的數據類型,可是要注意的是:
typeof null
的結果也是"object"
typeof
獲得的結果沒法判斷出數組,普通對象,其餘特殊對象那麼如何準確地知道一個變量的數據類型呢?
instanceof
運算符用於檢測構造函數的prototype
屬性是否出如今某個實例對象的原型鏈上。
利用instanceof
,咱們能夠判斷一個對象是否是某個構造函數的實例。那麼結合typeof
,咱們能夠封裝一個基本的判斷數據類型的函數。
基本思想是:首先看typeof
是否是返回"object"
,若是不是,說明是普通數據類型,那麼直接返回typeof
運算結果便可;若是是,則須要先把null
這個坑貨摘出來,而後依次判斷其餘對象類型。
function getType(val) { const type = typeof val; if (type === 'object') { if (val === null) { // null不是對象,因此不能用instanceof判斷 return 'null' } else if (val instanceof Array) { return 'array' } else if (val instanceof Date) { return 'date' } else if (// 其餘對象的instanceof判斷) { return 'xxx' } else if (val instanceof Object) { // 全部對象都是Object的實例,因此放最後 return 'object' } } else { return type } } // 測試下 getType(Symbol(1)) // "symbol" getType(null) // "null" getType(new Date()) // "date" getType([1, 2, 3]) // "array" getType({}) // "object"
可是,要把經常使用的對象類型都列舉出來也是有點麻煩的,因此也不算一個優雅的方法。
有沒有終極解決方案?固然是有的。可是,不是標題中的toString
,而是Object.prototype.toString
。用上它,不只上面的數據類型都能被判斷出來,並且也能夠判斷ES6
引入的一些新的對象類型,好比Map
, Set
等。
// 利用了Object.prototype.toString和正則表達式的捕獲組 function getType(val) { return Object.prototype.toString.call(val).replace(/\[object\s(\w+)\]/, '$1').toLowerCase(); } getType(new Map()) // "map" getType(new Set()) // "set" getType(new Promise((resolve, reject) => {})) // "promise"
爲何普通的調用toString
不能判斷數據類型,而Object.prototype.toString
能夠呢?
由於Object
是基類,而各個派生類,如Date
, Array
等在繼承Object
的時候,通常都重寫(overwrite
)了toString
方法,用以表達自身業務,從而失去了判斷類型的能力。
首先解釋一下什麼是裝箱和拆箱,把原始類型轉換爲對應的對象類型的操做稱爲裝箱,反之是拆箱。
咱們知道,只有對象才能夠擁有屬性和方法,可是咱們在使用一些基本類型數據的時候,卻能夠直接調用它們的一些屬性或方法,這是怎麼回事呢?
var a = 1; a.toFixed(2); // "1.00" var b = 'I love study'; b.length; // 12 b.substring(2, 6); // "love"
其實在讀取一些基本類型數據的屬性或方法時,javascript
會建立臨時對象(也稱爲「包裝對象」),經過這個臨時對象來讀取屬性或方法。以上代碼等價於:
var a = 1; var aObj = new Number(a); aObj.toFixed(2); // "1.00" var b = 'I love study'; var bObj1 = new String(b); bObj1.length; // 12 var bObj2 = new String(b); bObj2.substring(2, 6); // "love"
臨時對象是隻讀的,能夠理解爲它們在發生讀操做後就銷燬了,因此不能給它們定義新的屬性,也不能修改它們現有的屬性。
var c = '123'; c.name = 'jack'; // 給臨時對象加新屬性是無效的 c.name; // undefined c.length; // 3 c.length = 2; // 修改臨時對象的屬性值,是無效的 c.length; // 3
咱們也能夠顯示地進行裝箱操做,即經過String()
,Number()
,Boolean()
構造函數來顯示地建立包裝對象。
var b = 'I love study'; var bObj = new String(b);
對象的拆箱操做是經過valueOf
和toString
完成的,且看下文。
javascript
在某些場景會自動執行類型轉換操做,而咱們也會根據業務的須要進行數據類型的轉換。類型的轉換規則以下:
toString()
是默認的對象到字符串的轉換方法。
var a = {}; a.toString(); // "[object Object]"
可是不少類都自定義了toString()
方法,舉例以下:
var a = [1, 2, 3]; a.toString(); // 1,2,3
var a = new Date(); a.toString(); // "Sun May 10 2020 11:19:29 GMT+0800 (中國標準時間)"
var a = /\d+/; a.toString(); // "/\d+/"
valueOf()
會默認地返回對象自己,包括Object
, Array
, Function
, RegExp
。
日期類Date
重寫了valueOf()
方法,返回一個1970年1月1日以來的毫秒數。
var a = new Date(); a.toString(); // 1589095600419
從上表可見,對象(包括數組和函數)轉換爲布爾值都是true
。
對象轉字符串的基本規則以下:
toString()
方法,則調用這個方法。若是它返回字符串,則做爲轉換的結果;若是它返回其餘原始值,則將原始值轉爲字符串,做爲轉換的結果。toString()
方法,或toString()
不返回原始值(不返回原始值這種狀況好像沒見過,通常是自定義類的toString()
方法吧),那麼javascript
會調用valueOf()
方法。若是存在valueOf()
方法而且valueOf()
方法返回一個原始值,javascript
將這個值轉換爲字符串(若是這個原始值自己不是字符串),做爲轉換的結果。javascript
沒法從toString()
或valueOf()
得到一個原始值,會拋出異常。與對象轉字符串的規則相似,只不過是優先調用valueOf()
。
valueOf()
方法,且valueOf()
返回一個原始值,則javascript
將這個原始值轉換爲數字(若是原始值自己不是數字),做爲轉換結果。toString()
方法且返回一個原始值,javascript
將這個原始值轉換爲數字,做爲轉換結果。javascript
將拋出一個類型錯誤異常。使用String()
, Number()
, Boolean()
函數強制轉換類型。
var a = 1; var b = String(a); // "1" var c = Boolean(a); // true
在不一樣的使用場景中,javascript
會根據實際狀況進行類型的隱式轉換。舉幾個例子說明下。
咱們比較熟悉的運算符有算術運算符+
, -
, *
, /
,其中比較特殊的是+
。由於加法運算符+
能夠用於數字加法,也能夠用於字符串鏈接,因此加法運算符的兩個操做數多是類型不一致的。
當兩個操做數類型不一致時,加法運算符+
會有以下的運算規則。
valueOf()
,然而大部分對象的valueOf()
返回的值都是對象自己,不是一個原始值,因此最後也是調用toString()
去得到原始值。對於日期對象來講,會使用對象到字符串的轉換,因此首先調用toString()
。1 + {}; // "1[object Object]" 1 + new Date(); // "1Sun May 10 2020 22:53:24 GMT+0800 (中國標準時間)"
+
的其中一個操做數是字符串的話,就將另外一個操做數也轉換爲字符串,而後進行字符串鏈接。var a = {} + false; // "[object Object]false" var b = 1 + []; // "1"
var a = 1 + true; // 2 var b = 1 + undefined; // NaN var c = 1 + null; // 1
還有個很經典的例子,就是[] == ![]
,其結果是true
。一看,是否是以爲有點懵,一個值的求反居然還等於這個值!其實仔細分析下過程,就能發現其中的奧祕了。
!
的優先級高於關係運算符==
。
![]
首先會執行,而邏輯非運算符!
會首先將其操做數轉爲布爾值,再進行求反。[]
轉爲布爾值是true
,因此![]
的結果是false
。此時的比較變成了[] == false
。==
的其中一個值是false
,則將其轉換爲數字0
,再與另外一個操做數比較。此時的比較變成了[] == 0
。[]
轉爲原始值是空字符串""
,因此此時的比較變成了"" == 0
。0
,0
與0
天然是相等的。搞懂了這個問題,也能夠分析下爲何{} == !{}
的結果是false
了,這個就比較簡單了。
看到這裏,你還以爲數據類型是簡單的知識點嗎?有興趣深究的朋友能夠翻閱下ES5的權威解釋。
數據類型是javascript
中很是重要的一部分,搞清楚數據類型的基本知識點,對於學習javascript
的後續知識點多有裨益。
另外,寫筆記其實對思考問題頗有幫助,就算只是總結很簡單的基礎知識,也是多有助益。
以上內容是我的筆記和總結,不免有錯誤或遺漏之處,歡迎留言交流。