概述
眾所周知,JavaScript 是一門弱型別語言,不對變數進行型別強制,變數可以隨時持有任何型別的值,所以在 JavaScript 中,型別對於我們開發人員來說可以理解為值的內部特徵,型別定義了值的行為,以使其能夠區別於其他值。
JavaScript 中共有七種內建資料型別,包括基本型別和物件型別。
基本型別
基本型別分為以下六種:
string
(字串)boolean
(布林值)number
(數字)symbol
(符號)null
(空值)undefined
(未定義)
string
、number
、boolean
和 symbol
這四種型別統稱為原始型別(Primitive),表示不能再細分下去的基本型別;symbol
表示獨一無二的值,通過 Symbol
函式呼叫生成,由於生成的 symbol
值為原始型別,所以 Symbol
函式不能使用 new
呼叫;null
和 undefined
通常被認為是特殊值,這兩種型別的值唯一,就是其本身。
物件型別
物件型別( object
)也稱引用型別,以此和 JavaScript 的基本型別區分開來。物件在邏輯上是屬性的無序集合,是存放各種值的容器。物件值儲存的是引用地址,所以和基本型別值不可變的特性不同,物件值是可變的。
宣告一個物件通常有以下幾種方式:
const obj = {} // 字面量形式,推薦
const obj = new Object() // new呼叫
const obj = Object() // 與new呼叫相同
cosnt obj = Object.create(null) // 空物件
複製程式碼
包裝物件
我們知道物件擁有屬性和方法。但比如字串這種基本型別值不屬於物件為什麼還擁有屬性和方法呢?實際上在引用字串的屬性或方法時,會通過呼叫 new String()
的方式轉換成物件,該物件繼承了字串的方法來處理屬性的引用,一旦引用結束,便會銷燬這個臨時物件,這就是包裝物件的概念。
不僅僅只是字串有包裝物件的概念,數字和布林值也有相對應的 new Number()
和 new Boolean()
包裝物件。null
和 undefined
沒有包裝物件,訪問它們的屬性會報型別錯誤。
字串、數字和布林值通過建構函式顯式生成的包裝物件,既然屬於物件,和基本型別的值必然是有區別的,這點可以通過 typeof
檢測出來。
typeof 'seymoe' // 'string'
typeof new String('seymoe') // 'object'
複製程式碼
一切皆是物件?
我們經常聽到諸如“ JavaScript 中一切皆是物件”的論斷,並且能夠指出部分理由(或者說誤導性假象):
- 所謂例如字串、數字等基本型別值“擁有”屬性和方法
typeof null
結果是object
- 可呼叫的函式也屬於物件(子型別)
- ......
但經過上述的內容,我們應該很容易反駁“ JavaScript 中一切皆是物件”這一錯誤的論斷。
資料型別判定
上文中我們瞭解了 JavaScript 中的資料型別,那麼如何去判斷一個值屬於什麼資料型別呢?
判斷一個值屬於那種資料型別共有三種方式,分別為 typeof
、instanceof
和 Object.prototype.toString()
,我們分別來看。
typeof
一般通過 typeof
操作符來判斷一個值屬於哪種基本型別。
typeof 'seymoe' // 'string'
typeof true // 'boolean'
typeof 10 // 'number'
typeof Symbol() // 'symbol'
typeof null // 'object' 無法判定是否為 null
typeof undefined // 'undefined'
複製程式碼
根據以上可以看出,只有 null
的判定會有誤差。
如果使用 typeof
操作符對物件型別及其子型別,譬如函式(可呼叫物件)、陣列(有序索引物件)等進行判定,則除了函式都會得到 object
的結果。
typeof {} // 'object'
typeof [] // 'object'
typeof(() => {}) // 'function'
複製程式碼
由於無法得知一個值到底是陣列還是普通物件,顯然通過 typeof
判斷具體的物件子型別遠遠不夠。
instanceof
通過 instanceof
操作符也可以對物件型別進行判定,其原理就是測試建構函式的 prototype
是否出現在被檢測物件的原型鏈上。
[] instanceof Array // true
({}) instanceof Object // true
(()=>{}) instanceof Function // true
複製程式碼
注意:instanceof
也不是萬能的。
舉個例子:
let arr = []
let obj = {}
arr instanceof Array // true
arr instanceof Object // true
obj instanceof Object // true
複製程式碼
在這個例子中,arr
陣列相當於 new Array()
出的一個例項,所以 arr.__proto__ === Array.prototype
,又因為 Array
屬於 Object
子型別,即 Array.prototype.__proto__ === Object.prototype
,所以 Object
建構函式在 arr
的原型鏈上。所以 instanceof
仍然無法判斷優雅的判斷一個值到底屬於陣列還是普通物件。
還有一點,可能有人會說 Object.prototype.__proto__ === null
,豈不是說 arr instanceof null
也應該為 true
,這個語句其實會報錯提示右側引數應該為物件,這也印證 typeof null
的結果為 object
真的只是個 bug 。
Object.prototype.toString()
Object.prototype.toString()
可以說是判定 JavaScript 中資料型別的終極解決方法了,具體用法請看以下程式碼:
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(() => {}) // '[object Function]'
Object.prototype.toString.call('seymoe') // '[object String]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'
複製程式碼
我們可以發現該方法在傳入任何型別的值都能返回對應準確的物件型別。用法雖簡單明瞭,但其中有幾個點需要理解清楚:
- 該方法本質就是依託
Object.prototype.toString()
方法得到物件內部屬性[[Class]]
- 傳入原始型別卻能夠判定出結果是因為對值進行了包裝
null
和undefined
能夠輸出結果是內部實現有做處理
型別轉換
將值從一種型別轉換為另一種型別被稱為“型別轉換”,在 JavaScript 中型別轉換都屬於強制型別轉換。更進一步,我們可以借用《You Don't Know JS》作者在中卷提到的觀點,將強制型別轉換分為隱式強制型別轉換和顯式強制型別轉換。某些操作符產生的副作用等不明顯的轉換就是隱式轉換,我們能夠從程式碼中看到哪些地方進行了轉換就是顯式轉換。
let a = 10
let b = a + '' // 隱式強制型別轉換
var c = String(a) // 顯式強制型別轉換
複製程式碼
上述程式碼中,對於變數 b
來說,a
被強制轉換為字串型別與 ''
拼接,這個過程是“隱式”的,而對於變數 c
而言,主動呼叫 String
建構函式進行型別轉換是“顯式”的。但隱式和顯式仍然是相對的,如果你自己清楚數字與字串相加,數字會被強制轉換為字串這個規則,那麼它就是顯式的,反之同理。
JavaScript 中,強制型別轉換總是會返回基本型別的值,比如字串、數字、布林值,不會返回物件和函式。
轉換為字串
ES規範定義了一些抽象操作(即僅供內部使用的操作)和轉換規則來進行強制型別轉換,ToString 抽象操作就負責處理非字串到字串的強制型別轉換。
轉換規則:
null
轉換為'null'
undefined
轉換為undefined
true
轉換為'true'
,false
轉換為'false'
- 數字轉換遵循通用規則,極大極小的數字使用指數形式
- 普通物件除非自定義
toString()
方法,否則返回內部屬性[[Class]]
,如上文提到的[object Object]
- 物件子型別的
toString()
被重新定義的則相應呼叫返回結果
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(1) // '1'
String(-1) // '-1'
String(0) // '0'
String(-0) // '0'
String(Math.pow(1000,10)) // '1e+30'
String(Infinity) // 'Infinity'
String(-Infinity) // '-Infinity'
String({}) // '[object Object]'
String([1,[2,3]]) // '1,2,3'
複製程式碼
轉換為數字
ToNumber 抽象操作負責處理非數字型別轉換為數字型別。
轉換規則:
null
轉換為0
undefined
轉換為NaN
true
轉換為1
,false
轉換為0
- 字串轉換時遵循數字常量規則,轉換失敗返回
NaN
- 物件型別會被轉換為相應的基本型別值,如果得到的值型別不是數字,則遵循以上規則強制轉換為數字
為了將值轉換為基本型別值,規範定義了 ToPrimitive
內部操作,該操作首先檢測該值是否存在 valueOf()
方法,有且返回的值為基本型別值則用此值返回或繼續轉換,沒有則檢測是否存在 toString()
方法,有且返回基本型別值則用此值返回或繼續轉換,沒有則報錯。
轉換為布林值
ToBoolean 抽象操作負責處理非布林型別轉換為布林型別。
轉換規則:
- 可以被強制強制型別轉換為false的值:
null
、undefined
、false
、+0
、-0
、NaN
和''
- 假值列表以外的值都是真值
總結
JavaScript 中的資料型別總共只有七種,包括六種基本型別和物件型別,物件型別擁有很多子型別,也可以說是 JavaScript 的內建物件。判斷一個值屬於那種資料型別有三種方式。JavaScript 中的資料型別轉換隻會返回基本型別值,所以基本存在轉換為字串、數字和布林值三種情況,轉換的更多具體細節,本文不做討究。
參考資料
玩轉 JavaScript 系列
寫作是一個學習的過程,嘗試寫這個系列也主要是為了鞏固 JavaScript 基礎,並嘗試理解其中的一些知識點,以便能靈活運用。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!
整個系列會持續更新,不會完結。