最近腦子裏有冒出「多看點書」的想法,但我我的不是很喜歡翻閱紙質書籍,另外一方面也是由於我能抽出來看書的時間比較瑣碎,因此就乾脆用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
的後續知識點多有裨益。
另外,寫筆記其實對思考問題頗有幫助,就算只是總結很簡單的基礎知識,也是多有助益。
以上內容是我的筆記和總結,不免有錯誤或遺漏之處,歡迎留言交流。