1. 程式人生 > >javascript中判斷資料型別

javascript中判斷資料型別

編寫javascript程式碼的時候常常要判斷變數,字面量的型別,可以用typeof,instanceof,Array.isArray(),等方法,究竟哪一種最方便,最實用,最省心呢?本問探討這個問題。

1. typeof

1.1 語法

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’,但是該提案被拒絕了。

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'];
        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 

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 

注意兩個空物件不能判斷相等,除非是將一個物件賦值給另外一個變數,物件型別的變數是一個指標,比較的也是這個指標,而不是物件內部屬性,物件原型等。