分享JavaScript 類型判斷的幾種方法

一、JavaScript 基本類型

首先介紹一下JavaScript中的八大基本類型

1、原始數據類型

除對象類型(object)以外的其它任何類型定義的不可變的值(值本身無法被改變)。例如(與 C 語言不同),JavaScript 中字符串是不可變的(譯註:如,JavaScript 中對字符串的操作一定返回瞭一個新字符串,原始字符串並沒有被改變)。我們稱這些類型的值為“原始值”。 —— MDN

(1)佈爾類型(Boolean)

佈爾表示一個邏輯實體,可以有兩個值:truefalse 、

(2)Null類型

  • Null 類型隻有一個值:null
  • 值 null 是一個字面量,是表示缺少的標識,隻是變量未指向任何對象。
  • 註意:可以把 null 作為尚未創建的對象,但是 null 並非對象。

(3)Undefined類型

  • undefined:一個聲明未定義的變量的初始值,或沒有實際參數的形式參數。
  • undefined全局對象的一個屬性。也就是說,它是全局作用域的一個變量。undefined的最初值就是原始數據類型undefined。
console.log(undefined in window)  //  true
let a;
console.log(a)  // undefined
  • 一個函數如果沒有使用return語句指定返回值,就會返回一個undefined值。
function f() {
}
console.log(f())  // undefined

(4)數字類型(Number)

  • 數字類型是一種基於 IEEE 754 標準的雙精度 64 位二進制格式的值,能表示整數和浮點值
  • 值的范圍: Number.MIN_VALUE(5e-324)— Number.MAX_VALUE(1.7976931348623157e+308)
  • 如果某個正值大於 Number.MAX_VALUE,則表示為Infinity(或者+Infinity)
    • 如果某個負值小於 -Number.MAX_VALUE則表示為-Infinity
    • 如果某個正值小於 Number.MIN_VALUE,則表示為0(或者+0)
    • 如果某個負值大於 -Number.MIN_VALUE則表示為-0
// 註意:Number.MIN_VALUE(5e-324)— Number.MAX_VALUE(1.7976931348623157e+308) 是可以表示的正值范圍
console.log(Number.MIN_VALUE);  // 5e-324
console.log(Number.MAX_VALUE);  // 1.7976931348623157e+308

console.log(1e310);  // Infinity
console.log(-1e310);  // -Infinity

console.log(1e-330);  // 0
console.log(-1e-330);  // 0
  • NaN(非數值,Not-aNumber)。

not-a-number:NaN 是一個全局對象的屬性。NaN 屬性的初始值就是 NaN,和 Number.NaN 的值一樣。

  • 數字類型中隻有一個整數有兩種表示方法:0可表示為-0和+0(0是+0的簡寫)。

(5)BigInt

  • BigInt 類型是 JavaScript 中的一個基礎的數值類型,可以表示任意精度的整數。使用 BigInt,您可以安全地存儲和操作大整數,甚至可以超過數字類型安全整數限制。(BigInt 與 Number 的主要區別在於,BigInt能在內存中精確表示超過安全限制的整數)
// 數字類型安全整數限制
console.log(Number.MIN_SAFE_INTEGER)  // -9007199254740991
console.log(Number.MAX_SAFE_INTEGER)  // 9007199254740991
  • BigInt 是通過在整數末尾附加字母n或調用構造函數來創建的。
// 構造函數創建
// BigInt() 不與 new 運算符一起使用。
let a = BigInt(1);
// 末尾附加字母n創建
let b = 1n;
console.log(a)  // 1n
console.log(b)  // 2n
  • BigInt不能與數字相互運算。否則,將拋出TypeError
console.log(1 + 1n);
//Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

(6)字符串類型(String)

JavaScript 的字符串類型用於表示文本數據。它是一組 16 位的無符號整數值的“元素”。在字符串中的每個元素占據瞭字符串的位置。第一個元素的索引為0,下一個是索引1,依此類推。字符串的長度是它的元素的數量。

(7)符號類型(Symbols)

  • 符號(Symbols)類型是唯一不可修改的原始值,該類型的性質在於這個類型的值可以用來創建匿名的對象屬性,可以用來作為對象的鍵(key)
  • 可以通過調用函數 Symbol() 來創建 Symbol 數據類型實例。
  • 當一個 symbol 類型的值在屬性賦值語句中被用作標識符,該屬性(像這個 symbol 一樣)是匿名的;並且是不可枚舉的。因為這個屬性是不可枚舉的,它不會在循環結構 “for( ... in ...)” 中作為成員出現,也因為這個屬性是匿名的,它同樣不會出現在 “Object.getOwnPropertyNames()” 的返回數組裡。這個屬性可以通過創建時的原始 symbol 值訪問到,或者通過遍歷 “Object.getOwnPropertySymbols() ” 返回的數組。在之前的代碼示例中,通過保存在變量 myPrivateMethod的值可以訪問到對象屬性。
  • symbol 值提供瞭一種自定義類可以創建私有成員的方式,並維護一個僅適用於該類的 symbol 註冊表。 自定義類可以使用 symbol 值來創建“自有”屬性,這些屬性避免瞭不必要的被偶然發現帶來的影響。 在類定義中,動態創建的 symbol 值將保存到作用域變量中,該變量隻能在類定義中私有地使用。

2、引用數據類型

對象類型(Object)

在計算機科學中, 對象(object)是指內存中的可以被標識符引用的一塊區域。

在 JavaScript 中,對象可以被看作是一組屬性的集合。用對象字面量語法來定義一個對象時,會自動初始化一組屬性。而後,這些屬性還可以被添加和移除。

  • 屬性的值可以是任意類型,包括其它對象。
  • 屬性使用鍵(key)來標識,它的鍵值可以是一個字符串或者符號值(Symbol)。

對象擁有兩種屬性:

a、數據屬性

b、訪問器屬性

JavaScript中提供瞭很多內置對象,可參考MDN官方文檔。

3、兩種數據類型的存儲方式

(1)原始數據類型

基礎類型存儲在棧內存,被引用或拷貝時,會創建一個完全相等的變量;占據空間小、大小固定,屬於被頻繁使用數據,所以放入棧中存儲。

(2)引用數據類型

引用類型存儲在堆內存,存儲的是地址,多個引用指向同一個地址,這裡會涉及一個“共享”的概念;占據空間大、大小不固定。引用數據類型在棧中存儲瞭指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實體。

所以,在 JavaScript 中,原始類型的賦值會完整復制變量值,而引用類型的賦值是復制引用地址。

二、Javascript 數據類型判斷的幾種方法

1、typeof

typeof操作符返回一個字符串,表示未經計算的操作數的類型。

語法:

// operand:一個表示對象或原始值的表達式,其類型將被返回
typeof operand
typeof(operand)

返回值:

"undefined" "object"  "boolean"  "number" "bigint" "string" "symbol" "function" "object"

(1)不同數據類型的 typeof 值

// 歷史遺留問題
console.log(typeof null);  // object
// Undefined 類型typeof值為 undefined
console.log(typeof undefined);  // undefined
// 未定義的變量 typeof值也為 undefined
console.log(typeof a);  // undefined
// 非數值
console.log(typeof NaN);  // number
// 無窮大
console.log(typeof Infinity);  // number
// 除 Null 和 Undefined 之外的原始數據類型 typeof 值都是對應的類型
console.log(typeof true);  // boolean
console.log(typeof 2);  // number
console.log(typeof 2n);  // bigint
console.log(typeof BigInt(1));  // bigint
console.log(typeof 'str');  // string
console.log(typeof Symbol(1));  // symbol
console.log(typeof Boolean(true));  // boolean
console.log(typeof Number(1));  // number
console.log(typeof String('str'));  // string
// 基本包裝類型
console.log(typeof new Boolean(true));  // object
console.log(typeof new Number(1));  // object
console.log(typeof new String('str'));  // object
// 字面量
console.log(typeof {});  // object
console.log(typeof []);  // object
console.log(typeof /1/);  // object
// 除瞭 Function 對象實例, typeof值都是 object
console.log(typeof new Object());  // object
console.log(typeof new Array());  // object
console.log(typeof new Map());  // object
console.log(typeof new Set());  // object
console.log(typeof new Date());  // object
console.log(typeof new RegExp());  // object
console.log(typeof Math);  // object
// Function 對象實例, typeof值為 function
console.log(typeof new Function());  // function
console.log(typeof Function);  // function
console.log(typeof Array);  // function
console.log(typeof Set);  // function
console.log(typeof Date);  // function
console.log(typeof RegExp);  // function
console.log(typeof Object);  // function

(2) typeof null === 'object' 的原因

“typeof null”錯誤是 JavaScript 第一版的遺留物。在這個版本中,值存儲在 32 位單元中,由一個小型標簽(1-3 位)和值的實際數據組成。類型標簽存儲在單元的低位中。

其中有五個:

  • 000:對象。數據是對對象的引用。
  • 1:整數。數據是一個 31 位有符號整數。
  • 010:雙倍。數據是對雙浮點數的引用。
  • 100:字符串。數據是對字符串的引用。
  • 110:佈爾值。數據是佈爾值。

也就是說,最低位是任一位,那麼類型標簽隻有一位長。或者為零,則類型標簽的長度為三位,為四種類型提供兩個額外的位。

有兩個值是特殊的:

  • undefined ( JSVAL_VOID ) 是整數 -2 30(整數范圍之外的數字)。
  • null ( JSVAL_NULL ) 是機器碼 NULL 指針。或者:一個對象類型標簽加上一個為零的引用。(表示為全0)

所以 typeof 認為 null 是一個 object

(3)對象類型 typeof 返回值的理解

針對對象類型,個人理解 的是:

  • 如果是Function對象, 那麼返回值則是function
  • 如果是其他任何普通對象, 那麼返回值則是object,

(4)關於 typeof Boolean(true) 和 typeof new Boolean(true)不同的原因

// 簡單調用Boolean() 函數
console.log(Boolean(true));  // true
// 創建一個Boolean對象
console.log(new Boolean(true));  // Boolean {true} 

Boolean(true) 的返回值就是Boolean類型的數據,newBoolean(true)返回的是一個Boolean對象。

(5)總結

  • typeof 可以判斷除 null 之外所有原始數據類型
  • typeof 在判斷實例類型時,根據實例的構造函數返回 object 或 function
  • typeof null === 'object' 是一個歷史遺留問題,無法修改。
  • typeof NaN === 'number'

2、instanceof

instanceof運算符用於檢測構造函數的 prototype 屬性是否出現在某個實例對象的原型鏈

語法:

// object:某個實例對象
// constructor:某個構造函數
object instanceof constructor

(1)相關例子

// instanceof 右邊是一個構造函數,所以undefined 和 null 不能使用instanceof,下面兩條語句執行會報錯
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);
// 對於原始數據類型無法檢測
console.log(2 instanceof Number);  // false
console.log(1n instanceof BigInt);  // false
console.log(true instanceof Boolean);  // false 
console.log('str' instanceof String);  // false  
console.log(Symbol(1) instanceof Symbol);  // false

// 可以檢測內置對象類型
console.log([] instanceof Array);  // true
console.log(function(){} instanceof Function);  // true
console.log({} instanceof Object);  // true   
console.log(/1/ instanceof RegExp);  // true 
// 所有函數對象實例原型鏈上都存在Function
console.log(Array instanceof Function);  // true 
console.log(Set instanceof Function);  // true 
console.log(Map instanceof Function);  // true 
console.log(Date instanceof Function);  // true 
console.log(RegExp instanceof Function);  // true 
console.log(Function instanceof Function);  // true 
console.log(Object instanceof Function);  // true 
// Object 是所有實例對象原型鏈的盡頭
console.log(Array instanceof Object);  // true 
console.log(Set instanceof Object);  // true 
console.log(Map instanceof Object);  // true 
console.log(Date instanceof Object);  // true 
console.log(RegExp instanceof Object);  // true 
console.log(Function instanceof Object);  // true 
console.log(Object instanceof Object);  // true 
function foo() {}
// foo 是 Function的一個實例
console.log(foo instanceof Object);  // true
console.log(foo instanceof Function);  // true
// new foo() 是 foo 的一個實例
console.log(new foo() instanceof foo);  // true
console.log(new foo() instanceof Object);  // true
console.log(new foo() instanceof Function);  // false
// foo() 返回的是undefined
console.log(foo() instanceof Object);  // false
console.log(foo() instanceof Function);  // false

(2) 當構造函數的proptotype發生改變時,instanceof 結果可能會出錯

function foo() {}
let f = new foo();
console.log(f instanceof foo)  // true
// foo 原型發生改變
foo.prototype = Array.prototype;
console.log(f instanceof foo);  // false

(3)手動實現 instanceof

function myInstanceof(left, right) {
    // 獲得構造函數的原型
    let prototype = right.prototype
    // 獲得對象的原型
    left = left.__proto__
    // 判斷對象的類型是否等於類型的原型
    while (true) {
        // 原型鏈的盡頭是 null,說明實例對象的原型鏈遍歷結束
    	if (left === null)
    		return false
    	if (prototype === left)
    		return true
    	left = left.__proto__
    }
}

上述手動實現隻是實現瞭基本功能,但與原生instanceof仍然存在差別,例如:

  • 未對 right 進行錯誤處理
// 檢驗right 是否是一個對象(Object)
if (right is not Object){
    throw new TypeError(" Uncaught TypeError: Right-hand side of 'instanceof' is not Object")
}
// 檢驗 right 是否可被調用
if (right is not callable) {
    throw new TypeError(" Uncaught TypeError: Right-hand side of 'instanceof' is not callable")
}
  • myInstanceof('str', String) === true
console.log(_instanceof('str', String))  // true

有關instanceof 原理,可繼續深入瞭解原型鏈相關知識點。

(4)總結

  • instanceof 不能判斷原始數據類型
  • instanceof 能夠判斷引用數據類型,但由於是查找原型鏈,所以不能很精準指出數據類型。

例如:

console.log([] instanceof Array);  // true
console.log([] instanceof Object);  // true
  • 當構造函數的 prototype 發生改變時,會導致 instanceof 結果發生改變,詳見(2)

所以 instanceof 用於判斷數據類型也存在弊端。

3、構造函數(constructor)

(1)相關例子

// null 和 undefined 沒有構造函數
onsole.log(null.constructor)  // TypeError
onsole.log(undefined.constructor) // TypeError
// 原始數據類型
console.log((2).constructor === Number); // true
console.log((2n).constructor === BigInt); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(Symbol(1).constructor === Symbol); // true
// 字面量
console.log(([]).constructor === Array); // true
console.log((/1/).constructor === RegExp); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
// JavaScript中的內置函數對象的構造函數為 Function
console.log(Array.constructor === Function); // true
console.log(String.constructor === Function); // true
console.log(Function.constructor === Function); // true
console.log(Object.constructor === Function); // true
// JavaScript中的內置普通對象的構造函數為 Object
console.log(Math.constructor === Object); // true
console.log(JSON.constructor === Object); // true
console.log(Atomics.constructor === Object); // true
console.log(Intl.constructor === Object); // true
console.log(Reflect.constructor === Object); // true
console.log(WebAssembly.constructor === Object); // true

(2)如果創建一個對象前修改構造函數的原型,會導致constructor不可靠

function Fn(){};
Fn.prototype=new Array();
var f=new Fn();
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true 

(3)總結

  • 通過構造函數 constructor 能夠準確判斷出數據類型,但 null 和 undefined 沒有構造函數,無法判斷
  • 內置函數對象的構造函數都是 Function,例如 Array、Map等;內置普通對象的構造函數是Object,例如:JSON、Atomic、Intl、Reflect、WebAssembly 等
  • 在創建實例前修改實例的原型,會導致constructor不可靠

所以 constructor 判斷數據類型也存在弊端。

4、Object.prototype.toString.call()

(1)Object.prototype.toString()

每個對象都有一個toString()方法,當該對象被表示為一個文本值時,或者一個對象以預期的字符串方式引用時自動調用。默認情況下,toString()方法被每個Object對象繼承。如果此方法在自定義對象中未被覆蓋,toString() 返回  "[object type]" ,其中type是對象的類型 —— MDN

(2) Object.prototype.toString.call() 實例

// 原始數據類型
console.log(Object.prototype.toString.call(undefined))  // [object Undefined]
console.log(Object.prototype.toString.call(null))  // [object Null]
console.log(Object.prototype.toString.call(1))  // [object Number]
console.log(Object.prototype.toString.call(1n))  // [object BigInt]
console.log(Object.prototype.toString.call(true))  // [object Boolean]
console.log(Object.prototype.toString.call('str'))  // [object String]
console.log(Object.prototype.toString.call(Symbol(1)))  // [object Symbol]
// 字面量
console.log(Object.prototype.toString.call({}))  // [object object]
console.log(Object.prototype.toString.call([]))  // [object Array]
console.log(Object.prototype.toString.call(/1/))  // [object RegExp]
console.log(Object.prototype.toString.call(NaN))  // [object Number]
console.log(Object.prototype.toString.call(Infinity))  // [object Number]
console.log(Object.prototype.toString.call(globalThis))  // [object Window]
// 內置函數對象實例
console.log(Object.prototype.toString.call(new Array()))  // [object Array]
console.log(Object.prototype.toString.call(new Map()))  // [object Map]
console.log(Object.prototype.toString.call(new Set()))  // [object Set]
console.log(Object.prototype.toString.call(new Date()))  // [object Date]
console.log(Object.prototype.toString.call(new Function()))  // [object Function]
// 內置普通對象本身
console.log(Object.prototype.toString.call(Math))  // [object Math]
console.log(Object.prototype.toString.call(JSON))  // [object JSON]
console.log(Object.prototype.toString.call(Reflect))  // [object Reflect]
// 內置函數對象本身
console.log(Object.prototype.toString.call(Array))  // [object Function]
console.log(Object.prototype.toString.call(Map))  // [object Function]
console.log(Object.prototype.toString.call(Function))  // [object Function]
console.log(Object.prototype.toString.call(Object))  // [object Function]
// 瀏覽器全局對象
console.log(Object.prototype.toString.call(document))  // [object HTMLDocument]
console.log(Object.prototype.toString.call(window))  // [object Window]
// 自定義構造函數
function Foo() {}
f = new Foo();
console.log(Object.prototype.toString.call(f))  // [object object]
console.log(Object.prototype.toString.call(Foo))  // [object Function]

(3)基於Object.prototype.toString.call()實現數據類型判斷

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {    // 先進行typeof判斷,如果是基礎數據類型,直接返回
    return type;
  }
  // 對於typeof返回結果是object的,再進行如下的判斷,正則返回結果
  return Object.prototype.toString.call(obj).replace(/^[object (\S+)]$/, '$1');  // 註意正則中間有個空格
}

(4)總結

Object.prototype.toString.call() 能準確判斷數據類型。

三、寫在最後

  • 除瞭 Null 之外的原始數據類型,均可使用 typeof 判斷數據類型。
  • Object.prototype.toString.call()  是判斷數據類型的最好的方法。
  • 有關 instanceof 和 constructor , 可以深入去瞭解JavaScript中的原型鏈。

到此這篇關於分享JavaScript 類型判斷的幾種方法的文章就介紹到這瞭,更多相關JavaScript 類型判斷內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: