JavaScript 三種方法,可以確定一個值到底是什麼型別

任重道遠發表於2019-02-16

JavaScript 三種方法,可以確定一個值到底是什麼型別。

typeof
instanceof
Object.prototype.toString

為什麼需要確定型別 ?

​ 只有確定型別的情況,才知道當前操作物件擁有哪些功能; 比如使用 push,unshfit,shfit 等方法時,那麼其必須為陣列型別時才能正確使用;

​ 當某些情況新增型別檢查時,這樣程式碼更加健壯,安全;

typeof 運算子

返回一個值的資料型別。

基本語法:

typeof operand
or
typeof (operand)

operand 是一個表示式,表示物件或原始值,其型別將被返回。括號是可選的.

示例:基本資料型別

{

    typeof 1            // "number"
    typeof Number(1)    // "number"
    typeof ``           // "string"
    typeof true         // "boolean"
    typeof null         // "object"
    typeof undefined    // "undefined"
    typeof Symbol       // "function"

}

當運算元(operand)為基本資料型別,其返回字串與期望一樣,也能夠辨別當前運算元得型別;

這裡需要提及下基本資料型別 null ,為什麼 typeof null 返回的字串為 “object”;

在 JavaScript 最初的實現中,JavaScript 中的值是由一個表示型別的標籤和實際資料值表示的。物件的型別標籤是 0。由於 null 代表的是空指標(大多數平臺下值為 0x00),因此,null的型別標籤也成為了 0,typeof null就錯誤的返回了”object"

示例:引用資料型別

    typeof new Number(1)          //  `object`
    typeof new String()         //  `object`
    typeof new Array()          //  `object` 
    typeof new Date()             //  `object`
    
     typeof new Function()       //  `function`

從上面看出,所有通過 new 關鍵例項化的建構函式返回都 object 型別. 當然函式是一個例外;

從上述兩個示例可以看出,當typeof 的運算元為基本資料型別、函式返回字串能夠區分其資料型別;

那麼如果需要區分引用型別時需要藉助 JavaScript中另外一個運算子;

instanceof 運算子


判斷例項物件是不是類(建構函式)的例項

instanceof 工作原理基於原型鏈,也就是說如果在物件的原型鏈上能夠找到建構函式的 prototype 物件,那麼該操作就返回 true;

基本語法:**

    object instanceof constructor

引數

  • object
    要檢測的物件.
  • constructor
    某個建構函式

示例一:


let o = new Object();
let bool = new Boolean();
let num = new Number(1);
let str = new String();
let arr = new Array();
// 自定義建構函式
function Person(){} 
function Animal(){} 

let person = new Person();
let animal = new Animal();
person instanceof Person   // true
animal instanceof Animal   // true

o instanceof Object;       // true
bool instanceof Boolean;   // true
num instanceof Number;     // true
str instanceof String;     // true
arr instanceof Array;      // true

這樣彌補 typeof 在檢測引用型別的時的問題;

通過下面的示例,驗證下 instanceof 工作原理:

{
    function Person(){}
    function Animal(){}

    // 例如自定義定義兩個類
    let person = new Person();
    let animal = new Animal();


    console.log(animal instanceof Object);  // =>  true
    console.log(animal instanceof Animal);   // true

    console.log(person instanceof Object); // =>  true
    console.log(person instanceof Person);  // true

    console.log(person instanceof Animal);  // =>  false
    console.log(animal instanceof Person);  // =>  false
}

上面應該跟我們預期的一樣, 那麼有沒有通過一種辦法讓

person instanceof Animal  // => true

那麼來針對上面作如下調整:

person.__proto__ = Animal.prototype;
console.log(person instanceof Animal) // => true
console.log(person instanceof Person) // => false

嚴格意義上來說, 上述這麼改是沒有意思;這裡只是為了驗證 instanceof 如何工作的;其次說明 person instanceof Person 返回 true, 則並不意味著給表示式永遠返回 true, Person.prototypeperson.__proto__ 都是可變的;

instanceof 和多個全域性物件(多個frame或多個window之間的互動)


可以定義兩個頁面測試: parent.html、 child.html

//    parent.html

    <iframe src="child.html" onload="test()">
    </iframe>
    <script>
        function test(){
            var value = window.frames[0].v;
            console.log(value instanceof Array); // false
        }
    </script>
//  child.html
    <script>
        window.name = `child`;
        var v = [];
    </script>

嚴格上來說value 就是陣列,但parent頁面中列印輸出: false ;也就是 parent 頁面中 Array.prototype != window.frames[0].Array.prototype ;

主要引起問題是,因為多個視窗意味著多個全域性環境,不同的全域性環境擁有不同的全域性物件,從而擁有不同的內建型別建構函式.

instanceof 應用 – 檢測作用域的安全


function Person(name, age){
    this.name = name;
    this.age = age
}

// 正常情況下
{
    let person = new Person(`託尼`, 20);
    console.log(person.name,person.age);
}

// 假如在例項化時候忘記新增 new 關鍵字尼? 會出現什麼情況尼?
{
     let person = Person(`託尼`, 20);
    console.log(person.name, person.age);  // Uncaught TypeError: Cannot read property `name` of undefined
    
    // Person 被當作普通的方法執行,其次 name ,age 被新增window上
    console.log(name, age); //=> 託尼 20
}

// 如何避免這樣情況,在加 new 關鍵字或省略時候都能正常返回例項物件 
{
    function Person(name, age){
        if(this instanceof Person) {
            this.name = name;
            this.age = age
            return this;
        }else {
            return new Person(name, age);
        }
    }
    
    let person = Person(`託尼`, 20);
    console.log(person.name, person.age);  // 託尼 20
}

Object.prototype.toString 方法


預設情況下(不覆蓋 toString 方法前提下),任何一個物件呼叫 Object 原生的 toString 方法都會返回 “[object type]”,其中 type 是物件的型別;
每個類的內部都有一個 [[Class]] 屬性,這個屬性中就指定了上述字串中的 type(建構函式名) ;

舉個例子吧:

console.log(Object.prototype.toString.call([])); // [object Array]

上述中: Array 對應也就當前物件的 type,同樣就是當前物件的建構函式名;

前面在說 instanceof 在多個作用域的情況下,嘗試用這種方式解決:

  function isArray(arr){
      return Object.prototype.toString.call(arr) === "[object Array]"
  }
  
  console.log(isArray(value)); // true

從輸出來看時完美解決了;

為什麼這種方式是可以咧?這裡辨別型別根據 建構函式名稱,由於原生陣列的建構函式名稱與全域性作用域無關,因此使用 toString() 就能保證返回一致的值。

同理其它原生物件檢測也能按照這種方式來,如 Date,RegExp,Function

function isFunction(value) {
        return Object.prototype.toString.call(value) === "[object Function]"
    }
function isDate(value) {
        return Object.prototype.toString.call(value) === "[object Date]"
    }
function isRegExp(value) {
        return Object.prototype.toString.call(value) === "[object RegExp]"
    }

isDate(new Date()); // true
isRegExp(/w/);        // true
isFunction(function(){}); //true

上述程式碼,可以作進一步改進,由於 isFunction、isDate、isRegExp 方法中,其實只有 [object ConstructorName] ConstructorName不一樣,可以利用工廠函式再修飾一下:

 function generator(type){
        return function(value){
            return Object.prototype.toString.call(value) === "[object "+ type +"]"
        }
    }

 let isFunction = generator(`Function`)
 let isArray = generator(`Array`);
 let isDate = generator(`Date`);
 let isRegExp = generator(`RegExp`);

isArray([]));    // true
isDate(new Date()); // true
isRegExp(/w/);        // true
isFunction(function(){}); //true

這樣即使要生成其它原生物件驗證函式前面時就簡單多了;

總結:

  • typeof 適用於檢測值型別, 特別注意 null 的問題,這也是面試用經常遇到的.
  • instanceof 檢測引用型別.
  • toString 彌補 instanceof 在跨視窗下對型別檢測問題, toString 只適應於原生物件;
    對於使用者自定義的物件無法區分的.

相關文章