JavaScript數(shù)據(jù)類型檢測(cè)
JavaScript中創(chuàng)建一個(gè)值的兩種方案
1、字面量方式
let a = 1;
let obj = {};
...
2、構(gòu)造函數(shù)方式
不能 new Symbol 和 new BigInt,可以通過Object(Symbol()/BigInt()),其他基本類型(除null和undefined)也可以這樣做
let a = new Number(1)
let obj = new Object()
...

對(duì)于基本數(shù)據(jù)類型,兩種方式的結(jié)果不同
字面量方式得到的是基本數(shù)據(jù)類型(特殊的實(shí)例)
通過構(gòu)造函數(shù)方式的得到的是對(duì)象類型(這才是一個(gè)正規(guī)的實(shí)例,基于
__proto__找到所屬類原型,valueOf獲取原始值)
字面量方式下,也可以調(diào)用
a.toFixed(2),是因?yàn)樗约簳?huì)先轉(zhuǎn)換為標(biāo)準(zhǔn)的實(shí)例new Number出來的結(jié)果,然后再去調(diào)用原型上的方法。但是1.toFixed(2)不行,(1).toFixed(2)可以
對(duì)于引用數(shù)據(jù)類型,兩種方式處理語法上的一些區(qū)別,沒有本質(zhì)的區(qū)別,獲取的都是對(duì)應(yīng)類的實(shí)例對(duì)象
JavaScript檢測(cè)數(shù)據(jù)類型的方法
1、typeof
typeof [value]
好處:簡(jiǎn)單方便,大部分?jǐn)?shù)據(jù)類型都可以檢測(cè)
-
缺點(diǎn):typeof null => "object"
這是JS的設(shè)計(jì)缺陷:數(shù)據(jù)值都是按照二進(jìn)制存儲(chǔ)的。
默認(rèn)1開頭的是整數(shù),010開頭的是浮點(diǎn)數(shù),100開頭的是字符串,110開頭的是布爾,000開頭的是對(duì)象,-2^30undefined,000000null 也說明typeof是按照二進(jìn)制存儲(chǔ)值進(jìn)行檢測(cè)的
typeof不能細(xì)分具體的對(duì)象數(shù)據(jù)類型值,所有的對(duì)象數(shù)據(jù)類型值,檢測(cè)出來的結(jié)果都是"object"
typeof檢測(cè)基于構(gòu)造函數(shù)創(chuàng)建出來的,基本數(shù)據(jù)類型的實(shí)例對(duì)象,結(jié)果也是"object"
console.log(typeof 1); // => number
console.log(typeof 1.1); // => number
console.log(typeof '1'); // => string
console.log(typeof true); // => boolean
console.log(typeof Symbol()); // => symbol
console.log(typeof BigInt(1)); // => bigint
console.log(typeof function(){}); // => function
console.log(typeof [1, 2]); // => object
console.log(typeof { a: 1 }); // => object
console.log(typeof new Number(1)); // => object
console.log(typeof new Number(1.1)); // => object
console.log(typeof new Boolean(true));// => object
console.log(typeof new Boolean(1)); // => object
console.log(typeof new String(1)); // => object
console.log(typeof new String('1')); // => object
console.log(typeof new Object(1)); // => object
console.log(typeof new Object()); // => object
console.log(typeof Object(Symbol(1)));// => object
console.log(typeof Object(BigInt(1)));// => object
console.log(typeof new Function()); // => function
let Fn = function () {}
console.log(typeof Fn); // => function
console.log(typeof new Fn()); // => object
2、instanceof
檢測(cè)某個(gè)實(shí)例是否屬于這個(gè)類?;趇nstanceof可以細(xì)分一下不同類型的對(duì)象,也可以檢測(cè)出基于構(gòu)造函數(shù)方式創(chuàng)建出來的基本類型對(duì)象值
原理:instanceof基于構(gòu)造函數(shù)
[Symbol.hasInstance](實(shí)例)。檢測(cè)當(dāng)前構(gòu)造函數(shù)的原型prototype是否出現(xiàn)在,當(dāng)前實(shí)例所處的原型鏈上,如果能出現(xiàn),結(jié)果就是true,否則就是false-
缺陷:在JS中的原型鏈?zhǔn)强梢愿膭?dòng)的,所以結(jié)果不準(zhǔn)確。所有實(shí)例的原型鏈最后都指向Object.prototype,所有 “實(shí)例 instanceof Object” 的結(jié)果都是
true。字面量方式創(chuàng)造的基本數(shù)據(jù)類型值是無法給予instanceof檢測(cè)的,瀏覽器默認(rèn)并不會(huì)把它轉(zhuǎn)換為new的方式,所以它本身不是對(duì)象,不存在
__proto__
let a = 1;
// 原因:基本數(shù)據(jù)類型不是對(duì)象,不存在__proto__
console.log(a instanceof Number); // => false
// -----------------------------------------------------------------
let b = [1, 2];
console.log(b instanceof Array); // => true
console.log(b instanceof Object); // => true
// -----------------------------------------------------------------
let c = {
0: 1,
1: 2,
length: 2
}
console.log(c instanceof Array); // => false
console.log(c instanceof Object); // => true
// -----------------------------------------------------------------
let d = {
a: 1,
b: 2
}
console.log(d instanceof Array); // => false
console.log(d instanceof Object); // => true
// -----------------------------------------------------------------
function Fn () {}
let fn = new Fn;
console.log(fn instanceof Array); // => false
// 原型鏈可以改動(dòng),所以結(jié)果也不準(zhǔn)確
Fn.prototype = Array.prototype;
let fn1 = new Fn;
// fn1是函數(shù)而不是數(shù)組,所以原型鏈改動(dòng)后,結(jié)果就不準(zhǔn)確了
console.log(fn1 instanceof Array); // => true
3、constructor
檢查直屬類是誰
注:constructor是可以肆意被修改,所以也不準(zhǔn)確
let a = 1;
// 瀏覽器會(huì)默認(rèn)把 a 轉(zhuǎn)換為 new Number,所以也可以用
console.log(a.constructor === Number); // => true
a = new Number(1);
console.log(a.constructor === Number); // => true
// -----------------------------------------------------------------
let b = [1, 2];
console.log(b.constructor === Array); // => true
console.log(b.constructor === Object); // => false
// -----------------------------------------------------------------
let c = {
0: 1,
1: 2,
length: 2
}
console.log(c.constructor === Array); // => false
console.log(c.constructor === Object); // => true
4、Object.prototype.toString.call
Object.prototype.toString.call([value])
這是一個(gè)萬全之策。 (除了代碼略長(zhǎng))
- 原理:大部分內(nèi)置類的原型是都有toString(),但是一般都是轉(zhuǎn)換為字符串,只有Object.prototype上的toString()并不是轉(zhuǎn)換為字符串,而是返回當(dāng)前實(shí)例對(duì)象所屬類的信息的'[object 所屬構(gòu)造函數(shù)的信息]'
所屬構(gòu)造函數(shù)的信息是根據(jù)Symbol.toStringTag這個(gè)屬性獲取的,有這個(gè)屬性基于這個(gè)獲取,沒有的瀏覽器自己計(jì)算。
// 用call是改變this指向,使用Object原型上的toString方法(鴨子類型)[原型上方法的借用]
// 也可以這樣寫:({}).toString.call([value])
let a = 1;
console.log(Object.prototype.toString.call(a)); // => [object Number]
a = new Number(1);
console.log(Object.prototype.toString.call(a)); // => [object Number]
// -----------------------------------------------------------------
let b = [1, 2];
console.log(Object.prototype.toString.call(b)); // => [object Array]
// -----------------------------------------------------------------
let c = {
0: 1,
1: 2,
length: 2
}
console.log(Object.prototype.toString.call(c)); // => [object Object]
// -----------------------------------------------------------------
let d = {
a: 1,
b: 2
}
console.log(Object.prototype.toString.call(d)); // => [object Object]
// -----------------------------------------------------------------
let Fn = function () {}
console.log(Object.prototype.toString.call(Fn)); // => [object Function]
let fn = new Fn;
console.log(Object.prototype.toString.call(fn)); // => [object Object]
可以簡(jiǎn)寫為下面這種方式:
let type = {};
let toString = type.toString; // => Object.prototype.toString
console.log(toString.call(1)); // => [object Number]
console.log(toString.call(new Number(1))); // => [object Number]
console.log(toString.call('1')); // => [object String]
console.log(toString.call(true)); // => [object Boolean]
console.log(toString.call(null)); // => [object Null]
console.log(toString.call(undefined)); // => [object Undefined]
console.log(toString.call([1, 2])); // => [object Array]
console.log(toString.call(/^\d+$/)); // => [object RegExp]
console.log(toString.call({})); // => [object Object]
console.log(toString.call(function () {})); // => [object Function]
關(guān)于Symbol.toStringTag這個(gè)屬性
function Fn () {}
Fn.prototype[Symbol.toStringTag] = 'Fn';
let fn = new Fn;
console.log(Object.prototype.toString.call(fn)); // => [object Fn]
5、數(shù)組檢測(cè)的其他方式
Array.isArray
let a = [];
console.log(Array.isArray([])); // => true
其他
// 基于正則
// 這樣寫代碼太長(zhǎng)
console.log(/array/i.test(Object.prototype.toString.call([]))); // => true
jQuery源碼中的數(shù)據(jù)檢測(cè)代碼分析及部分重寫
這些方法可以直接引入到項(xiàng)目中使用。
var class2type = {};
var toString = class2type.toString; // Object.prototype.toString 檢測(cè)數(shù)據(jù)類型
var hasOwn = class2type.hasOwnProperty; // Object.prototype.hasOwnProperty 檢測(cè)是否是私有屬性
var fnToString = hasOwn.toString; // Function.prototype.toString 把函數(shù)轉(zhuǎn)換為字符串
var ObjectFunctionString = fnToString.call(Object);
// => "function Object() { [native code] }"
var getProto = Object.getPrototypeOf; // 獲取當(dāng)前對(duì)象的原型鏈__proto__
// 檢測(cè)是否為函數(shù)
var isFunction = function isFunction(obj) {
// typeof obj.nodeType !== "number":防止在部分瀏覽器中,檢測(cè)<object>元素對(duì)象結(jié)果也是"function",但是它的nodeType是1,處理瀏覽器兼容問題
// 元素節(jié)點(diǎn)(DOM對(duì)象)具備nodeType,值是1
// 所有的DOM節(jié)點(diǎn)具備nodeType。元素:1,文本:3,注釋:8,document:9
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
// 檢測(cè)是否為window對(duì)象
var isWindow = function isWindow(obj) {
// window對(duì)象有個(gè)特點(diǎn):window上有個(gè)屬性是window
// window.window === window (符合這個(gè)條件的就是window對(duì)象)
return obj != null && obj === obj.window;
};
// 檢測(cè)數(shù)據(jù)類型的方法
var toType = function toType(obj) {
if (obj == null) {
// 傳遞的null或者undefined或者null,直接返回字符串'undefined'或者'null'
return obj + "";
}
// 基于字面量方式創(chuàng)造的基本數(shù)據(jù)類型,直接基于typeof檢測(cè)類型即可(性能會(huì)好一些)(function也可以走typeof)
// 剩余的基于Object.prototyp.toString.call的方式來檢測(cè):格式 "[object xxx]"->對(duì)應(yīng)到class2type映射表直接拿到對(duì)應(yīng)數(shù)據(jù)類型字符串
return typeof obj === "object" || typeof obj === "function" ? class2type[toString.call(obj)] || "object" : typeof obj;
}
/**
jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),
function (_i, name) {
class2type["[object " + name + "]"] = name.toLowerCase();
});
*/
// 建立數(shù)據(jù)類型檢測(cè)映射表(jq源碼是上面這樣實(shí)現(xiàn)的)
// class2type: { [object Array]: "array", [object Boolean]: "boolean", ... }
var mapType = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol", "BigInt"];
mapType.forEach(function (name) {
class2type["[object " + name + "]"] = name.toLowerCase();
});
// 檢測(cè)是否為數(shù)組或者類數(shù)組
var isArrayLike = function isArrayLike(obj) {
// length存儲(chǔ)的是對(duì)象的length屬性值或者是false
// type存儲(chǔ)的是檢測(cè)的數(shù)據(jù)類型
var length = !!obj && "length" in obj && obj.length, type = toType(obj);
// 一定不是數(shù)組或類數(shù)組。window.length = 0 && Function.prototype.length = 0,需要排除掉
if (isFunction(obj) || isWindow(obj)) { return false; }
// type === "array":是數(shù)組
// length === 0:是空的類數(shù)組
// typeof length === "number" && length > 0 && (length - 1) in obj:如果有l(wèi)ength屬性并且是一個(gè)數(shù)字且不是空的類數(shù)組,并且索引是按序增長(zhǎng)的(最大索引在對(duì)象中)
return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;
}
// 檢測(cè)是否為純粹的對(duì)象,如{}
// 實(shí)例對(duì)象不是純粹的對(duì)象
var isPlainObject = function isPlainObject (obj) {
var proto, Ctor;
// 如果不存在 或者 基于toString檢測(cè)的結(jié)果不是"[object Object]",一定不是純對(duì)象
if (!obj || toString.call(obj) !== "[object Object]") {
return false;
}
// 獲取當(dāng)前值的原型鏈(直屬類的原型鏈)
proto = getProto(obj);
// Object.create(null):這樣創(chuàng)造的對(duì)象沒有__proto__
if (!proto) return true;
// Ctor存儲(chǔ)原型對(duì)象上的constructor這個(gè)屬性,沒有這個(gè)屬性就是false
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
// 條件成立說明原型上的構(gòu)造函數(shù)是Object:當(dāng)前對(duì)象就是Object的一個(gè)實(shí)例,并且obj.__proto__ === Object.prototype
return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
}
// 檢測(cè)是否為空對(duì)象
var isEmptyObject = function isEmptyObject (obj) {
/**
* jQ實(shí)現(xiàn)的方式(但這種for...in...的方式不太好):
* var name;
for (name in obj) { return false; }
return true;
*/
// obj是undefined或者null,排除掉
if (obj == null) { return false; }
// 基于typeof檢測(cè)不是object,不是對(duì)象類型
if (typeof obj !== "object") { return false }
// 是一個(gè)對(duì)象(純粹但這或者特殊對(duì)象)
var keys = Object.keys(obj); // Object.keys()[IE6,7,8不兼容] -> 拿到的私有的屬性,symbol拿不到
if (hasOwn.call(Object, 'getOwnPropertySymbols')) { // getOwnPropertySymbols兼容性差,看看Object有沒有這個(gè)屬性
// 兼容這個(gè)屬性的情況下,再去拼接
keys = keys.concat(Object.getOwnPropertySymbols(obj));
}
return keys === 0;
}
// 檢測(cè)是否為數(shù)字
var isNumeric = function isNumeric (obj) {
var type = jQuery.type(obj);
// 如果不是數(shù)字或者字符串,一定不是數(shù)字
// 如果是字符串,需要處理:用之前的值與浮點(diǎn)型進(jìn)行數(shù)學(xué)計(jì)算,如果結(jié)果是NaN,那就不是數(shù)字
// 排除16進(jìn)制數(shù)字
// return (type === "number" || type === "string") && !isNaN(obj - parseFloat(obj)); // => 【jq實(shí)現(xiàn)的方式】
return (type === "number" || type === "string") && !isNaN(+obj);
}
// 數(shù)組或類數(shù)組循環(huán)遍歷
var each = function each (obj, callback) {
var length, i = 0;
if (isArrayLike(obj)) {
length = obj.length;
for (; i < length; i++) {
if (callback.call(obj[i], i, obj[i]) === false) {
break;
}
}
} else {
var keys = Object.keys(obj);
// 如果瀏覽器兼容Symbol,也要處理Symbol屬性的遍歷
typeof Symbol !== "undefined" ? keys = keys.concat(Object.getOwnPropertySymbols(obj)) : null;
for (; i < keys.length; i++) {
var key = keys[i];
if (callback.call(obj[key], key, obj[key]) === false) {
break;
}
}
}
return obj;
}