【基礎】JavaScript類型判斷

本文主要講解如何準確判斷JavaScript中出現的各類類型和對象。(基本類型、 Object類、 Window對象、純對象 plainObject、類數組)其中部分參考了jQuery的函數實現。

typeof

JavaScript定義的數據類型有UndefinedNullBooleanNumberStringObjectSymbol(ES6新增)。javascript

其中typeof對大部分的數據類型都可以準確識別,以下:java

typeof undefined // "undefined"
typeof null // "object"
typeof true // "boolean"
typeof 1 // "number"
typeof "s" // "string"
typeof {} // "object"
typeof function a() {} // "function"
typeof Symbol('2') // "symbol"

其中返回的字符串首字母都是小寫的。git

對於typeof null === 'object'來講,這實際上是一個buggithub

在JavaScript中,Object下還有不少細分的類型,好比說DateRegExpErrorArrayFunction數組

typeof除了可以準確的判斷出Function以外,對於其餘細分類型均返回object函數

Object.prototype.toString()

toString方法被調用的時候,下面的步驟會被執行:this

  • 若是this值是undefined,就返回[object Undefined]
  • 若是this的值是null,就返回[object Null]
  • O成爲ToObject(this)的結果
  • class成爲O的內部屬性[[Class]]的值
  • 最後返回由"[object "class"]"三個部分組成的字符串

該方法至少能夠識別14種類型。es5

// 如下是11種:
var number = 1;          // [object Number]
var string = '123';      // [object String]
var boolean = true;      // [object Boolean]
var und = undefined;     // [object Undefined]
var nul = null;          // [object Null]
var obj = {a: 1}         // [object Object]
var array = [1, 2, 3];   // [object Array]
var date = new Date();   // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g;          // [object RegExp]
var func = function a(){}; // [object Function]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)

// 還有不常見的Math、JSON
console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]

// 還有一個arguments
function a() {
    console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();

type API

結合上面咱們能夠寫一個 type函數,其中基本類型值走 typeof,引用類型值走 toString
var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    // 一舉兩得
    if (obj == null) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}

經過toLowerCase()小寫化和typeof的結果是小寫一致。prototype

注意IE6中toString()會把UndefinedNull都識別爲[object Object],因此加了一個判斷,直接調用+來隱式toString()-> "null"翻譯

這裏之因此class2type[Object.prototype.toString.call(obj)] || "object"是考慮到ES6新增的SymbolMapSet在集合class2type中沒有,直接把他們識別成object

這個type其實就是jQuery中的type

isFunction

以後能夠直接封裝:

function isFunction(obj) {
    return type(obj) === "function";
}

數組

var isArray = Array.isArray || function( obj ) {
    return type(obj) === "array";
}

jQuery3.0中已經徹底使用Array.isArray()

plainObject

plainObject翻譯爲中文即爲純對象,所謂的純對象,就是該對象是經過{}new Object()建立的。

判斷是否爲「純對象」,是爲了和其餘對象區分開好比說null、數組以及宿主對象(全部的DOMBOM都是數組對象)等。

jQuery中有提供了該方法的實現,除了規定該對象是經過{}new Object()建立的,且對象含有零個或者多個鍵值對外,一個沒有原型(__proto__)的對象也是一個純對象。

console.log($.isPlainObject({})) // true

console.log($.isPlainObject(new Object)) // true

console.log($.isPlainObject(Object.create(null))); // true

jQuery3.0版本的plainObject實現以下:

var toString = Object.prototype.toString;

var hasOwn = Object.prototype.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;

    // 排除掉明顯不是obj的以及一些宿主對象如Window
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }

    /**
     * getPrototypeOf es5 方法,獲取 obj 的原型
     * 以 new Object 建立的對象爲例的話
     * obj.__proto__ === Object.prototype
     */
    proto = Object.getPrototypeOf(obj);

    // 沒有原型的對象是純粹的,Object.create(null) 就在這裏返回 true
    if (!proto) {
        return true;
    }

    /**
     * 如下判斷經過 new Object 方式建立的對象
     * 判斷 proto 是否有 constructor 屬性,若是有就讓 Ctor 的值爲 proto.constructor
     * 若是是 Object 函數建立的對象,Ctor 在這裏就等於 Object 構造函數
     */
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

    // 在這裏判斷 Ctor 構造函數是否是 Object 構造函數,用於區分自定義構造函數和 Object 構造函數
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}

注意最後這一句很是的重要:

hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object)

hasOwn.toString調用的實際上是Function.prototype.toString()而不是Object.prototype.toString(),由於hasOwnProperty是一個函數,它的原型是Function,因而Function.prototype.toString覆蓋了Object.prototype.toString

Function.prototype.toString()會把整個函數體轉換成一個字符串。若是該函數是內置函數的話,會返回一個表示函數源代碼的字符串。好比說:

Function.prototype.toString(Object) === function Object() { [native code] }

因此若是此時對象不是由內置構造函數生成的對象,這個hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object)false

function Person(name) {
    this.name = name;
}
var person = new Person("Devin");
plainObject(person) === false; // true
// 其實就是`hasOwn.toString.call(Ctor) === "function Person(name) { this.name = name; }"

Window對象

Window對象有一個特性:Window.window指向自身。

Window.window === Window; //true

類數組對象

常見的類數組有函數的argumentsNodeList對象。

判斷

對於類數組對象,只要該對象中存在length屬性而且length爲非負整數且在有限範圍以內便可判斷爲類數組對象。

JavaScript權威指南中提供了方法:

function isArrayLike(o) {
    if (o && // o is not null, undefined, etc
        // o is an object
        typeof o === "object" &&
        // o.length is a finite number
        isFinite(o.length) &&
        // o.length is non-negative
        o.length >= 0 &&
        // o.length is an integer
        o.length === Math.floor(o.length) &&
        // o.length < 2^32
        o.length < 4294967296) //數組的上限值
          return true;
    else 
          return false;
}

以上的判斷不管是真的數組對象或是類數組對象都會返回true,那咱們如何區分究竟是真的數組對象仍是類數組對象?

其實只須要先判斷是否爲數組對象便可。

function utilArray(o) {
    if (Array.isArray(o)) {
        return 'array';
    }
    if (isArrayLike(o)) {
        return 'arrayLike';
    } else {
        return 'neither array nor arrayLike';
    }
}

類數組對象的特徵

類數組對象並不關心除了數字索引和length之外的東西。

好比說:

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 5};
Array.prototype.join.call(a, "+"); // +a+b++c

其中,'0''3'沒有直接省略爲兩個undefined,一樣的abc被忽略爲undefined

若是length多出實際的位數會補undefined(空位也補充undefined),少位則截斷後面的數組成員。

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 6};
Array.from(a); // [undefined, "a", "b", undefined, "c", undefined]

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 5};
Array.from(a); // [undefined, "a", "b", undefined, "c"]

var a = {"1": "a", "2": "b", "4": "c", "abc": "abc", length: 4};
Array.from(a); // [undefined, "a", "b", undefined]

類數組對象的轉換

Array.from

該方法從一個相似數組或可迭代對象中建立一個新的數組實例。

Array.from('foo');
// ["f", "o", "o"]

Array.prototype.slice

該方法返回一個從開始到結束(不包括結束)選擇的數組的一部分淺拷貝到一個新數組對象。

var a = {"0":"a", "1":"b", "2":"c", length: 3};
Array.prototype.slice.call(a, 0); // ["a", "b", "c"]

ES6擴展運算符

var a = "hello";
[...a]; //["h", "e", "l", "l", "o"]

參考連接:

https://github.com/mqyqingfen...

https://github.com/mqyqingfen...

相關文章
相關標籤/搜索