引言
JavaScript中有七種原始資料型別和幾種引用資料型別,本文將清楚地介紹四種用於型別判斷的方法,分別是typeOf
、instanceOf
、Object.prototype.toString.call()
、Array.isArray()
,並介紹其使用方法和判定原理。
typeof
可以準確判斷除
null
之外的所有原始型別,null
會被判定成objectfunction
型別可以被準確判斷為function
,而其他所有引用型別都會被判定為object
let s = '123' // string let n = 123 // number let f = true // boolean let u = undefined // undefined let nu = null // null let sy = Symbol(123) // Symbol let big = 1234n // BigInt let obj = {} let arr = [] let fn = function() {} let date = new Date() console.log(typeof s); // string typeof後面有無括號都行 console.log(typeof n); // number console.log(typeof f); // boolean console.log(typeof u); // undefined console.log(typeof(sy)); // symbol console.log(typeof(big)); // bigint console.log(typeof(nu)); // object console.log(typeof(obj)); // object console.log(typeof(arr)); // object console.log(typeof(date)); // object console.log(typeof(fn)); // function
判定原理
typeof是透過將值轉換為二進制之後,判斷其前三位是否為0:都是0則為object,反之則為原始型別。因為原始型別轉二進制,前三位一定不都是0;反之引用型別被轉換成二進制前三位一定都是0。
null
是原始型別卻被判定為object
就是因為它在機器中是用一長串0來表示的,可以把這看作是一個史詩級的bug。
所以用typeof
判斷接收到的值是否為一個物件時,還要注意排除null的情況:
function isObject() { if(typeof(o) === 'object' && o !== null){ return true } return false }
你丟一個值給typeof
,它會告訴你這個字值是什麼型別,但是它無法準確告訴你這是一個Array
或是Date
,若想要如此精確地知道一個物件型別,可以用instanceof
告訴你是否為某種特定的型別
instanceof
只能精確地判斷引用型別,不能判斷原始型別
console.log(obj instanceof Object);// true console.log(arr instanceof Array);// true console.log(fn instanceof Function);// true console.log(date instanceof Date);// true console.log(s instanceof String);// false console.log(n instanceof Number);// false console.log(arr instanceof Object);// true
判定原理
instanceof
既能把陣列判定成Array
,又能把陣列判定成Object
,究其原因是原型鏈的作用————順著陣列例項 arr 的隱式原型一直找到了 Object 的建構函式,看下面的程式碼:
arr.__proto__ = Array.prototype Array.prototype.__proto__ = Object.prototype
所以我們就知道了,instanceof
能準確判斷出一個物件是否為某種型別,就是依靠物件的原型鏈來查詢的,一層又一層地判斷直到找到null
為止。
手寫instanceOf
根據這個原理,我們可以手寫出一個instanceof
:
function myinstanceof(L, R) { while(L != null) { if(L.__proto__ === R.prototype){ return true; } L = L.__proto__; } return false; } console.log(myinstanceof([], Array)) // true console.log(myinstanceof({}, Object)) // true
物件的隱式原型 等於 建構函式的顯式原型!
Object.prototype.toString.call()
可以判斷任何資料型別
在瀏覽器上執行這三段程式碼,會得到'[object Object]'
,'[object Array]'
,'[object Number]'
var a = {} Object.prototype.toString.call(a) var a = {} Object.prototype.toString.call(a) var a = 123 Object.prototype.toString.call(a)
原型上的toString的內部邏輯
呼叫Object.prototype.toString
的時候執行會以下步驟: 參考官方文件:帶註釋的 ES5
如果此值是
undefined
型別,則返回‘[object Undefined]’
如果此值是
null
型別,則返回‘[object Null]’
將 O 作為
ToObject(this)
的執行結果。toString
執行過程中會呼叫一個ToObject
方法,執行一個類似包裝類的過程,我們訪問不了這個方法,是JS自己用的定義一個
class
作為內部屬性[[class]]
的值。toString可以讀取到這個值並把這個值暴露出來讓我們看得見返回由
"[object"
和class
和"]"
組成的字串
為什麼結合call就能準確判斷值型別了呢?
① 首先我們要知道Object.prototype.toString(xxx)
往括號中不管傳遞什麼返回結果都是'[object Object]'
,因為根據上面五個步驟來看,它內部會自動執行ToObject()
方法,xxx
會被執行一個類似包裝類的過程然後轉變成一個物件。所以單獨一個Object.prototype.toString(xxx)
不能用來判定值的型別
② 其次瞭解call方法的核心原理就是:比如foo.call(obj)
,利用隱式繫結的規則,讓obj物件擁有foo這個函式的引用,從而讓foo函式的this指向obj,執行完foo函式內部邏輯後,再將foo函式的引用從obj上刪除掉。手搓一個call的原始碼就是這樣的:
// call方法只允許被函式呼叫,所以它應該是放在Function建構函式的顯式原型上的 Function.prototype.mycall = function(context) { // 判斷呼叫我的那個哥們是不是函式體 if (typeof this !== 'function') { return new TypeError(this+ 'is not a function') } // this(函式)裡面的this => context物件 const fn = Symbol('key') // 定義一個獨一無二的fn,防止使用該原始碼時與其他fn產生衝突 context[fn] = this // 讓物件擁有該函式 context={Symbol('key'): foo} context[fn]() // 觸發隱式繫結 delete context[fn] }
③ 所以Object.prototype.toString.call(xxx)
就相當於 xxx.toString()
,把toString()方法放在了xxx物件上呼叫,這樣就能精準給出xxx的物件型別
toString方法有幾個版本:
{}.toString()
得到由"[object" 和 class 和 "]" 組成的字串
[].toString()
陣列的toString方法重寫了物件上的toString方法,返回由陣列內部元素以逗號拼接的字串
xx.toString()
返回字串字面量,比如let fn = function(){}; console.log( fn.toString() ) // "function () {}"
Array.isArray(x)
只能判斷是否是陣列,若傳進去的x是陣列,返回true,否則返回false
總結
typeOf:原始型別除了null都能準確判斷,引用型別除了function能準確判斷其他都不能。依靠值轉為二進制後前三位是否為0來判斷
instanceOf:只能把引用型別丟給它準確判斷。順著物件的隱式原型鏈向上比對,與建構函式的顯式原型相等返回true,否則false
Object.prototype.toString.call():可以準確判斷任何型別。要了解物件原型的toString()內部邏輯和call()的核心原理,二者結合纔有精準判定的效果
Array.isArray():是陣列則返回true,不是則返回false。判定範圍最狹窄