接近完美地判斷JS數據類型,可行嗎

做者: JowayYoung
倉庫: GithubCodePen
博客: 掘金思否知乎簡書頭條CSDN
公衆號: IQ前端
聯繫我:關注公衆號後有個人 微信
特別聲明:原創不易,未經受權不得對此文章進行轉載或抄襲,不然按侵權處理,如需轉載或開通公衆號白名單可聯繫我,但願各位尊重原創的知識產權

本文由筆者師妹LazyCurry創做,收錄於筆者技術文章專欄下前端

前言

JS的變量與其餘語言的變量有很大區別,由於其變量鬆散的本質,決定了變量只是在特定時間內用於保存特定值的一個名字而已,變量的值及其數據類型可在聲明週期內改變。git

JS的數據類型可分爲基本類型引用類型,先簡單介紹兩種數據類型,再來分析判斷數據類型的幾種方法。固然,這個也是大廠常考的面試題,同窗們可按照文章的思路進行回答和擴展,讓面試官耳目一新。github

數據類型

基本類型

基本類型包括Undefined、Null、String、Number、Boolean、Symbol。基本類型按值訪問,因此咱們可操做保存在變量中實際的值。面試

基本類型的值在內存中佔據固定大小的空間,是被保存在棧內存中。從一個變量向另外一個變量複製基本類型的值,會建立這個值的一個副本,這兩個值徹底獨立地存放在棧內存中。segmentfault

引用類型

引用類型是對象類型,包括Object、Array、Function、Data、Regexp、Error。引用類型的值是保存在堆內存中的對象,JS不容許直接訪問內存中的位置,也就是說不能直接訪問操做對象的內存空間。數組

操做對象時,其實是在操做對象的引用,因此說引用類型的值是按引用訪問的。從而有[1, 2] === [1, 2]false微信

判斷數據類型

簡單的講完JS的兩種數據類型,接下來介紹一下JS判斷數據類型的4種方法。app

typeof

typeof是肯定一個變量是stringnumberbooleansymbol(ES6新增類型)仍是undefined的最佳工具。注意,這裏並無說起null以及引用型數據。框架

typeof可能返回下面某個結果,結果的對應值以下:frontend

  • undefined:未定義的值
  • string:字符串
  • number:數值
  • boolean:布爾
  • symbol:惟一值
  • object:對象或空值(null)
  • function:函數
typeof undefined; // undefined
typeof null; // object
typeof "這是一段字符串"; // string
typeof 1; // number
typeof true; // boolean
typeof new Symbol(); // symbol
typeof new Object(); // object
typeof new Function(); // function
typeof new Date(); // object

上面的例子中,對於基本類型來講,除開null均可返回正確的結果。調用typeof null會返回object,是由於null被認爲是一個空的對象引用,所以返回了object,固然這個也是JS設計語言早期遺留的Bug。

而在其餘引用類型,除開function均返回object類型,所以用typeof來判斷引用類型數據的類型並不可取,typeof適合用來判斷基礎類型值。

instanceof

instanceof可用來判斷一個實例對象是否屬於一個構造函數,其表達式A instanceof B,若是A是B的實例,則返回true,不然返回false

實現原理其實就是在A的原型鏈上尋找是否有原型等於B.prototype,若是一直找到A原型鏈的頂端null,仍然找不到原型等於B.prototype,那麼就可返回false。原型鏈的知識可戳往期文章《來自原形與原型鏈的拷問》回顧下哦,這裏就再也不講原型鏈啦~

new Date() instanceof Date; // true
new Date() instanceof Object; // true

[] instanceof Array; // true
[] instanceof Object; // true

function Person() {};
const person = new Person();
person instanceof Person; // true
person instanceof Object; // true

從上面的例子可看到,instanceof可判斷出[]Array的實例,Date對象是Date的實例,personPerson構造函數的實例,到這裏並沒什麼問題,可是instanceof認爲這些也都是Object的實例,這就有點使人疑惑。

其實可根據instanceof的實現原理來分析一下,上面已經講過實現原理,在這裏咱們套用一下instanceof用於數組判斷的過程。

[] instanceof Array,由於能找到[].__proto__指向Array.prototype,所以返回true。[] instanceof Object,在這裏就是也是要沿着[]的原型鏈找,有[].__proto__指向Array.prototype,又由於Array.prototype默認是Object的實例,因此有Array.prototype.__proto__指向了Object.prototype,所以這就是爲何instanceof認爲[]也是Object的實例。

instanceof只能用來判斷兩個對象是否屬於實例關係,並不能判斷一個對象屬於什麼類型。簡單說,就是判斷兩個類是否從屬關係。

avatar

instanceof的問題在於,假如只有一個全局執行環境,若是網頁中有兩個框架,實際上就存在兩個不用的全局執行環境,從而存在兩個不一樣版本的Array構造函數。若是從一個框架向另外一個框架傳入一個數組,那麼傳入的數組與第二個框架中原生建立的數組分別是不一樣的構造函數。

const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const IArray = window.frames[0].Array;
const iarr = new IArray();
iarr instanceof Array; // false
Array.isArray(iarr); // true

爲了解決這個問題,ES5新增了Array.isArray(),這個方法能肯定某個值是否是數組或類數組。

constructor

上面提到的原型鏈,原型對象的constructor屬性指向了構造函數,又由於實例對象的__proto__屬性指向原型對象,所以可有:每個實例對象均可經過constructor來訪問它的構造函數。而JS內置對象在內部構建時也是這麼作的,所以可用來判斷數據類型。

"".__proto__.constructor === String; // true
// 下面將屬性__proto__去掉,效果相同
"".constructor === String; // true
new Number(1).constructor === Number; // true
true.constructor === Boolean; // true
[].constructor === Array; // true
new Date().constructor === Date; // true
new Function().constructor === Function; // true

可看出,大部分類型都能經過這個屬性來判斷。可是因爲undefinednull是無效的對象,所以是沒有constructor屬性的,這兩個值不能用這種方法判斷。另外,當重寫原型時,原型原有的constructor會丟失,這時判斷也就不生效了。

function Person() {};
Person.prototype = {
    name: "XX"
};
const person = new Person();
person.constructor === Person; // false

這時打印person.constructor,可看到是一個Object。爲何會變成Object呢?這是由於在從新定義原型時,傳入的是一個對象{}{}new Object()的字面量,所以會將Object原型上的constructor傳遞給{},因此person.constructor也就打印出了Object。

所以,在重寫原型對象時,都須要給constructor從新賦值,來保證對象實例的類型不改變。這個點在開發時記得記得注意!

toString

Object.prototype.toString方法返回對象的類型字符串,所以可用來判斷一個值的類型。由於實例對象有可能會自定義toString方法,會覆蓋Object.prototype.toString,因此在使用時,最好加上call。會有如下返回值:

  • [object Undefined]:未定義的值
  • [object Null]:空值
  • [object String]:字符串
  • [object Number]:數值
  • [object Boolean]:布爾
  • [object Symbol]:惟一值
  • [object Object]:對象
  • [object Array]:數組
  • [object Function]:函數
  • [object Date]:日期
  • [object RegExp]:正則
  • [object Error]:錯誤
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call("這是字符串"); // [object String]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(new Function()); // [object Function]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
總結與對比
  • typeof使用簡單,可是隻適用於判斷基礎類型數據
  • instanceof能判斷引用類型,不能檢測出基本類型,且不能跨iframe使用
  • constructor基本能判斷全部類型,除了nullundefined,可是constructor容易被修改,也不能跨iframe使用
  • toString能判斷全部類型,所以可將其封裝成一個全能的DataType()判斷全部數據類型
function DataType(tgt, type) {
    const dataType = Object.prototype.toString.call(tgt).replace(/\[object (\w+)\]/, "$1").toLowerCase();
    return type ? dataType === type : dataType;
}

DataType("young"); // "string"
DataType(20190214); // "number"
DataType(true); // "boolean"
DataType([], "array"); // true
DataType({}, "array"); // false

總結

JS的四種判斷方法都有各自的優勢跟缺點,要根據具體狀況採起合適的判斷方式。那麼就到這裏啦,有什麼寫得不對還麻煩各位大佬指出。有大家的支持我還會繼續寫出更好的文章~

結語

❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創做更好的文章

關注公衆號IQ前端,一個專一於CSS/JS開發技巧的前端公衆號,更多前端小乾貨等着你喔

  • 關注後回覆關鍵詞免費領取視頻教程
  • 關注後添加我微信拉你進技術交流羣
  • 歡迎關注IQ前端,更多CSS/JS開發技巧只在公衆號推送

相關文章
相關標籤/搜索