在 JavaScript 中,主要有七種基本數(shù)據(jù)類型Undefined、Null、Boolean、Number、String、Symbol、BigInt,還有一種復(fù)雜數(shù)據(jù)類型Object,其中包含了Data、function、Array、RegExp等。JavaScript 不支持任何創(chuàng)建自定義類型的機(jī)制,而所有值最終都將是上述8種數(shù)據(jù)類型之一。由于JavaScript 是一種動(dòng)態(tài)類型語言,這意味著你可以在程序執(zhí)行過程中改變變量的類型。
基本數(shù)據(jù)類型與引用數(shù)據(jù)類型的區(qū)別:基本數(shù)據(jù)類型是指存放在棧(stack)中的簡單數(shù)據(jù)段,數(shù)據(jù)大小確定,內(nèi)存空間大小可以分配,它們是直接按值存放的,所以可以直接按值訪問引用類型是存放在堆(heap)內(nèi)存中的對象,變量其實(shí)是保存的在棧內(nèi)存中的一個(gè)指針(保存的是堆內(nèi)存中的引用地址),這個(gè)指針指向堆內(nèi)存。引用類型數(shù)據(jù)在棧內(nèi)存中保存的實(shí)際上是對象在堆內(nèi)存中的引用地址。通過這個(gè)引用地址可以快速查找到保存中堆內(nèi)存中的對象。
那么,在實(shí)際工作代碼開發(fā)中,有哪些方法去判斷一個(gè)數(shù)據(jù)是哪種數(shù)據(jù)類型呢?這就是本篇文章要重點(diǎn)介紹的內(nèi)容——JavaScript中的數(shù)據(jù)類型判斷方法大全!
typeof 運(yùn)算符是 JavaScript 的基礎(chǔ)知識點(diǎn),盡管它存在一定的局限性(見下文),但在前端js的實(shí)際編碼過程中,仍然是使用比較多的類型判斷方式。因此,掌握該運(yùn)算符的特點(diǎn),對于寫出好的代碼,就會起到很大的幫助作用。
typeof 運(yùn)算符可能返回的類型字符串有:string, boolean, number, bigint, symbol, undefined, function, object。
// string
typeof '123'; // 'string'
typeof String(1); // 'string'
// boolean
typeof true; // 'boolean'
typeof Boolean(); // 'boolean'
// number
typeof 1; // number
typeof Number(10); // number
typeof NaN;//number
// undefined
typeof a; // undefined
typeof undefined; // undefined
// symbol
typeof Symbol(); // 'symbol'
typeof Symbol('foo'); // 'symbol'
typeof Symbol.iterator; // 'symbol'
// bigint
typeof 42n; // 'bigint'
typeof BigInt(1); // 'bigint'
// function
typeof function () { return 1 }; // function
typeof console.log; //function
typeof class cs {}; // 'function'
typeof String; // 'function'
typeof RegExp; // 'function'
typeof new Function(); // 'function'
// object
typeof null; // object
typeof []; // object
typeof [1,2,3]; // object
typeof {'id':11}; //object
typeof {}; //object
typeof Math; // 'object'
typeof new Number(1); // 'object'
需要注意的是typeof null返回為object,因?yàn)樘厥庵祅ull被認(rèn)為是一個(gè)空的對象引用。這是 JavaScript 語言的一個(gè)歷史遺留問題。在JavaScript 最初的版本中,使用 32 位的值表示一個(gè)變量,其中前 3 位用于表示值的類型。000 表示對象,010 表示浮點(diǎn)數(shù),100 表示字符串,110 表示布爾值,和其他的值都被認(rèn)為是指針。在這種表示法下,null 被解釋為一個(gè)全零的指針,也就是說它被認(rèn)為是一個(gè)空對象引用,因此 typeof null的結(jié)果就是 "object"。
typeof 的局限性:在于無法精確判斷出 null、數(shù)組、對象、正則 的類型。引用類型,除了function返回function類型外,其它均返回object。所以如果要精準(zhǔn)判斷,還需要使用其他技術(shù)手段,或組合判斷(見下文)。
在 JS 的原型鏈和原型對象中,會通過 new 一個(gè)構(gòu)造函數(shù),來創(chuàng)建實(shí)例對象。構(gòu)造函數(shù)的原型對象上會有一個(gè) constructor 屬性,指向了構(gòu)造函數(shù)自身,所以實(shí)例對象通過原型鏈訪問 constructor 屬性,就能找到自己的構(gòu)造函數(shù),也就是自己的類型了。
new String('a').constructor === String; // true
"a".constructor === String; // true
(1).constructor === Number; // true
new Number(1).constructor === Number; // true
true.constructor === Boolean; // true
new Function().constructor === Function; // true
Symbol(0).constructor === Symbol; // true; // true
BigInt(1).constructor === BigInt; // true
new Error().constructor === Error; // true
[].constructor === Array; // true
new Date().constructor === Date; // true
new RegExp().constructor === RegExp; // true
new Object().constructor === Object; // true
注意:
null 和 undefined 是無效的對象,因此是不會有 constructor 存在的,這兩種類型的數(shù)據(jù)需要通過其他方式來判斷。
函數(shù)的 constructor 不一定準(zhǔn)確,這個(gè)主要體現(xiàn)在自定義對象上,當(dāng)開發(fā)者重寫 prototype 后,原有的 constructor 引用會丟失,constructor 會默認(rèn)為 Object。
String.prototype.constructor = function fn() {
return {};
}
console.log("前端技術(shù)營".constructor) // [Function: fn]
console.log("前端技術(shù)營".constructor === String) // false
instanceof 運(yùn)算符用于檢測構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在某個(gè)實(shí)例對象的原型鏈上。也就是說檢測實(shí)例對象是不是屬于某個(gè)構(gòu)造函數(shù),可以用來做數(shù)據(jù)類型的檢測。不能檢測基本數(shù)據(jù)類型,只可用來判斷引用數(shù)據(jù)。
// 不能檢測基本數(shù)據(jù)類型
1 instanceof Number; // false
// 來檢測引用數(shù)據(jù)
[] instanceof Array; // true
[] instanceof Object; // true
new Object() instanceof Object; // true
new Date() instanceof Date; // true
function Person(){}
new Person() instanceof Person; //true
Person instanceof Object; //true
new Date() instanceof Object; //true
new Person instanceof Object; // true
通過上面代碼可以看到,instanceof 既能夠判斷出 [ ] 是Array的實(shí)例,又能判斷出 [ ] 也是Object的實(shí)例,這是為什么呢?
通過《一文讓你搞懂javascript中的構(gòu)造函數(shù)、實(shí)例、原型、原型鏈,和它們之間的關(guān)系》這篇文章就可以明白其原因了。instanceof 能夠判斷出 [ ].__proto__ 指向 Array.prototype,而 Array.prototype.__proto__ 又指向了Object.prototype,最終Object.prototype.__proto__ 指向了null,標(biāo)志著原型鏈的結(jié)束。因此,[ ]、Array、Object 就在內(nèi)部形成了一條原型鏈。
缺點(diǎn):
不能檢測基本數(shù)據(jù)類型,只可用來判斷引用數(shù)據(jù),obj instanceof Type右操作數(shù)必須是函數(shù)或者 class。
2. 原型鏈可能被修改,導(dǎo)致檢測結(jié)果不準(zhǔn)確。
Object.prototype.toString
Object.prototype.toString——專業(yè)檢測數(shù)據(jù)類型一百年! 它是一個(gè)專門檢測數(shù)據(jù)類型的方法。
原理:當(dāng)調(diào)用 Object.prototype.toString 時(shí),JavaScript會將該調(diào)用傳遞給我們需要檢查類型的對象。因?yàn)镺bject.prototype.toString是一個(gè)函數(shù),所以傳遞給它的唯一參數(shù)是this,而且該參數(shù)是一個(gè)隱式參數(shù)。具體如下:Object.prototype.toString.call(obj),由于toString是Object.prototype上的方法,因此我們傳遞給它的參數(shù)應(yīng)該是一個(gè)對象,而Object.prototype.toString方法本身卻沒有傳遞參數(shù)。這就是為什么我們需要用call將它與需要檢查類型的對象連接起來。該方法可以指定函數(shù)內(nèi)的this關(guān)鍵字上下文。因此,我們將檢查類型的對象作為首個(gè)參數(shù)傳遞給了toString方法,并使用call方法將Object.prototype.toString作為一個(gè)函數(shù)來執(zhí)行。這樣JavaScript就會在上下文對象上執(zhí)行Object.prototype.toString方法,從而返回一個(gè)表示該對象類型的字符串。
function checkDataType(data) {
return Object.prototype.toString.call(data).slice(8, -1);
}
checkDataType(1); // Number
checkDataType('a'); // String
checkDataType(true); //Boolean
checkDataType(null); //Null
checkDataType(undefined); //Undefined
checkDataType(Symbol('b')); //Symbol
checkDataType(BigInt(10)); //BigInt
checkDataType([]); //Array
checkDataType({}); //Object
checkDataType(function fn() {}); //Function
checkDataType(new Date()); //Date
checkDataType(new RegExp()); //RegExp
checkDataType(new Error()); //Error
checkDataType(document); //HTMLDocument
checkDataType(window); //Window
編寫一個(gè)函數(shù),對返回的字符串從第8位做一個(gè)截取,截取到倒數(shù)第一位,再去做類型比較。
Array.isArray專業(yè)檢測數(shù)組三十年
Array.isArray() 檢查傳遞的值是否為Array。它不檢查值的原型鏈,也不依賴于它所附加的 Array 構(gòu)造函數(shù)。對于使用數(shù)組字面量語法或 Array 構(gòu)造函數(shù)創(chuàng)建的任何值,它都會返回 true。
Array.isArray() 也拒絕原型鏈中帶有 Array.prototype,而實(shí)際不是數(shù)組的對象,但 instanceof Array 會接受。
// 下面的函數(shù)調(diào)用都返回 true
Array.isArray([]);
Array.isArray([1]);
Array.isArray(new Array());
Array.isArray(new Array("a", "b", "c", "d"));
Array.isArray(new Array(3));
// 鮮為人知的事實(shí):其實(shí) Array.prototype 也是一個(gè)數(shù)組:
Array.isArray(Array.prototype);
// 下面的函數(shù)調(diào)用都返回 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray("Array");
Array.isArray(true);
Array.isArray(false);
Array.isArray(new Uint8Array(32));
// 這不是一個(gè)數(shù)組,因?yàn)樗皇鞘褂脭?shù)組字面量語法或 Array 構(gòu)造函數(shù)創(chuàng)建的
Array.isArray({ __proto__: Array.prototype });
Number.isNaN專業(yè)檢測NaN三十年
JS 中有一個(gè)特殊的數(shù)字——NaN,表示 not a number,不是一個(gè)數(shù)字,但它卻歸屬于數(shù)字類型。在上文中我們知道 typeof NaN 返回number。NaN 用于表示不是一個(gè)數(shù)字,它不等于任何值,包括它本身。ES6 提供了 Number.isNaN 方法,它能判斷一個(gè)值是否嚴(yán)格等于NaN。
Number.isNaN(NaN); //true
Number.isNaN(1); //false
Number.isNaN('abc'); //false;
Number.isNaN([]); //false;
Number.isNaN({}); //false
上面的 Object.prototype.toString 方法,之所以對不同的數(shù)據(jù)類型,返回不同的標(biāo)識字符串,就是因?yàn)?nbsp;Symbol.toStringTag 。Symbol.toStringTag 是一個(gè)內(nèi)置符號屬性,它的值是一個(gè)字符串,用于表示一個(gè)對象的默認(rèn)描述,也就是調(diào)用 Object.prototype.toString 會返回的內(nèi)容。
const obj = {};
obj[Symbol.toStringTag] = 'ABCD';
console.log(Object.prototype.toString.call(obj)) // [object ABCD]
Symbol.toStringTag主要適用于需自定義類型的場景。
對于自定義對象,調(diào)用 Object.prototype.toString.call()方法,都只會返回 [object Object]。此時(shí)就可以使用 Symbol.toStringTag 來指定一個(gè)確定的類型了。
const arr = [];
Object.defineProperty(arr, Symbol.toStringTag, {
value: 'MyArray'
})
console.log(Object.prototype.toString.call(arr))
// [object MyArray]
Object.getPrototypeOf() 靜態(tài)方法返回指定對象的原型,即內(nèi)部 [[Prototype]] 屬性的值。
Object.getPrototypeOf([]) === Array.prototype; // true
Object.getPrototypeOf({}) === Object.prototype; // true
Object.getPrototypeOf(function fn(){}) === Function.prototype; // true
Object.getPrototypeOf(new Date()) === Date.prototype; // true
Object.getPrototypeOf(new RegExp()) === RegExp.prototype; // true
Object.getPrototypeOf(new Error()) === Error.prototype; // true
isPrototypeOf() 是 Object函數(shù)(類)的下的一個(gè)方法,用于判斷當(dāng)前對象是否為另外一個(gè)對象的原型,如果是就返回 true,否則就返回 false。
let obj = new Object();
console.log(Object.prototype.isPrototypeOf(obj)); // true
obj對象是Object的實(shí)例,所以obj對象的原型(__proto__)指向Object的原型(prototype),上面會輸出true。
function Human() {}
let human = new Human();
console.log(Human.prototype.isPrototypeOf(human)); // true
因?yàn)閔uman對象是Human的實(shí)例,所以human對象的原型(__proto__)指向Human的原型(prototype),上面會輸出true。
JavaScript中內(nèi)置類Number、String、Boolean、Function、Array因?yàn)槎际抢^承Object,所以下面的輸出也都是true。
Object.prototype.isPrototypeOf(Number); // true
Object.prototype.isPrototypeOf(String); // true
Object.prototype.isPrototypeOf(Boolean); // true
Object.prototype.isPrototypeOf(Array); // true
Object.prototype.isPrototypeOf(Function); // true
另外值得一提的是 Function.prototype 也是Object的原型,因?yàn)镺bject也是一個(gè)函數(shù)(類),它們是互相生成的。
Object.prototype.isPrototypeOf(Function); // true
Function.prototype.isPrototypeOf(Object); // true
與instanceof的區(qū)別:instanceof 作用的原理就是判斷實(shí)例的原型鏈中能否找到類的原型對象(prototype),而 isPrototypeOf 又是判斷類的原型對象(prototype)是否在實(shí)例的原型鏈上。這兩個(gè)表達(dá)的意思是一致的,之是寫法不同而已。
instanceof的寫法:A instanceof B,isPrototypeOf的寫法:B.prototype.isPrototypeOf(A)。
直接通過與一個(gè)特定的值進(jìn)行比較,從而判斷數(shù)據(jù)的類型。主要適用undefined、 window、 document、 null 等。
const value = null;
console.log(value === null) // true
// 同時(shí)判斷一個(gè)值是 undefined 或者 null
let value;
console.log(value == null) // true
如何判斷當(dāng)前腳本運(yùn)行在瀏覽器還是node環(huán)境中?
this === window ? 'browser' : 'node'
通過判斷Global對象是否為window,如果不為window,當(dāng)前腳本沒有運(yùn)行在瀏覽器中。
本文整理總結(jié)了 JS 中常用的判斷數(shù)據(jù)類型的方法,判斷數(shù)據(jù)類型方法有很多,實(shí)際使用需要根據(jù)自己的需求使用最適合自己的方法。其中 typeof 和 Object.prototype.toString 使用場景是最多的,對一些特殊的數(shù)據(jù)類型,比如 null,NaN,自定義類型,可以選擇其它的方式去進(jìn)行判斷,做到靈活運(yùn)用。
該文章在 2024/4/19 18:05:26 編輯過