編寫javascript代碼的時候經常要判斷變量,字面量的類型,能夠用typeof,instanceof,Array.isArray(),等方法,究竟哪種最方便,最實用,最省心呢?本問探討這個問題。javascript
1. typeofjava
1.1 語法web
typeof返回一個字符串,表示未經計算的操做數的類型。正則表達式
語法:typeof(operand) | typeof operand
參數:一個表示對象或原始值的表達式,其類型將被返回
描述:typeof可能返回的值以下:算法
類型 | 結果 |
Undefined | 「undefined」 |
Null | 「object」 |
Boolean | 「boolean」 |
Number | 「number」 |
Bigint | 「bigint」 |
String | 「string」 |
Symbol | 「symbol」 |
宿主對象(由JS環境提供) | 取決於具體實現 |
Function對象 | 「function」 |
其餘任何對象 | 「object」 |
從定義和描述上來看,這個語法能夠判斷出不少的數據類型,可是仔細觀察,typeof null竟然返回的是「object」,讓人摸不着頭腦,下面會具體介紹,先看看這個效果:數組
// 數值 console.log(typeof 37) // number console.log(typeof 3.14) // number console.log(typeof(42)) // number console.log(typeof Math.LN2) // number console.log(typeof Infinity) // number console.log(typeof NaN) // number 儘管它是Not-A-Number的縮寫,實際NaN是數字計算獲得的結果,或者將其餘類型變量轉化成數字失敗的結果 console.log(Number(1)) //number Number(1)構造函數會把參數解析成字面量 console.log(typeof 42n) //bigint // 字符串 console.log(typeof '') //string console.log(typeof 'boo') // string console.log(typeof `template literal`) // string console.log(typeof '1') //string 內容爲數字的字符串仍然是字符串 console.log(typeof(typeof 1)) //string,typeof老是返回一個字符串 console.log(typeof String(1)) //string String將任意值轉換成字符串 // 布爾值 console.log(typeof true) // boolean console.log(typeof false) // boolean console.log(typeof Boolean(1)) // boolean Boolean會基於參數是真值仍是虛值進行轉換 console.log(typeof !!(1)) // boolean 兩次調用!!操做想短語Boolean() // Undefined console.log(typeof undefined) // undefined console.log(typeof declaredButUndefinedVariabl) // 未賦值的變量返回undefined console.log(typeof undeclaredVariable ) // 未定義的變量返回undefined // 對象 console.log(typeof {a: 1}) //object console.log(typeof new Date()) //object console.log(typeof /s/) // 正則表達式返回object // 下面的例子使人迷惑,很是危險,沒有用處,應避免使用,new操做符返回的實例都是對象 console.log(typeof new Boolean(true)) // object console.log(typeof new Number(1)) // object console.log(typeof new String('abc')) // object // 函數 console.log(typeof function () {}) // function console.log(typeof class C { }) // function console.log(typeof Math.sin) // function
1.2 迷之null瀏覽器
javascript誕生以來,typeof null都是返回‘object’的,這個是由於javascript中的值由兩部分組成,一部分是表示類型的標籤,另外一部分是表示實際的值。對象類型的值類型標籤是0,不巧的是null表示空指針,它的類型標籤也被設計成0,因而就有這個typeof null === ‘object’這個‘惡魔之子’。安全
曾經有ECMAScript提案讓typeof null返回‘null’,可是該提案被拒絕了。app
1.3 使用new操做符函數
除Function以外全部構造函數的類型都是‘object’,以下:
var str = new String('String'); var num = new Number(100) console.log(typeof str) // object console.log(typeof num) // object var func = new Function() console.log(typeof func) // function
1.4 語法中的括號
typeof運算的優先級要高於「+」操做,可是低於圓括號
var iData = 99 console.log(typeof iData + ' Wisen') // number Wisen console.log(typeof (iData + 'Wisen')) // string
1.5 判斷正則表達式的兼容性問題
typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1 typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1
1.6 錯誤
ECMAScript 2015以前,typeof總能保證對任何所給的操做數都返回一個字符串,即便是沒有聲明,沒有賦值的標示符,typeof也能返回undefined,也就是說使用typeof永遠不會報錯。
可是ES6中加入了塊級做用域以及let,const命令以後,在變量聲明以前使用由let,const聲明的變量都會拋出一個ReferenceError錯誤,塊級做用域變量在塊的頭部到聲明變量之間是「暫時性死區」,在這期間訪問變量會拋出錯誤。以下:
console.log(typeof undeclaredVariable) // 'undefined' console.log(typeof newLetVariable) // ReferenceError console.log(typeof newConstVariable) // ReferenceError console.log(typeof newClass) // ReferenceError let newLetVariable const newConstVariable = 'hello' class newClass{}
1.7 例外
當前全部瀏覽器都暴露一個類型爲undefined的非標準宿主對象document.all。typeof document.all === 'undefined'。景觀規範容許爲非標準的外來對象自定義類型標籤,單要求這些類型標籤與已有的不一樣,document.all的類型標籤爲undefined的例子在web領域被歸類爲對原ECMA javascript標準的「故意侵犯」,可能就是瀏覽器的惡做劇。
總結:typeof返回變量或者值的類型標籤,雖然對大部分類型都能返回正確結果,可是對null,構造函數實例,正則表達式這三種不太理想。
2. instanceof
2.1 語法
instanceof運算符用於檢測實例對象(參數)的原型鏈上是否出現構造函數的prototype。
語法:object instanceof constructor
參數:object 某個實例對象
constructor 某個構造函數
描述:instanceof運算符用來檢測constructor.property是否存在於參數object的原型鏈上。
// 定義構造函數 function C() { } function D() { } var o = new C() console.log(o instanceof C) //true,由於Object.getPrototypeOf(0) === C.prototype console.log(o instanceof D) //false,D.prototype不在o的原型鏈上 console.log(o instanceof Object) //true 同上 C.prototype = {} var o2 = new C() console.log(o2 instanceof C) // true console.log(o instanceof C) // false C.prototype指向了一個空對象,這個空對象不在o的原型鏈上 D.prototype = new C() // 繼承 var o3 = new D() console.log(o3 instanceof D) // true console.log(o3 instanceof C) // true C.prototype如今在o3的原型鏈上
須要注意的是,若是表達式obj instanceof Foo返回true,則並不意味着該表達式會永遠返回true,應爲Foo.prototype屬性的值可能被修改,修改以後的值可能不在obj的原型鏈上,這時表達式的值就是false了。另一種狀況,改變obj的原型鏈的狀況,雖然在當前ES規範中,只能讀取對象的原型而不能修改它,可是藉助非標準的__proto__僞屬性,是能夠修改的,好比執行obj.__proto__ = {}後,obj instanceof Foo就返回false了。此外ES6中Object.setPrototypeOf(),Reflect.setPrototypeOf()均可以修改對象的原型。
instanceof和多全局對象(多個iframe或多個window之間的交互)
瀏覽器中,javascript腳本可能須要在多個窗口之間交互。多個窗口意味着多個全局環境,不一樣全局環境擁有不一樣的全局對象,從而擁有不一樣的內置構造函數。這可能會引起一些問題。例如表達式[] instanceof window.frames[0].Array會返回false,由於Array.prototype !== window.frames[0].Array.prototype。
起初,這樣可能沒有意義,可是當在腳本中處理多個frame或多個window以及經過函數將對象從一個窗口傳遞到另外一個窗口時,這就是一個很是有意義的話題。實際上,能夠經過Array.isArray(myObj)或者Object.prototype.toString.call(myObj) = "[object Array]"來安全的檢測傳過來的對象是不是一個數組。
2.2 示例
String對象和Date對象都屬於Object類型(它們都由Object派生出來)。
可是,使用對象文字符號建立的對象在這裏是一個例外,雖然原型未定義,可是instanceof of Object返回true。
var simpleStr = "This is a simple string"; var myString = new String(); var newStr = new String("String created with constructor"); var myDate = new Date(); var myObj = {}; var myNonObj = Object.create(null); console.log(simpleStr instanceof String); // 返回 false,雖然String.prototype在simpleStr的原型鏈上,可是後者是字面量,不是對象 console.log(myString instanceof String); // 返回 true console.log(newStr instanceof String); // 返回 true console.log(myString instanceof Object); // 返回 true console.log(myObj instanceof Object); // 返回 true, 儘管原型沒有定義 console.log(({}) instanceof Object); // 返回 true, 同上 console.log(myNonObj instanceof Object); // 返回 false, 一種建立非 Object 實例的對象的方法 console.log(myString instanceof Date); //返回 false console.log( myDate instanceof Date); // 返回 true console.log(myDate instanceof Object); // 返回 true console.log(myDate instanceof String); // 返回 false
注意:instanceof運算符的左邊必須是一個對象,像"string" instanceof String,true instanceof Boolean這樣的字面量都會返回false。
下面代碼建立了一個類型Car,以及該類型的對象實例mycar,instanceof運算符代表了這個myca對象既屬於Car類型,又屬於Object類型。
function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } var mycar = new Car("Honda", "Accord", 1998); var a = mycar instanceof Car; // 返回 true var b = mycar instanceof Object; // 返回 true
不是...的實例
要檢測對象不是某個構造函數的實例時,可使用!運算符,例如if(!(mycar instanceof Car))
instanceof雖然可以判斷出對象的類型,可是必需要求這個參數是一個對象,簡單類型的變量,字面量就不行了,很顯然,這在實際編碼中也是不夠實用。
總結:obj instanceof constructor雖然能判斷出對象的原型鏈上是否有構造函數的原型,可是隻能判斷出對象類型變量,字面量是判斷不出的。
3. Object.prototype.toString()
1. 語法
toString()方法返回一個表示該對象的字符串。
語法:obj.toString()
返回值:一個表示該對象的字符串
描述:每一個對象都有一個toString()方法,該對象被表示爲一個文本字符串時,或一個對象以預期的字符串方式引用時自動調用。默認狀況下,toString()方法被每一個Object對象繼承,若是此方法在自定義對象中未被覆蓋,toString()返回「[object type]」,其中type是對象的類型,看下面代碼:
var o = new Object(); console.log(o.toString()); // returns [object Object]
注意:如ECMAScript 5和隨後的Errata中所定義,從javascript1.8.5開始,toString()調用null返回[object, Null],undefined返回[object Undefined]
2. 示例
覆蓋默認的toString()方法
能夠自定義一個方法,來覆蓋默認的toString()方法,該toString()方法不能傳入參數,而且必須返回一個字符串,自定義的toString()方法能夠是任何咱們須要的值,但若是帶有相關的信息,將變得很是有用。
下面代碼中定義Dog對象類型,並在構造函數原型上覆蓋toString()方法,返回一個有實際意義的字符串,描述當前dog的姓名,顏色,性別,飼養員等信息。
function Dog(name,breed,color,sex) { this.name = name; this.breed = breed; this.color = color; this.sex = sex; } Dog.prototype.toString = function dogToString() { return "Dog " + this.name + " is a " + this.sex + " " + this.color + " " + this.breed } var theDog = new Dog("Gabby", "Lab", "chocolate", "female"); console.log(theDog.toString()) //Dog Gabby is a female chocolate Lab
3. 使用toString()檢測數據類型
目前來看toString()方法可以基本知足javascript數據類型的檢測需求,能夠經過toString()來檢測每一個對象的類型。爲了每一個對象都能經過Object.prototype.toString()來檢測,須要以Function.prototype.call()或者Function.prototype.apply()的形式來檢測,傳入要檢測的對象或變量做爲第一個參數,返回一個字符串"[object type]"。
// null undefined console.log(Object.prototype.toString.call(null)) //[object Null] 很給力 console.log(Object.prototype.toString.call(undefined)) //[object Undefined] 很給力 // Number console.log(Object.prototype.toString.call(Infinity)) //[object Number] console.log(Object.prototype.toString.call(Number.MAX_SAFE_INTEGER)) //[object Number] console.log(Object.prototype.toString.call(NaN)) //[object Number],NaN通常是數字運算獲得的結果,返回Number還算能夠接受 console.log(Object.prototype.toString.call(1)) //[object Number] var n = 100 console.log(Object.prototype.toString.call(n)) //[object Number] console.log(Object.prototype.toString.call(0)) // [object Number] console.log(Object.prototype.toString.call(Number(1))) //[object Number] 很給力 console.log(Object.prototype.toString.call(new Number(1))) //[object Number] 很給力 console.log(Object.prototype.toString.call('1')) //[object String] console.log(Object.prototype.toString.call(new String('2'))) // [object String] // Boolean console.log(Object.prototype.toString.call(true)) // [object Boolean] console.log(Object.prototype.toString.call(new Boolean(1))) //[object Boolean] // Array console.log(Object.prototype.toString.call(new Array(1))) // [object Array] console.log(Object.prototype.toString.call([])) // [object Array] // Object console.log(Object.prototype.toString.call(new Object())) // [object Object] function foo() {} let a = new foo() console.log(Object.prototype.toString.call(a)) // [object Object] // Function console.log(Object.prototype.toString.call(Math.floor)) //[object Function] console.log(Object.prototype.toString.call(foo)) //[object Function] // Symbol console.log(Object.prototype.toString.call(Symbol('222'))) //[object Symbol] // RegExp console.log(Object.prototype.toString.call(/sss/)) //[object RegExp]
上面的結果,除了NaN返回Number稍微有點差池以外其餘的都返回了意料之中的結果,都能知足實際開發的需求,因而咱們能夠寫一個通用的函數來檢測變量,字面量的類型。以下:
let Type = (function () { let type = {}; let typeArr = ['String', 'Object', 'Number', 'Array', 'Undefined', 'Function', 'Null', 'Symbol', 'Boolean', 'RegExp', 'BigInt']; for (let i = 0; i < typeArr.length; i++) { (function (name) { type['is' + name] = function (obj) { return Object.prototype.toString.call(obj) === '[object ' + name + ']' } })(typeArr[i]) } return type })() let s = true console.log(Type.isBoolean(s)) // true console.log(Type.isRegExp(/22/)) // true
除了能檢測ECMAScript規定的八種數據類型(七種原始類型,Boolean,Null,Undefined,Number,BigInt,String,Symbol,一種複合類型Object)以外,還能檢測出正則表達式RegExp,Function這兩種類型,基本上能知足開發中的判斷數據類型需求。
4. 判斷相等
既然說道這裏,不妨說一說另外一個開發中常見的問題,判斷一個變量是否等於一個值。ES5中比較兩個值是否相等,可使用相等運算符(==),嚴格相等運算符(===),但它們都有缺點,== 會將‘4’轉換成4,後者NaN不等於自身,以及+0 !=== -0。ES6中提出」Same-value equality「(同值相等)算法,用來解決這個問題。Object.is就是部署這個算法的新方法,它用來比較兩個值是否嚴格相等,與嚴格比較運算(===)行爲基本一致。
console.log(5 == '5') // true console.log(NaN == NaN) // false console.log(+0 == -0) // true console.log({} == {}) // false console.log(5 === '5') // false console.log(NaN === NaN) // false console.log(+0 === -0) // true console.log({} === {}) // false
Object.js()不一樣之處有兩處,一是+0不等於-0,而是NaN等於自身,以下:
let a = {} let b = {} let c = b console.log(a === b) // false console.log(b === c) // true console.log(Object.is(b, c)) // true
注意兩個空對象不能判斷相等,除非是將一個對象賦值給另一個變量,對象類型的變量是一個指針,比較的也是這個指針,而不是對象內部屬性,對象原型等。