切換語言為:簡體

JavaScript 型別判斷常用的的四種方法

  • 爱糖宝
  • 2024-08-16
  • 2055
  • 0
  • 0

引言

JavaScript中有七種原始資料型別和幾種引用資料型別,本文將清楚地介紹四種用於型別判斷的方法,分別是typeOfinstanceOfObject.prototype.toString.call()Array.isArray(),並介紹其使用方法和判定原理。

typeof

  1. 可以準確判斷除null之外的所有原始型別,null會被判定成object

  2. function型別可以被準確判斷為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

  1. 如果此值是undefined型別,則返回 ‘[object Undefined]’

  2. 如果此值是null型別,則返回 ‘[object Null]’

  3. 將 O 作為 ToObject(this) 的執行結果。toString執行過程中會呼叫一個ToObject方法,執行一個類似包裝類的過程,我們訪問不了這個方法,是JS自己用的

  4. 定義一個class作為內部屬性[[class]]的值。toString可以讀取到這個值並把這個值暴露出來讓我們看得見

  5. 返回由 "[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方法有幾個版本:

  1. {}.toString() 得到由"[object" 和 class 和 "]" 組成的字串

  2. [].toString() 陣列的toString方法重寫了物件上的toString方法,返回由陣列內部元素以逗號拼接的字串

  3. 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。判定範圍最狹窄

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.