本文主要講解如何準確判斷JavaScript中出現的各類類型和對象。(基本類型、Object
類、Window
對象、純對象plainObject
、類數組)其中部分參考了jQuery的函數實現。
JavaScript定義的數據類型有Undefined
、Null
、Boolean
、Number
、String
、Object
、Symbol
(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'
來講,這實際上是一個bug
。github
在JavaScript中,Object
下還有不少細分的類型,好比說Date
、RegExp
、Error
、Array
、Function
。數組
typeof
除了可以準確的判斷出Function
以外,對於其餘細分類型均返回object
。函數
當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
函數,其中基本類型值走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()
會把Undefined
和Null
都識別爲[object Object]
,因此加了一個判斷,直接調用+
來隱式toString()-> "null"
。翻譯
這裏之因此class2type[Object.prototype.toString.call(obj)] || "object"
是考慮到ES6
新增的Symbol
、Map
、Set
在集合class2type
中沒有,直接把他們識別成object
。
這個type
其實就是jQuery
中的type
。
以後能夠直接封裝:
function isFunction(obj) { return type(obj) === "function"; }
var isArray = Array.isArray || function( obj ) { return type(obj) === "array"; }
jQuery3.0中已經徹底使用Array.isArray()
。
plainObject
翻譯爲中文即爲純對象,所謂的純對象,就是該對象是經過{}
或new Object()
建立的。
判斷是否爲「純對象」,是爲了和其餘對象區分開好比說null
、數組以及宿主對象(全部的DOM
和BOM
都是數組對象)等。
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; //true
常見的類數組有函數的arguments
和NodeList
對象。
對於類數組對象,只要該對象中存在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"]
參考連接: